From 9e01ad5018335c6b6c471fbfd4f2f39f7373009c Mon Sep 17 00:00:00 2001 From: cp-Coder Date: Mon, 15 Aug 2022 12:40:08 +0530 Subject: [PATCH 01/44] Added Patient Health Details model - Added facility & patient as ForeignKey field - Added has_allergy as BooleanField - Added family_details & allergies as TextField - Added blood_group as CharField --- care/facility/models/patient.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/care/facility/models/patient.py b/care/facility/models/patient.py index 618cc5929e..1e6d7690b1 100644 --- a/care/facility/models/patient.py +++ b/care/facility/models/patient.py @@ -766,3 +766,29 @@ class PatientNotes(FacilityBaseModel, PatientRelatedPermissionMixin): null=True, ) note = models.TextField(default="", blank=True) + + +class PatientHealthDetails(PatientBaseModel, PatientRelatedPermissionMixin): + patient = models.ForeignKey( + PatientRegistration, on_delete=models.CASCADE, related_name="health_details" + ) + facility = models.ForeignKey( + Facility, on_delete=models.CASCADE, related_name="health_details" + ) + + family_details = models.TextField( + default="", blank=True, verbose_name="Patient's Family Details" + ) + + has_allergy = models.BooleanField(default=False) + allergies = models.TextField( + default="", blank=True, verbose_name="Patient's Known Allergies" + ) + + blood_group = models.CharField( + choices=BLOOD_GROUP_CHOICES, + null=True, + blank=False, + max_length=4, + verbose_name="Blood Group of Patient", + ) From c41a1337045bf24b781f0d449a0f6471da03646c Mon Sep 17 00:00:00 2001 From: cp-Coder Date: Mon, 15 Aug 2022 12:43:54 +0530 Subject: [PATCH 02/44] Added migrations for patient health details model --- .../migrations/0309_patienthealthdetails.py | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 care/facility/migrations/0309_patienthealthdetails.py diff --git a/care/facility/migrations/0309_patienthealthdetails.py b/care/facility/migrations/0309_patienthealthdetails.py new file mode 100644 index 0000000000..e517fdbfd1 --- /dev/null +++ b/care/facility/migrations/0309_patienthealthdetails.py @@ -0,0 +1,70 @@ +import care.facility.models.mixins.permissions.patient +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ("facility", "0308_auto_20220805_2247"), + ] + + operations = [ + migrations.CreateModel( + name="PatientHealthDetails", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "external_id", + models.UUIDField(db_index=True, default=uuid.uuid4, unique=True), + ), + ( + "created_date", + models.DateTimeField(auto_now_add=True, db_index=True, null=True), + ), + ( + "modified_date", + models.DateTimeField(auto_now=True, db_index=True, null=True), + ), + ("deleted", models.BooleanField(db_index=True, default=False)), + ( + "family_details", + models.TextField( + blank=True, default="", verbose_name="Patient's Family Details" + ), + ), + ( + "facility", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="health_details", + to="facility.Facility", + ), + ), + ( + "patient", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="health_details", + to="facility.PatientRegistration", + ), + ), + ], + options={ + "abstract": False, + }, + bases=( + models.Model, + care.facility.models.mixins.permissions.patient.PatientRelatedPermissionMixin, + ), + ), + ] From 7ae6f875a1be129ddf8da6d4cb47221eb966bfa5 Mon Sep 17 00:00:00 2001 From: cp-Coder Date: Mon, 15 Aug 2022 12:44:56 +0530 Subject: [PATCH 03/44] Added migrations for new fields - Added has_allergy as BooleanField - Migrated blood_group & allergies fields from PatientRegistration Model --- .../migrations/0310_auto_20220813_1123.py | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 care/facility/migrations/0310_auto_20220813_1123.py diff --git a/care/facility/migrations/0310_auto_20220813_1123.py b/care/facility/migrations/0310_auto_20220813_1123.py new file mode 100644 index 0000000000..51c5cc5460 --- /dev/null +++ b/care/facility/migrations/0310_auto_20220813_1123.py @@ -0,0 +1,59 @@ +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("facility", "0309_patienthealthdetails"), + ] + + operations = [ + migrations.AddField( + model_name="patienthealthdetails", + name="allergies", + field=models.TextField( + blank=True, default="", verbose_name="Patient's Known Allergies" + ), + ), + migrations.AddField( + model_name="patienthealthdetails", + name="blood_group", + field=models.CharField( + choices=[ + ("A+", "A+"), + ("A-", "A-"), + ("B+", "B+"), + ("B-", "B-"), + ("AB+", "AB+"), + ("AB-", "AB-"), + ("O+", "O+"), + ("O-", "O-"), + ("UNK", "UNKNOWN"), + ], + max_length=4, + null=True, + verbose_name="Blood Group of Patient", + ), + ), + migrations.AddField( + model_name="patienthealthdetails", + name="has_allergy", + field=models.BooleanField(default=False), + ), + migrations.RemoveField( + model_name="historicalpatientregistration", + name="allergies", + ), + migrations.RemoveField( + model_name="historicalpatientregistration", + name="blood_group", + ), + migrations.RemoveField( + model_name="patientregistration", + name="allergies", + ), + migrations.RemoveField( + model_name="patientregistration", + name="blood_group", + ), + ] From a3726f44ac7e3362c443ebd622b9fbc1f4a67234 Mon Sep 17 00:00:00 2001 From: cp-Coder Date: Mon, 15 Aug 2022 12:46:06 +0530 Subject: [PATCH 04/44] Added logic to create health details for existing patients --- .../migrations/0310_auto_20220813_1123.py | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/care/facility/migrations/0310_auto_20220813_1123.py b/care/facility/migrations/0310_auto_20220813_1123.py index 51c5cc5460..5507589cd8 100644 --- a/care/facility/migrations/0310_auto_20220813_1123.py +++ b/care/facility/migrations/0310_auto_20220813_1123.py @@ -1,6 +1,37 @@ from django.db import migrations, models +def populate_data(apps, schema_editor): + HealthDetails = apps.get_model("facility", "PatientHealthDetails") + PatientRegistration = apps.get_model("facility", "PatientRegistration") + patients = PatientRegistration.objects.all() + + health_details_objs = [] + for patient in patients: + has_allergy = False + allergies = "" + blood_group = None + + if patient.allergies: + has_allergy = True + allergies = patient.allergies + + if patient.blood_group is not None: + blood_group = patient.blood_group + + health_details_objs.append( + HealthDetails( + patient=patient, + facility=patient.facility, + has_allergy=has_allergy, + allergies=allergies, + blood_group=blood_group, + ) + ) + + HealthDetails.objects.bulk_create(health_details_objs) + + class Migration(migrations.Migration): dependencies = [ @@ -40,6 +71,7 @@ class Migration(migrations.Migration): name="has_allergy", field=models.BooleanField(default=False), ), + migrations.RunPython(populate_data, migrations.RunPython.noop), migrations.RemoveField( model_name="historicalpatientregistration", name="allergies", From b28c25762bc50626e4600f2384d8171252d68eb5 Mon Sep 17 00:00:00 2001 From: cp-Coder Date: Mon, 15 Aug 2022 12:51:12 +0530 Subject: [PATCH 05/44] Added Patient Health details serializer - Added import statement of PatientHealthDetails model - Removed blood_group field from PatientListSerializer - Removed allergies from exclude option of PatientListSerializer - Added serializer for patient health details --- care/facility/api/serializers/patient.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/care/facility/api/serializers/patient.py b/care/facility/api/serializers/patient.py index 5a0bdbd7c5..fdda692277 100644 --- a/care/facility/api/serializers/patient.py +++ b/care/facility/api/serializers/patient.py @@ -25,6 +25,7 @@ PatientSearch, ) from care.facility.models.notification import Notification +from care.facility.models.patient import PatientHealthDetails from care.facility.models.patient_base import ( BLOOD_GROUP_CHOICES, DISEASE_STATUS_CHOICES, @@ -71,7 +72,6 @@ class PatientListSerializer(serializers.ModelSerializer): last_consultation = PatientConsultationSerializer(read_only=True) - blood_group = ChoiceField(choices=BLOOD_GROUP_CHOICES, required=True) disease_status = ChoiceField( choices=DISEASE_STATUS_CHOICES, default=DiseaseStatusEnum.SUSPECTED.value ) @@ -89,7 +89,6 @@ class Meta: "year_of_birth", "meta_info", "countries_travelled_old", - "allergies", "external_id", ) read_only = TIMESTAMP_FIELDS @@ -433,3 +432,14 @@ class Meta: model = PatientNotes fields = ("note", "facility", "created_by_object", "created_date") read_only_fields = ("created_date",) + + +class PatientHealthDetailsSerializer(serializers.ModelSerializer): + patient = ExternalIdSerializerField(queryset=PatientRegistration.objects.all()) + facility = ExternalIdSerializerField(queryset=Facility.objects.all()) + facility_object = FacilityBasicInfoSerializer(source="facility", read_only=True) + blood_group = ChoiceField(choices=BLOOD_GROUP_CHOICES, required=True) + + class Meta: + model = PatientHealthDetails + exclude = ("deleted", "external_id") From 45d054f4df2629eb30a5a64e2598d916f194f89f Mon Sep 17 00:00:00 2001 From: cp-Coder Date: Mon, 15 Aug 2022 12:54:29 +0530 Subject: [PATCH 06/44] Added viewset for patient health details --- care/facility/api/viewsets/patient.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/care/facility/api/viewsets/patient.py b/care/facility/api/viewsets/patient.py index a897eec81e..40adc8ee9f 100644 --- a/care/facility/api/viewsets/patient.py +++ b/care/facility/api/viewsets/patient.py @@ -15,7 +15,12 @@ from rest_framework.decorators import action from rest_framework.exceptions import ValidationError from rest_framework.generics import get_object_or_404 -from rest_framework.mixins import CreateModelMixin, ListModelMixin, RetrieveModelMixin +from rest_framework.mixins import ( + CreateModelMixin, + ListModelMixin, + RetrieveModelMixin, + UpdateModelMixin, +) from rest_framework.pagination import PageNumberPagination from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response @@ -24,6 +29,7 @@ from care.facility.api.serializers.patient import ( FacilityPatientStatsHistorySerializer, PatientDetailSerializer, + PatientHealthDetailsSerializer, PatientListSerializer, PatientNotesSerializer, PatientSearchSerializer, @@ -44,6 +50,7 @@ PatientRegistration, PatientSearch, ShiftingRequest, + PatientHealthDetails, ) from care.facility.models.base import covert_choice_dict from care.facility.models.bed import AssetBed, ConsultationBed @@ -609,3 +616,15 @@ def perform_create(self, serializer): return serializer.save( facility=patient.facility, patient=patient, created_by=self.request.user ) + + +class PatientHealthDetailsViewSet( + RetrieveModelMixin, + CreateModelMixin, + ListModelMixin, + UpdateModelMixin, + GenericViewSet, +): + queryset = PatientHealthDetails.objects.all().select_related("facility", "patient") + serializer_class = PatientHealthDetailsSerializer + permission_classes = (IsAuthenticated, DRYPermissions) From 864040e8230ef9e3b87e8609b07269646da21f3b Mon Sep 17 00:00:00 2001 From: cp-Coder Date: Mon, 15 Aug 2022 12:58:48 +0530 Subject: [PATCH 07/44] Added api route for patient health details --- config/api_router.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config/api_router.py b/config/api_router.py index dd949a3a1d..fcd65e4859 100644 --- a/config/api_router.py +++ b/config/api_router.py @@ -34,6 +34,7 @@ from care.facility.api.viewsets.notification import NotificationViewSet from care.facility.api.viewsets.patient import ( FacilityPatientStatsHistoryViewSet, + PatientHealthDetailsViewSet, PatientNotesViewSet, PatientSearchViewSet, PatientViewSet, @@ -110,6 +111,7 @@ router.register("patient", PatientViewSet) router.register("consultation", PatientConsultationViewSet) +router.register("health_details", PatientHealthDetailsViewSet) router.register("external_result", PatientExternalTestViewSet) From 7a2766d2c8ea1ea2bdd75953efd0fd6f59c287fc Mon Sep 17 00:00:00 2001 From: cp-Coder Date: Wed, 31 Aug 2022 15:17:13 +0530 Subject: [PATCH 08/44] Added created_in_consultation in patient_consultation serializer --- .../api/serializers/patient_consultation.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/care/facility/api/serializers/patient_consultation.py b/care/facility/api/serializers/patient_consultation.py index 2841097385..007b605dcc 100644 --- a/care/facility/api/serializers/patient_consultation.py +++ b/care/facility/api/serializers/patient_consultation.py @@ -1,6 +1,11 @@ from datetime import timedelta from django.utils.timezone import localtime, now +from care.facility.api.serializers.patient_health_details import ( + PatientHealthDetailsSerializer, +) + +from care.facility.models.patient import PatientHealthDetails from rest_framework import serializers from rest_framework.exceptions import ValidationError @@ -77,6 +82,7 @@ class PatientConsultationSerializer(serializers.ModelSerializer): last_edited_by = UserBaseMinimumSerializer(read_only=True) created_by = UserBaseMinimumSerializer(read_only=True) last_daily_round = DailyRoundSerializer(read_only=True) + last_health_details = PatientHealthDetailsSerializer(read_only=True) current_bed = ConsultationBedSerializer(read_only=True) @@ -239,6 +245,24 @@ def create(self, validated_data): consultation.last_edited_by = self.context["request"].user consultation.save() + # Check if health details has been created + try: + health_details = PatientHealthDetails.objects.get( + id=self.validated_data["patient"].id + ) + health_details.created_in_consultation = consultation + health_details.save() + consultation.last_health_details = health_details + consultation.save(update_fields=["last_health_details"]) + except PatientHealthDetails.DoesNotExist: + raise ValidationError( + { + "last_health_details": [ + "Patient heatlh details has not been registered" + ] + } + ) + if bed: consultation_bed = ConsultationBed( bed=bed, consultation=consultation, start_date=consultation.created_date From c37a6b3a7ab1c662bfd6f4600afcc12cee1e06bd Mon Sep 17 00:00:00 2001 From: cp-Coder Date: Wed, 31 Aug 2022 15:18:41 +0530 Subject: [PATCH 09/44] Migrated patient health serializer to new file and added new fields --- care/facility/api/serializers/patient.py | 11 -------- .../api/serializers/patient_health_details.py | 27 +++++++++++++++++++ 2 files changed, 27 insertions(+), 11 deletions(-) create mode 100644 care/facility/api/serializers/patient_health_details.py diff --git a/care/facility/api/serializers/patient.py b/care/facility/api/serializers/patient.py index fdda692277..4a64b0034f 100644 --- a/care/facility/api/serializers/patient.py +++ b/care/facility/api/serializers/patient.py @@ -432,14 +432,3 @@ class Meta: model = PatientNotes fields = ("note", "facility", "created_by_object", "created_date") read_only_fields = ("created_date",) - - -class PatientHealthDetailsSerializer(serializers.ModelSerializer): - patient = ExternalIdSerializerField(queryset=PatientRegistration.objects.all()) - facility = ExternalIdSerializerField(queryset=Facility.objects.all()) - facility_object = FacilityBasicInfoSerializer(source="facility", read_only=True) - blood_group = ChoiceField(choices=BLOOD_GROUP_CHOICES, required=True) - - class Meta: - model = PatientHealthDetails - exclude = ("deleted", "external_id") diff --git a/care/facility/api/serializers/patient_health_details.py b/care/facility/api/serializers/patient_health_details.py new file mode 100644 index 0000000000..e4f07556ec --- /dev/null +++ b/care/facility/api/serializers/patient_health_details.py @@ -0,0 +1,27 @@ +from django.forms import ChoiceField +from care.facility.models.facility import Facility +from care.facility.models.patient_base import BLOOD_GROUP_CHOICES +from rest_framework import serializers +from care.facility.models import ( + PatientHealthDetails, + PatientRegistration, + PatientConsultation, +) +from care.facility.api.serializers.facility import FacilityBasicInfoSerializer +from care.utils.serializer.external_id_field import ExternalIdSerializerField + + +class PatientHealthDetailsSerializer(serializers.ModelSerializer): + id = serializers.CharField(source="external_id", read_only=True) + + patient = ExternalIdSerializerField(queryset=PatientRegistration.objects.all()) + facility = ExternalIdSerializerField(queryset=Facility.objects.all()) + facility_object = FacilityBasicInfoSerializer(source="facility", read_only=True) + created_in_consultation = ExternalIdSerializerField( + queryset=PatientConsultation.objects.all(), required=False + ) + blood_group = ChoiceField(choices=BLOOD_GROUP_CHOICES, required=True) + + class Meta: + model = PatientHealthDetails + exclude = ("deleted", "external_id") From 6bc2ad1366b8efa830ada6a5f7ee4001700dc2c3 Mon Sep 17 00:00:00 2001 From: cp-Coder Date: Wed, 31 Aug 2022 15:21:03 +0530 Subject: [PATCH 10/44] Migrated height and weight fields to patient health details model --- care/facility/models/patient.py | 20 ++++++++++++++++++++ care/facility/models/patient_consultation.py | 15 +++------------ 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/care/facility/models/patient.py b/care/facility/models/patient.py index 1e6d7690b1..04072ad30b 100644 --- a/care/facility/models/patient.py +++ b/care/facility/models/patient.py @@ -792,3 +792,23 @@ class PatientHealthDetails(PatientBaseModel, PatientRelatedPermissionMixin): max_length=4, verbose_name="Blood Group of Patient", ) + + created_in_consultation = models.ForeignKey( + PatientConsultation, + on_delete=models.CASCADE, + null=True, + ) + + height = models.FloatField( + default=None, + null=True, + verbose_name="Patient's Height in CM", + validators=[MinValueValidator(0)], + ) + + weight = models.FloatField( + default=None, + null=True, + verbose_name="Patient's Weight in KG", + validators=[MinValueValidator(0)], + ) diff --git a/care/facility/models/patient_consultation.py b/care/facility/models/patient_consultation.py index d6075d6e8d..631885fc14 100644 --- a/care/facility/models/patient_consultation.py +++ b/care/facility/models/patient_consultation.py @@ -126,20 +126,11 @@ class PatientConsultation(PatientBaseModel, PatientRelatedPermissionMixin): default=None, ) + last_health_details = models.ForeignKey( + "PatientHealthDetails", on_delete=models.SET_NULL, null=True, default=None + ) # Physical Information - height = models.FloatField( - default=None, - null=True, - verbose_name="Patient's Height in CM", - validators=[MinValueValidator(0)], - ) - weight = models.FloatField( - default=None, - null=True, - verbose_name="Patient's Weight in KG", - validators=[MinValueValidator(0)], - ) HBA1C = models.FloatField( default=None, null=True, From 1b63c8bfaa5a7ca27aa43169f8f5926a1b8e2252 Mon Sep 17 00:00:00 2001 From: cp-Coder Date: Wed, 31 Aug 2022 15:22:05 +0530 Subject: [PATCH 11/44] Added migrations for migrating height and weight fields --- .../migrations/0311_auto_20220831_0900.py | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 care/facility/migrations/0311_auto_20220831_0900.py diff --git a/care/facility/migrations/0311_auto_20220831_0900.py b/care/facility/migrations/0311_auto_20220831_0900.py new file mode 100644 index 0000000000..c66c0354a8 --- /dev/null +++ b/care/facility/migrations/0311_auto_20220831_0900.py @@ -0,0 +1,83 @@ +import django.core.validators +from django.db import migrations, models +import django.db.models.deletion + +from care.facility.models.patient import PatientHealthDetails + + +def populate_data(apps, schema_editor): + PatientConsultation = apps.get_model("facility", "PatientConsultation") + patients_cons = PatientConsultation.objects.all() + + for patient_cons in patients_cons: + try: + health_details = PatientHealthDetails.objects.get( + id=patient_cons.patient.id + ) + if patient_cons.weight: + health_details.weight = patient_cons.weight + + if patient_cons.height: + health_details.height = patient_cons.height + + health_details.save() + except PatientHealthDetails.DoesNotExist: + pass + + +class Migration(migrations.Migration): + + dependencies = [ + ("facility", "0310_auto_20220813_1123"), + ] + + operations = [ + migrations.AddField( + model_name="patienthealthdetails", + name="created_in_consultation", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="facility.PatientConsultation", + ), + ), + migrations.AddField( + model_name="patientconsultation", + name="last_health_details", + field=models.ForeignKey( + default=None, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="facility.PatientHealthDetails", + ), + ), + migrations.AddField( + model_name="patienthealthdetails", + name="height", + field=models.FloatField( + default=None, + null=True, + validators=[django.core.validators.MinValueValidator(0)], + verbose_name="Patient's Height in CM", + ), + ), + migrations.AddField( + model_name="patienthealthdetails", + name="weight", + field=models.FloatField( + default=None, + null=True, + validators=[django.core.validators.MinValueValidator(0)], + verbose_name="Patient's Weight in KG", + ), + ), + migrations.RunPython(populate_data, migrations.RunPython.noop), + migrations.RemoveField( + model_name="patientconsultation", + name="height", + ), + migrations.RemoveField( + model_name="patientconsultation", + name="weight", + ), + ] From bc956b215c78516929e93bc70c762f5f09ff0af0 Mon Sep 17 00:00:00 2001 From: cp-Coder Date: Wed, 31 Aug 2022 15:22:41 +0530 Subject: [PATCH 12/44] Minor change in import statement --- care/facility/api/viewsets/patient.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/care/facility/api/viewsets/patient.py b/care/facility/api/viewsets/patient.py index 40adc8ee9f..ee8c845b14 100644 --- a/care/facility/api/viewsets/patient.py +++ b/care/facility/api/viewsets/patient.py @@ -29,12 +29,14 @@ from care.facility.api.serializers.patient import ( FacilityPatientStatsHistorySerializer, PatientDetailSerializer, - PatientHealthDetailsSerializer, PatientListSerializer, PatientNotesSerializer, PatientSearchSerializer, PatientTransferSerializer, ) +from care.facility.api.serializers.patient_health_details import ( + PatientHealthDetailsSerializer, +) from care.facility.api.serializers.patient_icmr import PatientICMRSerializer from care.facility.api.viewsets import UserAccessMixin from care.facility.api.viewsets.mixins.history import HistoryMixin @@ -628,3 +630,4 @@ class PatientHealthDetailsViewSet( queryset = PatientHealthDetails.objects.all().select_related("facility", "patient") serializer_class = PatientHealthDetailsSerializer permission_classes = (IsAuthenticated, DRYPermissions) + lookup_field = "external_id" From 1da70bc6e747b333d84fc6b0da658e0d08b94f78 Mon Sep 17 00:00:00 2001 From: cp-Coder Date: Wed, 31 Aug 2022 15:23:44 +0530 Subject: [PATCH 13/44] Formatted the code --- care/facility/api/serializers/patient.py | 14 ++- care/facility/api/viewsets/patient_otp.py | 17 ++- care/facility/models/patient.py | 136 +++++++++++++++------- 3 files changed, 113 insertions(+), 54 deletions(-) diff --git a/care/facility/api/serializers/patient.py b/care/facility/api/serializers/patient.py index 4a64b0034f..1e6a1b3962 100644 --- a/care/facility/api/serializers/patient.py +++ b/care/facility/api/serializers/patient.py @@ -131,7 +131,9 @@ class Meta: phone_number = PhoneNumberIsPossibleField() - facility = ExternalIdSerializerField(queryset=Facility.objects.all(), required=False) + facility = ExternalIdSerializerField( + queryset=Facility.objects.all(), required=False + ) medical_history = serializers.ListSerializer( child=MedicalHistorySerializer(), required=False ) @@ -385,11 +387,11 @@ class PatientSearchSerializer(serializers.ModelSerializer): class Meta: model = PatientSearch exclude = ( - "date_of_birth", - "year_of_birth", - "external_id", - "id", - ) + TIMESTAMP_FIELDS + "date_of_birth", + "year_of_birth", + "external_id", + "id", + ) + TIMESTAMP_FIELDS class PatientTransferSerializer(serializers.ModelSerializer): diff --git a/care/facility/api/viewsets/patient_otp.py b/care/facility/api/viewsets/patient_otp.py index 6bd4b49280..c16936eee5 100644 --- a/care/facility/api/viewsets/patient_otp.py +++ b/care/facility/api/viewsets/patient_otp.py @@ -1,4 +1,3 @@ -from rest_framework import serializers, status from rest_framework.decorators import action from rest_framework import mixins from rest_framework.permissions import AllowAny @@ -6,7 +5,9 @@ from rest_framework.viewsets import GenericViewSet from rest_framework.exceptions import ValidationError from django.conf import settings -from care.facility.api.serializers.patient_otp import PatientMobileOTPSerializer +from care.facility.api.serializers.patient_otp import ( + PatientMobileOTPSerializer, +) from care.facility.models.patient import PatientMobileOTP from care.users.models import phone_number_regex @@ -15,7 +16,8 @@ class PatientMobileOTPViewSet( - mixins.CreateModelMixin, GenericViewSet, + mixins.CreateModelMixin, + GenericViewSet, ): permission_classes = (AllowAny,) serializer_class = PatientMobileOTPSerializer @@ -30,11 +32,15 @@ def login(self, request): try: phone_number_regex(phone_number) except: - raise ValidationError({"phone_number": "Invalid phone number format"}) + raise ValidationError( + {"phone_number": "Invalid phone number format"} + ) if len(otp) != settings.OTP_LENGTH: raise ValidationError({"otp": "Invalid OTP"}) - otp_object = PatientMobileOTP.objects.filter(phone_number=phone_number, otp=otp, is_used=False).first() + otp_object = PatientMobileOTP.objects.filter( + phone_number=phone_number, otp=otp, is_used=False + ).first() if not otp_object: raise ValidationError({"otp": "Invalid OTP"}) @@ -47,4 +53,3 @@ def login(self, request): token["phone_number"] = phone_number return Response({"access": str(token)}) - diff --git a/care/facility/models/patient.py b/care/facility/models/patient.py index 04072ad30b..a205b12caa 100644 --- a/care/facility/models/patient.py +++ b/care/facility/models/patient.py @@ -97,8 +97,12 @@ class TestTypeEnum(enum.Enum): TestTypeChoices = [(e.value, e.name) for e in TestTypeEnum] - source = models.IntegerField(choices=SourceChoices, default=SourceEnum.CARE.value) - facility = models.ForeignKey("Facility", on_delete=models.SET_NULL, null=True) + source = models.IntegerField( + choices=SourceChoices, default=SourceEnum.CARE.value + ) + facility = models.ForeignKey( + "Facility", on_delete=models.SET_NULL, null=True + ) nearest_facility = models.ForeignKey( "Facility", on_delete=models.SET_NULL, @@ -137,7 +141,9 @@ class TestTypeEnum(enum.Enum): max_length=255, default="", verbose_name="Nationality of Patient" ) passport_no = models.CharField( - max_length=255, default="", verbose_name="Passport Number of Foreign Patients" + max_length=255, + default="", + verbose_name="Passport Number of Foreign Patients", ) # aadhar_no = models.CharField(max_length=255, default="", verbose_name="Aadhar Number of Patient") @@ -145,14 +151,6 @@ class TestTypeEnum(enum.Enum): default=False, verbose_name="Is the Patient a Medical Worker" ) - blood_group = models.CharField( - choices=BLOOD_GROUP_CHOICES, - null=True, - blank=False, - max_length=4, - verbose_name="Blood Group of Patient", - ) - contact_with_confirmed_carrier = models.BooleanField( default=False, verbose_name="Confirmed Contact with a Covid19 Carrier" ) @@ -172,7 +170,9 @@ class TestTypeEnum(enum.Enum): editable=False, ) countries_travelled = JSONField( - null=True, blank=True, verbose_name="Countries Patient has Travelled to" + null=True, + blank=True, + verbose_name="Countries Patient has Travelled to", ) date_of_return = models.DateTimeField( blank=True, @@ -180,15 +180,13 @@ class TestTypeEnum(enum.Enum): verbose_name="Return Date from the Last Country if Travelled", ) - allergies = models.TextField( - default="", blank=True, verbose_name="Patient's Known Allergies" - ) - present_health = models.TextField( default="", blank=True, verbose_name="Patient's Current Health Details" ) ongoing_medication = models.TextField( - default="", blank=True, verbose_name="Already pescribed medication if any" + default="", + blank=True, + verbose_name="Already pescribed medication if any", ) has_SARI = models.BooleanField( default=False, verbose_name="Does the Patient Suffer from SARI" @@ -202,14 +200,18 @@ class TestTypeEnum(enum.Enum): max_length=255, default="", verbose_name="Ward of Patient", blank=False ) - ward = models.ForeignKey(Ward, on_delete=models.SET_NULL, null=True, blank=True) + ward = models.ForeignKey( + Ward, on_delete=models.SET_NULL, null=True, blank=True + ) local_body = models.ForeignKey( LocalBody, on_delete=models.SET_NULL, null=True, blank=True ) district = models.ForeignKey( District, on_delete=models.SET_NULL, null=True, blank=True ) - state = models.ForeignKey(State, on_delete=models.SET_NULL, null=True, blank=True) + state = models.ForeignKey( + State, on_delete=models.SET_NULL, null=True, blank=True + ) is_migrant_worker = models.BooleanField( default=False, @@ -249,7 +251,10 @@ class TestTypeEnum(enum.Enum): ) created_by = models.ForeignKey( - User, on_delete=models.SET_NULL, null=True, related_name="patient_created_by" + User, + on_delete=models.SET_NULL, + null=True, + related_name="patient_created_by", ) is_active = models.BooleanField( default=True, @@ -260,10 +265,14 @@ class TestTypeEnum(enum.Enum): help_text="FKey to PatientSearch", null=True ) date_of_receipt_of_information = models.DateTimeField( - null=True, blank=True, verbose_name="Patient's information received date" + null=True, + blank=True, + verbose_name="Patient's information received date", ) - test_id = models.CharField(default="", max_length=100, null=True, blank=True) + test_id = models.CharField( + default="", max_length=100, null=True, blank=True + ) # Issue #600 Care_Fe date_of_test = models.DateTimeField( @@ -329,19 +338,29 @@ class TestTypeEnum(enum.Enum): blank=True, ) date_of_result = models.DateTimeField( - null=True, blank=True, default=None, verbose_name="Patient's result Date" + null=True, + blank=True, + default=None, + verbose_name="Patient's result Date", ) number_of_primary_contacts = models.IntegerField( - null=True, default=None, blank=True, verbose_name="Number of Primary Contacts" + null=True, + default=None, + blank=True, + verbose_name="Number of Primary Contacts", ) number_of_secondary_contacts = models.IntegerField( - null=True, default=None, blank=True, verbose_name="Number of Secondary Contacts" + null=True, + default=None, + blank=True, + verbose_name="Number of Secondary Contacts", ) # IDSP Requirements End # Vaccination Fields is_vaccinated = models.BooleanField( - default=False, verbose_name="Is the Patient Vaccinated Against COVID-19" + default=False, + verbose_name="Is the Patient Vaccinated Against COVID-19", ) number_of_doses = models.PositiveIntegerField( default=0, @@ -350,7 +369,11 @@ class TestTypeEnum(enum.Enum): validators=[MinValueValidator(0), MaxValueValidator(3)], ) vaccine_name = models.CharField( - choices=vaccineChoices, default=None, null=True, blank=False, max_length=15 + choices=vaccineChoices, + default=None, + null=True, + blank=False, + max_length=15, ) covin_id = models.CharField( @@ -391,12 +414,16 @@ class TestTypeEnum(enum.Enum): related_name="root_patient_assigned_to", ) - history = HistoricalRecords(excluded_fields=["patient_search_id", "meta_info"]) + history = HistoricalRecords( + excluded_fields=["patient_search_id", "meta_info"] + ) objects = BaseManager() def __str__(self): - return "{} - {} - {}".format(self.name, self.age, self.get_gender_display()) + return "{} - {} - {}".format( + self.name, self.age, self.get_gender_display() + ) @property def tele_consultation_history(self): @@ -570,9 +597,13 @@ def save(self, *args, **kwargs) -> None: "last_consultation__deprecated_covid_category": ( lambda x: REVERSE_COVID_CATEGORY_CHOICES.get(x, "-") ), - "last_consultation__category": lambda x: REVERSE_CATEGORY_CHOICES.get(x, "-"), + "last_consultation__category": lambda x: REVERSE_CATEGORY_CHOICES.get( + x, "-" + ), "last_consultation__suggestion": ( - lambda x: PatientConsultation.REVERSE_SUGGESTION_CHOICES.get(x, "-") + lambda x: PatientConsultation.REVERSE_SUGGESTION_CHOICES.get( + x, "-" + ) ), "last_consultation__admitted": pretty_boolean, "last_consultation__current_bed__bed__bed_type": ( @@ -591,7 +622,9 @@ class PatientSearch(PatientBaseModel): year_of_birth = models.IntegerField() state_id = models.IntegerField() - facility = models.ForeignKey("Facility", on_delete=models.SET_NULL, null=True) + facility = models.ForeignKey( + "Facility", on_delete=models.SET_NULL, null=True + ) patient_external_id = EncryptedCharField(max_length=100, default="") allow_transfer = models.BooleanField(default=True) @@ -599,7 +632,9 @@ class PatientSearch(PatientBaseModel): class Meta: indexes = [ - models.Index(fields=["year_of_birth", "date_of_birth", "phone_number"]), + models.Index( + fields=["year_of_birth", "date_of_birth", "phone_number"] + ), models.Index(fields=["year_of_birth", "phone_number"]), ] @@ -607,7 +642,8 @@ class Meta: def has_read_permission(request): if ( request.user.is_superuser - or request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"] + or request.user.user_type + >= User.TYPE_VALUE_MAP["DistrictLabAdmin"] ): return True elif ( @@ -670,10 +706,14 @@ class ModeOfContactEnum(enum.IntEnum): TRAVELLED_TOGETHER_WITHOUT_HIGH_EXPOSURE = 9 RelationChoices = [(item.value, item.name) for item in RelationEnum] - ModeOfContactChoices = [(item.value, item.name) for item in ModeOfContactEnum] + ModeOfContactChoices = [ + (item.value, item.name) for item in ModeOfContactEnum + ] patient = models.ForeignKey( - PatientRegistration, on_delete=models.PROTECT, related_name="contacted_patients" + PatientRegistration, + on_delete=models.PROTECT, + related_name="contacted_patients", ) patient_in_contact = models.ForeignKey( PatientRegistration, @@ -686,7 +726,9 @@ class ModeOfContactEnum(enum.IntEnum): date_of_first_contact = models.DateField(null=True) date_of_last_contact = models.DateField(null=True) - is_primary = models.BooleanField(help_text="If false, then secondary contact") + is_primary = models.BooleanField( + help_text="If false, then secondary contact" + ) condition_of_contact_is_symptomatic = models.BooleanField( help_text="While in contact, did the patient showing symptoms" ) @@ -698,7 +740,9 @@ class ModeOfContactEnum(enum.IntEnum): class Disease(models.Model): patient = models.ForeignKey( - PatientRegistration, on_delete=models.CASCADE, related_name="medical_history" + PatientRegistration, + on_delete=models.CASCADE, + related_name="medical_history", ) disease = models.IntegerField(choices=DISEASE_CHOICES) details = models.TextField(blank=True, null=True) @@ -709,7 +753,9 @@ class Disease(models.Model): class Meta: indexes = [ PartialIndex( - fields=["patient", "disease"], unique=True, where=PQ(deleted=False) + fields=["patient", "disease"], + unique=True, + where=PQ(deleted=False), ) ] @@ -720,7 +766,9 @@ def get_disease_display(self): return DISEASE_CHOICES[self.disease - 1][1] -class FacilityPatientStatsHistory(FacilityBaseModel, FacilityRelatedPermissionMixin): +class FacilityPatientStatsHistory( + FacilityBaseModel, FacilityRelatedPermissionMixin +): facility = models.ForeignKey("Facility", on_delete=models.PROTECT) entry_date = models.DateField() num_patients_visited = models.IntegerField(default=0) @@ -749,7 +797,9 @@ class Meta: class PatientMobileOTP(BaseModel): is_used = models.BooleanField(default=False) - phone_number = models.CharField(max_length=14, validators=[phone_number_regex]) + phone_number = models.CharField( + max_length=14, validators=[phone_number_regex] + ) otp = models.CharField(max_length=10) @@ -770,7 +820,9 @@ class PatientNotes(FacilityBaseModel, PatientRelatedPermissionMixin): class PatientHealthDetails(PatientBaseModel, PatientRelatedPermissionMixin): patient = models.ForeignKey( - PatientRegistration, on_delete=models.CASCADE, related_name="health_details" + PatientRegistration, + on_delete=models.CASCADE, + related_name="health_details", ) facility = models.ForeignKey( Facility, on_delete=models.CASCADE, related_name="health_details" From 52840a62043cd79d5c0ae9f014071f977f3e691d Mon Sep 17 00:00:00 2001 From: cp-Coder Date: Wed, 31 Aug 2022 15:56:07 +0530 Subject: [PATCH 14/44] Added migrations to merge conflicting migrations --- .../migrations/0313_merge_20220831_1553.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 care/facility/migrations/0313_merge_20220831_1553.py diff --git a/care/facility/migrations/0313_merge_20220831_1553.py b/care/facility/migrations/0313_merge_20220831_1553.py new file mode 100644 index 0000000000..5fa8360c30 --- /dev/null +++ b/care/facility/migrations/0313_merge_20220831_1553.py @@ -0,0 +1,14 @@ +# Generated by Django 2.2.11 on 2022-08-31 10:23 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('facility', '0312_patientconsultation_investigation'), + ('facility', '0311_auto_20220831_0900'), + ] + + operations = [ + ] From 9bdb1cf40f4177bd0e194c5c1d8fadec1191e81a Mon Sep 17 00:00:00 2001 From: cp-Coder Date: Thu, 1 Sep 2022 08:34:16 +0530 Subject: [PATCH 15/44] Added data migration for created_in_consultation field in patient health details model --- care/facility/migrations/0311_auto_20220831_0900.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/care/facility/migrations/0311_auto_20220831_0900.py b/care/facility/migrations/0311_auto_20220831_0900.py index c66c0354a8..5a0a0b77a4 100644 --- a/care/facility/migrations/0311_auto_20220831_0900.py +++ b/care/facility/migrations/0311_auto_20220831_0900.py @@ -2,12 +2,12 @@ from django.db import migrations, models import django.db.models.deletion -from care.facility.models.patient import PatientHealthDetails +from care.facility.models.patient import PatientHealthDetails, PatientConsultation def populate_data(apps, schema_editor): - PatientConsultation = apps.get_model("facility", "PatientConsultation") - patients_cons = PatientConsultation.objects.all() + PatientConsultationModel = apps.get_model("facility", "PatientConsultation") + patients_cons = PatientConsultationModel.objects.all() for patient_cons in patients_cons: try: @@ -20,6 +20,10 @@ def populate_data(apps, schema_editor): if patient_cons.height: health_details.height = patient_cons.height + health_details.created_in_consultation = PatientConsultation.objects.get( + id=patient_cons.id + ) + health_details.save() except PatientHealthDetails.DoesNotExist: pass From c177d5f23da01376d8a1e8543fbf5effa3acd507 Mon Sep 17 00:00:00 2001 From: cp-Coder Date: Thu, 1 Sep 2022 08:35:31 +0530 Subject: [PATCH 16/44] Updated the logic for creation of patient health details instance --- .../api/serializers/patient_consultation.py | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/care/facility/api/serializers/patient_consultation.py b/care/facility/api/serializers/patient_consultation.py index 007b605dcc..c1964ba3aa 100644 --- a/care/facility/api/serializers/patient_consultation.py +++ b/care/facility/api/serializers/patient_consultation.py @@ -245,20 +245,28 @@ def create(self, validated_data): consultation.last_edited_by = self.context["request"].user consultation.save() - # Check if health details has been created try: - health_details = PatientHealthDetails.objects.get( - id=self.validated_data["patient"].id + health_details = PatientHealthDetails( + patient=consultation.patient, + facility=consultation.facility, + family_details=self.context["request"].data["family_details"], + has_allergy=self.context["request"].data["has_allergy"], + allergies=self.context["request"].data["allergies"], + blood_group=self.context["request"].data["blood_group"], + weight=self.context["request"].data["weight"], + height=self.context["request"].data["height"], + created_in_consultation=consultation, ) - health_details.created_in_consultation = consultation health_details.save() consultation.last_health_details = health_details consultation.save(update_fields=["last_health_details"]) - except PatientHealthDetails.DoesNotExist: + except KeyError as error: raise ValidationError( { "last_health_details": [ - "Patient heatlh details has not been registered" + "Please provide the {} detail regarding patient health".format( + error + ) ] } ) From 8bd2609a90fb63f285d91ece4f81a5de11e34300 Mon Sep 17 00:00:00 2001 From: cp-Coder Date: Sat, 3 Sep 2022 11:14:24 +0530 Subject: [PATCH 17/44] Updated the logic of creating new patient health record --- .../api/serializers/patient_consultation.py | 32 ++++++++----------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/care/facility/api/serializers/patient_consultation.py b/care/facility/api/serializers/patient_consultation.py index c1964ba3aa..7f05f21df6 100644 --- a/care/facility/api/serializers/patient_consultation.py +++ b/care/facility/api/serializers/patient_consultation.py @@ -247,29 +247,23 @@ def create(self, validated_data): try: health_details = PatientHealthDetails( - patient=consultation.patient, - facility=consultation.facility, - family_details=self.context["request"].data["family_details"], - has_allergy=self.context["request"].data["has_allergy"], - allergies=self.context["request"].data["allergies"], - blood_group=self.context["request"].data["blood_group"], - weight=self.context["request"].data["weight"], - height=self.context["request"].data["height"], - created_in_consultation=consultation, + **self.context["request"].data["new_health_details"] ) + health_details.created_in_consultation = consultation + health_details.facility = consultation.facility + health_details.patient = consultation.patient health_details.save() consultation.last_health_details = health_details consultation.save(update_fields=["last_health_details"]) - except KeyError as error: - raise ValidationError( - { - "last_health_details": [ - "Please provide the {} detail regarding patient health".format( - error - ) - ] - } - ) + except KeyError: + if consultation.patient.last_consultation is None: + raise ValidationError( + { + "last_health_details": [ + "Please provide the health details of the patient" + ] + } + ) if bed: consultation_bed = ConsultationBed( From 44fb81dfe5c755e8230f005e9d81ec40386593af Mon Sep 17 00:00:00 2001 From: cp-Coder Date: Sat, 3 Sep 2022 11:15:40 +0530 Subject: [PATCH 18/44] Added the logic of updating the patient health record --- care/facility/api/serializers/patient_consultation.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/care/facility/api/serializers/patient_consultation.py b/care/facility/api/serializers/patient_consultation.py index 7f05f21df6..9b2375e920 100644 --- a/care/facility/api/serializers/patient_consultation.py +++ b/care/facility/api/serializers/patient_consultation.py @@ -171,6 +171,17 @@ def update(self, instance, validated_data): consultation = super().update(instance, validated_data) + try: + PatientHealthDetails.objects.filter( + id=consultation.last_health_details.id + ).update(**self.context["request"].data["new_health_details"]) + consultation.last_health_details = PatientHealthDetails.objects.get( + id=consultation.last_health_details.id + ) + consultation.save(update_fields=["last_health_details"]) + except KeyError: + pass + if "assigned_to" in validated_data: if validated_data["assigned_to"] != _temp and validated_data["assigned_to"]: NotificationGenerator( From 63c02b95b403bf66ca5060b073262c95f13495fe Mon Sep 17 00:00:00 2001 From: cp-Coder Date: Tue, 13 Sep 2022 13:59:24 +0530 Subject: [PATCH 19/44] Fixed blood group field for csv --- care/facility/models/patient.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/care/facility/models/patient.py b/care/facility/models/patient.py index a205b12caa..b9ff2df873 100644 --- a/care/facility/models/patient.py +++ b/care/facility/models/patient.py @@ -538,7 +538,7 @@ def save(self, *args, **kwargs) -> None: # remarks "number_of_aged_dependents": "Number of people aged above 60 living with the patient", "number_of_chronic_diseased_dependents": "Number of people who have chronic diseases living with the patient", - "blood_group": "Blood Group", + "last_consultation__last_health_details__blood_group": "Blood Group", "is_medical_worker": "Is the Patient a Medical Worker", "contact_with_confirmed_carrier": "Confirmed Contact with a Covid19 Carrier", "contact_with_suspected_carrier": "Suspected Contact with a Covid19 Carrier", From 4203a8b82ba1c0544474d876b54059ea2559fd4a Mon Sep 17 00:00:00 2001 From: cp-Coder Date: Tue, 13 Sep 2022 14:02:01 +0530 Subject: [PATCH 20/44] Moved VACCINE_CHOICES to patient_base file --- care/facility/models/patient.py | 11 ----------- care/facility/models/patient_base.py | 17 ++++++++++++++++- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/care/facility/models/patient.py b/care/facility/models/patient.py index b9ff2df873..e81b111d0c 100644 --- a/care/facility/models/patient.py +++ b/care/facility/models/patient.py @@ -64,17 +64,6 @@ class SourceEnum(enum.Enum): SourceChoices = [(e.value, e.name) for e in SourceEnum] - class vaccineEnum(enum.Enum): - COVISHIELD = "CoviShield" - COVAXIN = "Covaxin" - SPUTNIK = "Sputnik" - MODERNA = "Moderna" - PFIZER = "Pfizer" - JANSSEN = "Janssen" - SINOVAC = "Sinovac" - - vaccineChoices = [(e.value, e.name) for e in vaccineEnum] - class ActionEnum(enum.Enum): PENDING = 10 SPECIALIST_REQUIRED = 30 diff --git a/care/facility/models/patient_base.py b/care/facility/models/patient_base.py index d26a83a961..60fda78a2c 100644 --- a/care/facility/models/patient_base.py +++ b/care/facility/models/patient_base.py @@ -113,7 +113,22 @@ class BedType(enum.Enum): REVERSE_BLOOD_GROUP_CHOICES = reverse_choices(BLOOD_GROUP_CHOICES) REVERSE_DISEASE_STATUS_CHOICES = reverse_choices(DISEASE_STATUS_CHOICES) -REVERSE_COVID_CATEGORY_CHOICES = reverse_choices(COVID_CATEGORY_CHOICES) # Deprecated +REVERSE_COVID_CATEGORY_CHOICES = reverse_choices( + COVID_CATEGORY_CHOICES +) # Deprecated REVERSE_CATEGORY_CHOICES = reverse_choices(CATEGORY_CHOICES) # REVERSE_ADMIT_CHOICES = reverse_choices(ADMIT_CHOICES) REVERSE_BED_TYPE_CHOICES = reverse_choices(BedTypeChoices) + + +class VaccineEnum(enum.Enum): + COVISHIELD = "CoviShield" + COVAXIN = "Covaxin" + SPUTNIK = "Sputnik" + MODERNA = "Moderna" + PFIZER = "Pfizer" + JANSSEN = "Janssen" + SINOVAC = "Sinovac" + + +VACCINE_CHOICES = [(e.value, e.name) for e in VaccineEnum] From 02f58984f7314c1faef1552e7ed79c5d16adfa76 Mon Sep 17 00:00:00 2001 From: cp-Coder Date: Tue, 13 Sep 2022 14:03:30 +0530 Subject: [PATCH 21/44] Added Vaccine model --- care/facility/models/patient.py | 59 ++++++++++++++++++++++----------- 1 file changed, 40 insertions(+), 19 deletions(-) diff --git a/care/facility/models/patient.py b/care/facility/models/patient.py index e81b111d0c..3c582fee9a 100644 --- a/care/facility/models/patient.py +++ b/care/facility/models/patient.py @@ -34,6 +34,7 @@ REVERSE_CATEGORY_CHOICES, REVERSE_COVID_CATEGORY_CHOICES, REVERSE_DISEASE_STATUS_CHOICES, + VACCINE_CHOICES, ) from care.facility.models.patient_consultation import PatientConsultation from care.users.models import ( @@ -351,19 +352,6 @@ class TestTypeEnum(enum.Enum): default=False, verbose_name="Is the Patient Vaccinated Against COVID-19", ) - number_of_doses = models.PositiveIntegerField( - default=0, - null=False, - blank=False, - validators=[MinValueValidator(0), MaxValueValidator(3)], - ) - vaccine_name = models.CharField( - choices=vaccineChoices, - default=None, - null=True, - blank=False, - max_length=15, - ) covin_id = models.CharField( max_length=15, @@ -372,9 +360,6 @@ class TestTypeEnum(enum.Enum): blank=True, verbose_name="COVID-19 Vaccination ID", ) - last_vaccinated_date = models.DateTimeField( - null=True, blank=True, verbose_name="Date Last Vaccinated" - ) # Extras cluster_name = models.CharField( @@ -558,9 +543,6 @@ def save(self, *args, **kwargs) -> None: "date_declared_positive": "Date Patient is Declared Positive", # Vaccination Data "is_vaccinated": "Is Patient Vaccinated", - "number_of_doses": "Number of Vaccine Doses Recieved", - "vaccine_name": "Vaccine Name", - "last_vaccinated_date": "Last Vaccinated Date", # Consultation Data "last_consultation__admission_date": "Date of Admission", "last_consultation__symptoms_onset_date": "Date of Onset of Symptoms", @@ -853,3 +835,42 @@ class PatientHealthDetails(PatientBaseModel, PatientRelatedPermissionMixin): verbose_name="Patient's Weight in KG", validators=[MinValueValidator(0)], ) + + +class Vaccine(models.Model): + health_details = models.ForeignKey( + PatientHealthDetails, + on_delete=models.CASCADE, + related_name="vaccination_history", + ) + vaccine = models.CharField( + choices=VACCINE_CHOICES, + default=None, + null=True, + blank=False, + max_length=20, + ) + doses = models.PositiveIntegerField( + default=0, + null=False, + blank=False, + validators=[MinValueValidator(0), MaxValueValidator(3)], + ) + last_vaccinated_date = models.DateField( + null=True, blank=True, verbose_name="Date Last Vaccinated" + ) + + deleted = models.BooleanField(default=False) + objects = BaseManager() + + class Meta: + indexes = [ + PartialIndex( + fields=["health_details", "vaccine"], + unique=True, + where=PQ(deleted=False), + ) + ] + + def __str__(self): + return self.patient.name + " - " + self.vaccine From 9737a0189a29aec44462b4cd6889562d41166fe4 Mon Sep 17 00:00:00 2001 From: cp-Coder Date: Tue, 13 Sep 2022 14:05:10 +0530 Subject: [PATCH 22/44] Updated blood_group field for csv and reformatted the code --- care/facility/models/patient.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/care/facility/models/patient.py b/care/facility/models/patient.py index 3c582fee9a..8747eac166 100644 --- a/care/facility/models/patient.py +++ b/care/facility/models/patient.py @@ -556,7 +556,9 @@ def save(self, *args, **kwargs) -> None: CSV_MAKE_PRETTY = { "gender": (lambda x: REVERSE_GENDER_CHOICES[x]), - "blood_group": (lambda x: REVERSE_BLOOD_GROUP_CHOICES[x]), + "last_consultation__last_health_details__blood_group": ( + lambda x: REVERSE_BLOOD_GROUP_CHOICES[x] + ), "disease_status": (lambda x: REVERSE_DISEASE_STATUS_CHOICES[x]), "is_medical_worker": pretty_boolean, "will_donate_blood": pretty_boolean, From d987f5f6c7199c3b93e3ebc63e414fdf3f0999a8 Mon Sep 17 00:00:00 2001 From: cp-Coder Date: Tue, 13 Sep 2022 14:06:59 +0530 Subject: [PATCH 23/44] Added migrations for merging previous migrations --- .../migrations/0315_merge_20220912_1001.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 care/facility/migrations/0315_merge_20220912_1001.py diff --git a/care/facility/migrations/0315_merge_20220912_1001.py b/care/facility/migrations/0315_merge_20220912_1001.py new file mode 100644 index 0000000000..f5cb0dbb24 --- /dev/null +++ b/care/facility/migrations/0315_merge_20220912_1001.py @@ -0,0 +1,14 @@ +# Generated by Django 2.2.11 on 2022-09-12 04:31 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('facility', '0314_patientconsultation_icd11_diagnoses'), + ('facility', '0313_merge_20220831_1553'), + ] + + operations = [ + ] From 2bb7d2825a16478fac2c18b73ff0813bf6f9e9a0 Mon Sep 17 00:00:00 2001 From: cp-Coder Date: Tue, 13 Sep 2022 14:07:39 +0530 Subject: [PATCH 24/44] * Added migrations for Vaccine model * Added function to migrate previous data to new Vaccine model --- .../migrations/0316_auto_20220912_2244.py | 125 ++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 care/facility/migrations/0316_auto_20220912_2244.py diff --git a/care/facility/migrations/0316_auto_20220912_2244.py b/care/facility/migrations/0316_auto_20220912_2244.py new file mode 100644 index 0000000000..b5e2565c28 --- /dev/null +++ b/care/facility/migrations/0316_auto_20220912_2244.py @@ -0,0 +1,125 @@ +# Generated by Django 2.2.11 on 2022-09-12 17:14 + +import django.core.validators +from django.db import migrations, models +import django.db.models.deletion +import partial_index + + +def create_vaccination_data(apps, schema_editor): + PatientHealthDetails = apps.get_model("facility", "PatientHealthDetails") + Vaccine = apps.get_model("facility", "Vaccine") + health_details_objs = PatientHealthDetails.objects.all() + vaccine_objs = [] + + for health_detail in health_details_objs: + vaccine_objs.append( + Vaccine( + health_details=health_detail, + vaccine=health_detail.patient.vaccine_name, + doses=health_detail.patient.number_of_doses, + last_vaccinated_date=health_detail.patient.last_vaccinated_date, + ) + ) + + Vaccine.objects.bulk_create(vaccine_objs) + + +class Migration(migrations.Migration): + + dependencies = [ + ("facility", "0315_merge_20220912_1001"), + ] + + operations = [ + migrations.CreateModel( + name="Vaccine", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "vaccine", + models.CharField( + choices=[ + ("CoviShield", "COVISHIELD"), + ("Covaxin", "COVAXIN"), + ("Sputnik", "SPUTNIK"), + ("Moderna", "MODERNA"), + ("Pfizer", "PFIZER"), + ("Janssen", "JANSSEN"), + ("Sinovac", "SINOVAC"), + ], + default=None, + max_length=20, + null=True, + ), + ), + ( + "doses", + models.PositiveIntegerField( + default=0, + validators=[ + django.core.validators.MinValueValidator(0), + django.core.validators.MaxValueValidator(3), + ], + ), + ), + ( + "last_vaccinated_date", + models.DateField( + blank=True, null=True, verbose_name="Date Last Vaccinated" + ), + ), + ("deleted", models.BooleanField(default=False)), + ( + "health_details", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="vaccination_history", + to="facility.PatientHealthDetails", + ), + ), + ], + ), + migrations.AddIndex( + model_name="vaccine", + index=partial_index.PartialIndex( + fields=["health_details", "vaccine"], + name="facility_va_health__0b97b3_partial", + unique=True, + where=partial_index.PQ(deleted=False), + ), + ), + migrations.RunPython(create_vaccination_data, migrations.RunPython.noop), + migrations.RemoveField( + model_name="historicalpatientregistration", + name="last_vaccinated_date", + ), + migrations.RemoveField( + model_name="historicalpatientregistration", + name="number_of_doses", + ), + migrations.RemoveField( + model_name="historicalpatientregistration", + name="vaccine_name", + ), + migrations.RemoveField( + model_name="patientregistration", + name="last_vaccinated_date", + ), + migrations.RemoveField( + model_name="patientregistration", + name="number_of_doses", + ), + migrations.RemoveField( + model_name="patientregistration", + name="vaccine_name", + ), + ] From 2857e47161734e4fb68365812d51c6a9f37cc7e5 Mon Sep 17 00:00:00 2001 From: cp-Coder Date: Tue, 13 Sep 2022 15:42:18 +0530 Subject: [PATCH 25/44] * Added serializer for Vaccine model * Added serializer field for vaccination in health details serializer * Reformatted the code --- .../api/serializers/patient_health_details.py | 38 ++++++++++++++++--- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/care/facility/api/serializers/patient_health_details.py b/care/facility/api/serializers/patient_health_details.py index e4f07556ec..993f0378d7 100644 --- a/care/facility/api/serializers/patient_health_details.py +++ b/care/facility/api/serializers/patient_health_details.py @@ -1,27 +1,53 @@ from django.forms import ChoiceField -from care.facility.models.facility import Facility -from care.facility.models.patient_base import BLOOD_GROUP_CHOICES from rest_framework import serializers + +from care.facility.api.serializers.facility import FacilityBasicInfoSerializer from care.facility.models import ( + PatientConsultation, PatientHealthDetails, PatientRegistration, - PatientConsultation, ) -from care.facility.api.serializers.facility import FacilityBasicInfoSerializer +from care.facility.models.facility import Facility +from care.facility.models.patient import Vaccine +from care.facility.models.patient_base import ( + BLOOD_GROUP_CHOICES, + VACCINE_CHOICES, +) from care.utils.serializer.external_id_field import ExternalIdSerializerField class PatientHealthDetailsSerializer(serializers.ModelSerializer): + class VaccinationHistorySerializer(serializers.Serializer): + vaccine = serializers.ChoiceField(choices=VACCINE_CHOICES) + doses = serializers.IntegerField(required=False, default=0) + last_vaccinated_date = serializers.DateField() + + class Meta: + model = Vaccine + fields = ( + "vaccine", + "doses", + "last_vaccinated_date", + ) + id = serializers.CharField(source="external_id", read_only=True) - patient = ExternalIdSerializerField(queryset=PatientRegistration.objects.all()) + patient = ExternalIdSerializerField( + queryset=PatientRegistration.objects.all() + ) facility = ExternalIdSerializerField(queryset=Facility.objects.all()) - facility_object = FacilityBasicInfoSerializer(source="facility", read_only=True) + facility_object = FacilityBasicInfoSerializer( + source="facility", read_only=True + ) created_in_consultation = ExternalIdSerializerField( queryset=PatientConsultation.objects.all(), required=False ) blood_group = ChoiceField(choices=BLOOD_GROUP_CHOICES, required=True) + vaccination_history = serializers.ListSerializer( + child=VaccinationHistorySerializer(), required=False + ) + class Meta: model = PatientHealthDetails exclude = ("deleted", "external_id") From 56788da1e9830e1d926ae0fe9e124df6748e78d0 Mon Sep 17 00:00:00 2001 From: cp-Coder Date: Tue, 13 Sep 2022 15:49:37 +0530 Subject: [PATCH 26/44] Removed vaccine name from patient serializer --- care/facility/api/serializers/patient.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/care/facility/api/serializers/patient.py b/care/facility/api/serializers/patient.py index 1e6a1b3962..a89be7a950 100644 --- a/care/facility/api/serializers/patient.py +++ b/care/facility/api/serializers/patient.py @@ -25,9 +25,7 @@ PatientSearch, ) from care.facility.models.notification import Notification -from care.facility.models.patient import PatientHealthDetails from care.facility.models.patient_base import ( - BLOOD_GROUP_CHOICES, DISEASE_STATUS_CHOICES, DiseaseStatusEnum, ) @@ -168,9 +166,6 @@ class Meta: last_edited = UserBaseMinimumSerializer(read_only=True) created_by = UserBaseMinimumSerializer(read_only=True) - vaccine_name = serializers.ChoiceField( - choices=PatientRegistration.vaccineChoices, required=False, allow_null=True - ) assigned_to_object = UserBaseMinimumSerializer(source="assigned_to", read_only=True) From 69edd0b1baaf956360724905d65d621e7c2a9223 Mon Sep 17 00:00:00 2001 From: cp-Coder Date: Tue, 13 Sep 2022 15:54:45 +0530 Subject: [PATCH 27/44] Removed vaccine related field from patient viewset --- care/facility/api/viewsets/patient.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/care/facility/api/viewsets/patient.py b/care/facility/api/viewsets/patient.py index ee8c845b14..99b7c20f67 100644 --- a/care/facility/api/viewsets/patient.py +++ b/care/facility/api/viewsets/patient.py @@ -105,9 +105,7 @@ class PatientFilterSet(filters.FilterSet): field_name="date_declared_positive" ) date_of_result = filters.DateFromToRangeFilter(field_name="date_of_result") - last_vaccinated_date = filters.DateFromToRangeFilter( - field_name="last_vaccinated_date" - ) + is_antenatal = filters.BooleanFilter(field_name="is_antenatal") is_active = filters.BooleanFilter(field_name="is_active") # Location Based Filtering @@ -151,7 +149,6 @@ class PatientFilterSet(filters.FilterSet): # Vaccination Filters covin_id = filters.CharFilter(field_name="covin_id") is_vaccinated = filters.BooleanFilter(field_name="is_vaccinated") - number_of_doses = filters.NumberFilter(field_name="number_of_doses") # Permission Filters assigned_to = filters.NumberFilter(field_name="assigned_to") # Other Filters @@ -259,7 +256,6 @@ class PatientViewSet( "modified_date", "date_declared_positive", "date_of_result", - "last_vaccinated_date", "last_consultation_admission_date", "last_consultation_discharge_date", "last_consultation_symptoms_onset_date", From fc4d332caf81262815a3fd9af4f6ea7dbfa0089e Mon Sep 17 00:00:00 2001 From: cp-Coder Date: Tue, 13 Sep 2022 15:55:38 +0530 Subject: [PATCH 28/44] Added logic for creating/updating vaccine records --- .../api/serializers/patient_consultation.py | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/care/facility/api/serializers/patient_consultation.py b/care/facility/api/serializers/patient_consultation.py index 9b2375e920..186ea15bcb 100644 --- a/care/facility/api/serializers/patient_consultation.py +++ b/care/facility/api/serializers/patient_consultation.py @@ -5,7 +5,7 @@ PatientHealthDetailsSerializer, ) -from care.facility.models.patient import PatientHealthDetails +from care.facility.models.patient import PatientHealthDetails, Vaccine from rest_framework import serializers from rest_framework.exceptions import ValidationError @@ -172,6 +172,8 @@ def update(self, instance, validated_data): consultation = super().update(instance, validated_data) try: + health_details_data = self.context["request"].data["new_health_details"] + vaccination_history = health_details_data.pop("vaccination_history", []) PatientHealthDetails.objects.filter( id=consultation.last_health_details.id ).update(**self.context["request"].data["new_health_details"]) @@ -179,6 +181,13 @@ def update(self, instance, validated_data): id=consultation.last_health_details.id ) consultation.save(update_fields=["last_health_details"]) + vaccines = [] + for vaccine in vaccination_history: + vaccines.append( + Vaccine(health_details=consultation.last_health_details, **vaccine) + ) + if vaccines: + Vaccine.objects.bulk_create(vaccines, ignore_conflicts=True) except KeyError: pass @@ -256,14 +265,23 @@ def create(self, validated_data): consultation.last_edited_by = self.context["request"].user consultation.save() + # Patient Health Details try: + health_details_data = self.context["request"].data["new_health_details"] + vaccination_history = health_details_data.pop("vaccination_history", []) + health_details = PatientHealthDetails( - **self.context["request"].data["new_health_details"] + patient=consultation.patient, + facility=consultation.facility, + created_in_consultation=consultation, + **health_details_data, ) - health_details.created_in_consultation = consultation - health_details.facility = consultation.facility - health_details.patient = consultation.patient health_details.save() + vaccines = [] + for vaccine in vaccination_history: + vaccines.append(Vaccine(health_details=health_details, **vaccine)) + if vaccines: + Vaccine.objects.bulk_create(vaccines, ignore_conflicts=True) consultation.last_health_details = health_details consultation.save(update_fields=["last_health_details"]) except KeyError: From e8dcdadc4a42e37f6e6a656b05566be665531af8 Mon Sep 17 00:00:00 2001 From: cp-Coder Date: Tue, 13 Sep 2022 15:56:16 +0530 Subject: [PATCH 29/44] Reformatted the code --- care/facility/api/viewsets/patient.py | 98 ++++++++++++++++++++------- 1 file changed, 75 insertions(+), 23 deletions(-) diff --git a/care/facility/api/viewsets/patient.py b/care/facility/api/viewsets/patient.py index 99b7c20f67..b801e2134b 100644 --- a/care/facility/api/viewsets/patient.py +++ b/care/facility/api/viewsets/patient.py @@ -9,7 +9,10 @@ from django.utils.timezone import localtime, now from django_filters import rest_framework as filters from djqscsv import render_to_csv_response -from dry_rest_permissions.generics import DRYPermissionFiltersBase, DRYPermissions +from dry_rest_permissions.generics import ( + DRYPermissionFiltersBase, + DRYPermissions, +) from rest_framework import filters as rest_framework_filters from rest_framework import mixins, serializers, status, viewsets from rest_framework.decorators import action @@ -57,7 +60,9 @@ from care.facility.models.base import covert_choice_dict from care.facility.models.bed import AssetBed, ConsultationBed from care.facility.models.patient_base import DISEASE_STATUS_DICT -from care.facility.tasks.patient.discharge_report import generate_discharge_report +from care.facility.tasks.patient.discharge_report import ( + generate_discharge_report, +) from care.users.models import User from care.utils.cache.cache_allowed_facilities import get_accessible_facilities from care.utils.filters import CareChoiceFilter, MultiSelectFilter @@ -77,10 +82,13 @@ class PatientFilterSet(filters.FilterSet): disease_status = CareChoiceFilter(choice_dict=DISEASE_STATUS_DICT) facility = filters.UUIDFilter(field_name="facility__external_id") facility_type = CareChoiceFilter( - field_name="facility__facility_type", choice_dict=REVERSE_FACILITY_TYPES + field_name="facility__facility_type", + choice_dict=REVERSE_FACILITY_TYPES, ) phone_number = filters.CharFilter(field_name="phone_number") - emergency_phone_number = filters.CharFilter(field_name="emergency_phone_number") + emergency_phone_number = filters.CharFilter( + field_name="emergency_phone_number" + ) allow_transfer = filters.BooleanFilter(field_name="allow_transfer") name = filters.CharFilter(field_name="name", lookup_expr="icontains") ip_no = filters.CharFilter( @@ -100,7 +108,9 @@ class PatientFilterSet(filters.FilterSet): created_date = filters.DateFromToRangeFilter(field_name="created_date") modified_date = filters.DateFromToRangeFilter(field_name="modified_date") srf_id = filters.CharFilter(field_name="srf_id") - is_declared_positive = filters.BooleanFilter(field_name="is_declared_positive") + is_declared_positive = filters.BooleanFilter( + field_name="is_declared_positive" + ) date_declared_positive = filters.DateFromToRangeFilter( field_name="date_declared_positive" ) @@ -118,7 +128,9 @@ class PatientFilterSet(filters.FilterSet): field_name="local_body__name", lookup_expr="icontains" ) state = filters.NumberFilter(field_name="state__id") - state_name = filters.CharFilter(field_name="state__name", lookup_expr="icontains") + state_name = filters.CharFilter( + field_name="state__name", lookup_expr="icontains" + ) # Consultation Fields is_kasp = filters.BooleanFilter(field_name="last_consultation__is_kasp") last_consultation_kasp_enabled_date = filters.DateFromToRangeFilter( @@ -152,7 +164,9 @@ class PatientFilterSet(filters.FilterSet): # Permission Filters assigned_to = filters.NumberFilter(field_name="assigned_to") # Other Filters - has_bed = filters.BooleanFilter(field_name="has_bed", method="filter_bed_not_null") + has_bed = filters.BooleanFilter( + field_name="has_bed", method="filter_bed_not_null" + ) def filter_bed_not_null(self, queryset, name, value): return queryset.filter( @@ -169,14 +183,21 @@ def filter_queryset(self, request, queryset, view): return queryset.filter( last_consultation__last_daily_round__bed_id__in=AssetBed.objects.filter( asset=request.user.asset - ).values("id"), + ).values( + "id" + ), last_consultation__last_daily_round__bed__isnull=False, ) if not request.user.is_superuser: if request.user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]: queryset = queryset.filter(facility__state=request.user.state) - elif request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: - queryset = queryset.filter(facility__district=request.user.district) + elif ( + request.user.user_type + >= User.TYPE_VALUE_MAP["DistrictLabAdmin"] + ): + queryset = queryset.filter( + facility__district=request.user.district + ) elif view.action != "transfer": allowed_facilities = get_accessible_facilities(request.user) q_filters = Q(facility__id__in=allowed_facilities) @@ -354,7 +375,9 @@ def discharge_patient(self, request, *args, **kwargs): patient.allow_transfer = not discharged patient.save(update_fields=["allow_transfer", "is_active"]) last_consultation = ( - PatientConsultation.objects.filter(patient=patient).order_by("-id").first() + PatientConsultation.objects.filter(patient=patient) + .order_by("-id") + .first() ) current_time = localtime(now()) if last_consultation: @@ -390,12 +413,16 @@ def discharge_summary(self, request, *args, **kwargs): @action(detail=True, methods=["POST"]) def transfer(self, request, *args, **kwargs): patient = PatientRegistration.objects.get( - id=PatientSearch.objects.get(external_id=kwargs["external_id"]).patient_id + id=PatientSearch.objects.get( + external_id=kwargs["external_id"] + ).patient_id ) if patient.allow_transfer is False: return Response( - {"Patient": "Cannot Transfer Patient , Source Facility Does Not Allow"}, + { + "Patient": "Cannot Transfer Patient , Source Facility Does Not Allow" + }, status=status.HTTP_406_NOT_ACCEPTABLE, ) patient.is_active = True @@ -405,7 +432,9 @@ def transfer(self, request, *args, **kwargs): serializer.save() patient = PatientRegistration.objects.get( - id=PatientSearch.objects.get(external_id=kwargs["external_id"]).patient_id + id=PatientSearch.objects.get( + external_id=kwargs["external_id"] + ).patient_id ) response_serializer = self.get_serializer_class()(patient) # Update all Active Shifting Request to Rejected @@ -419,7 +448,9 @@ def transfer(self, request, *args, **kwargs): + f"\n The shifting request was auto rejected by the system as the patient was moved to {patient.facility.name}" ) shifting_request.save(update_fields=["status", "comments"]) - return Response(data=response_serializer.data, status=status.HTTP_200_OK) + return Response( + data=response_serializer.data, status=status.HTTP_200_OK + ) class FacilityPatientStatsHistoryFilterSet(filters.FilterSet): @@ -447,9 +478,14 @@ def get_queryset(self): ) if user.is_superuser: return queryset - elif self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: + elif ( + self.request.user.user_type + >= User.TYPE_VALUE_MAP["DistrictLabAdmin"] + ): return queryset.filter(facility__district=user.district) - elif self.request.user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]: + elif ( + self.request.user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"] + ): return queryset.filter(facility__state=user.state) return queryset.filter(facility__users__id__exact=user.id) @@ -502,7 +538,10 @@ def get_queryset(self): data=self.request.query_params, partial=True ) serializer.is_valid(raise_exception=True) - if self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: + if ( + self.request.user.user_type + >= User.TYPE_VALUE_MAP["DistrictLabAdmin"] + ): search_keys = [ "date_of_birth", "year_of_birth", @@ -511,7 +550,12 @@ def get_queryset(self): "age", ] else: - search_keys = ["date_of_birth", "year_of_birth", "phone_number", "age"] + search_keys = [ + "date_of_birth", + "year_of_birth", + "phone_number", + "age", + ] search_fields = { key: serializer.validated_data[key] for key in search_keys @@ -541,7 +585,9 @@ def get_queryset(self): if name: queryset = ( - queryset.annotate(similarity=TrigramSimilarity("name", name)) + queryset.annotate( + similarity=TrigramSimilarity("name", name) + ) .filter(similarity__gt=0.2) .order_by("-similarity") ) @@ -592,7 +638,9 @@ def get_queryset(self): if user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]: queryset = queryset.filter(patient__facility__state=user.state) elif user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: - queryset = queryset.filter(patient__facility__district=user.district) + queryset = queryset.filter( + patient__facility__district=user.district + ) else: allowed_facilities = get_accessible_facilities(user) q_filters = Q(patient__facility__id__in=allowed_facilities) @@ -612,7 +660,9 @@ def perform_create(self, serializer): {"patient": "Only active patients data can be updated"} ) return serializer.save( - facility=patient.facility, patient=patient, created_by=self.request.user + facility=patient.facility, + patient=patient, + created_by=self.request.user, ) @@ -623,7 +673,9 @@ class PatientHealthDetailsViewSet( UpdateModelMixin, GenericViewSet, ): - queryset = PatientHealthDetails.objects.all().select_related("facility", "patient") + queryset = PatientHealthDetails.objects.all().select_related( + "facility", "patient" + ) serializer_class = PatientHealthDetailsSerializer permission_classes = (IsAuthenticated, DRYPermissions) lookup_field = "external_id" From 521dfb815745f76b003df8112e6140a37bd55f77 Mon Sep 17 00:00:00 2001 From: cp-Coder Date: Tue, 13 Sep 2022 20:32:54 +0530 Subject: [PATCH 30/44] * Added new notification events for health details * Added migrations for adding new notification events * Added new fields in notification generator for health details * Added notification generator in consultation serializers for health details --- .../api/serializers/patient_consultation.py | 14 ++++++- .../migrations/0317_auto_20220913_2021.py | 39 +++++++++++++++++++ care/facility/models/notification.py | 2 + care/utils/notification_handler.py | 26 ++++++++++++- 4 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 care/facility/migrations/0317_auto_20220913_2021.py diff --git a/care/facility/api/serializers/patient_consultation.py b/care/facility/api/serializers/patient_consultation.py index 186ea15bcb..0edecac708 100644 --- a/care/facility/api/serializers/patient_consultation.py +++ b/care/facility/api/serializers/patient_consultation.py @@ -180,7 +180,6 @@ def update(self, instance, validated_data): consultation.last_health_details = PatientHealthDetails.objects.get( id=consultation.last_health_details.id ) - consultation.save(update_fields=["last_health_details"]) vaccines = [] for vaccine in vaccination_history: vaccines.append( @@ -188,6 +187,13 @@ def update(self, instance, validated_data): ) if vaccines: Vaccine.objects.bulk_create(vaccines, ignore_conflicts=True) + consultation.save(update_fields=["last_health_details"]) + NotificationGenerator( + event=Notification.Event.PATIENT_HEALTH_DETAILS_UPDATED, + caused_by=self.context["request"].user, + caused_object=consultation.last_health_details, + facility=consultation.patient.facility, + ).generate() except KeyError: pass @@ -284,6 +290,12 @@ def create(self, validated_data): Vaccine.objects.bulk_create(vaccines, ignore_conflicts=True) consultation.last_health_details = health_details consultation.save(update_fields=["last_health_details"]) + NotificationGenerator( + event=Notification.Event.PATIENT_HEALTH_DETAILS_CREATED, + caused_by=self.context["request"].user, + caused_object=health_details, + facility=consultation.patient.facility, + ).generate() except KeyError: if consultation.patient.last_consultation is None: raise ValidationError( diff --git a/care/facility/migrations/0317_auto_20220913_2021.py b/care/facility/migrations/0317_auto_20220913_2021.py new file mode 100644 index 0000000000..271ec5f622 --- /dev/null +++ b/care/facility/migrations/0317_auto_20220913_2021.py @@ -0,0 +1,39 @@ +# Generated by Django 2.2.11 on 2022-09-13 14:51 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("facility", "0316_auto_20220912_2244"), + ] + + operations = [ + migrations.AlterField( + model_name="notification", + name="event", + field=models.IntegerField( + choices=[ + (0, "MESSAGE"), + (20, "PATIENT_CREATED"), + (30, "PATIENT_UPDATED"), + (40, "PATIENT_DELETED"), + (42, "PATIENT_HEALTH_DETAILS_CREATED"), + (44, "PATIENT_HEALTH_DETAILS_UPDATED"), + (50, "PATIENT_CONSULTATION_CREATED"), + (60, "PATIENT_CONSULTATION_UPDATED"), + (70, "PATIENT_CONSULTATION_DELETED"), + (80, "INVESTIGATION_SESSION_CREATED"), + (90, "INVESTIGATION_UPDATED"), + (100, "PATIENT_FILE_UPLOAD_CREATED"), + (110, "CONSULTATION_FILE_UPLOAD_CREATED"), + (120, "PATIENT_CONSULTATION_UPDATE_CREATED"), + (130, "PATIENT_CONSULTATION_UPDATE_UPDATED"), + (140, "PATIENT_CONSULTATION_ASSIGNMENT"), + (200, "SHIFTING_UPDATED"), + ], + default=0, + ), + ), + ] diff --git a/care/facility/models/notification.py b/care/facility/models/notification.py index d5a40db4f8..912c82e289 100644 --- a/care/facility/models/notification.py +++ b/care/facility/models/notification.py @@ -26,6 +26,8 @@ class Event(enum.Enum): PATIENT_CREATED = 20 PATIENT_UPDATED = 30 PATIENT_DELETED = 40 + PATIENT_HEALTH_DETAILS_CREATED = 42 + PATIENT_HEALTH_DETAILS_UPDATED = 44 PATIENT_CONSULTATION_CREATED = 50 PATIENT_CONSULTATION_UPDATED = 60 PATIENT_CONSULTATION_DELETED = 70 diff --git a/care/utils/notification_handler.py b/care/utils/notification_handler.py index 8689bb5dcf..c9b905c50e 100644 --- a/care/utils/notification_handler.py +++ b/care/utils/notification_handler.py @@ -8,7 +8,10 @@ from care.facility.models.daily_round import DailyRound from care.facility.models.facility import Facility, FacilityUser from care.facility.models.notification import Notification -from care.facility.models.patient import PatientRegistration +from care.facility.models.patient import ( + PatientHealthDetails, + PatientRegistration, +) from care.facility.models.patient_consultation import PatientConsultation from care.facility.models.patient_investigation import InvestigationSession, InvestigationValue from care.facility.models.shifting import ShiftingRequest @@ -160,6 +163,27 @@ def generate_system_message(self): message = "Patient {} was deleted by {}".format( self.caused_object.name, self.caused_by.get_full_name() ) + elif isinstance(self.caused_object, PatientHealthDetails): + if ( + self.event + == Notification.Event.PATIENT_HEALTH_DETAILS_CREATED.value + ): + message = ( + "Health Details for Patient {} was created by {}".format( + self.caused_object.patient.name, + self.caused_object.get_full_name(), + ) + ) + if ( + self.event + == Notification.Event.PATIENT_HEALTH_DETAILS_UPDATED.value + ): + message = ( + "Health Details for Patient {} was updated by {}".format( + self.caused_object.patient.name, + self.caused_object.get_full_name(), + ) + ) elif isinstance(self.caused_object, PatientConsultation): if self.event == Notification.Event.PATIENT_CONSULTATION_CREATED.value: message = "Consultation for Patient {} was created by {}".format( From 86f9d185a607aa76187ae2c164b5cc1e55d4b66c Mon Sep 17 00:00:00 2001 From: cp-Coder Date: Tue, 13 Sep 2022 20:35:07 +0530 Subject: [PATCH 31/44] Reformatted the code --- .../api/serializers/patient_consultation.py | 112 ++++++--- care/facility/models/notification.py | 24 +- care/utils/notification_handler.py | 229 +++++++++++++----- 3 files changed, 275 insertions(+), 90 deletions(-) diff --git a/care/facility/api/serializers/patient_consultation.py b/care/facility/api/serializers/patient_consultation.py index 0edecac708..33e2137680 100644 --- a/care/facility/api/serializers/patient_consultation.py +++ b/care/facility/api/serializers/patient_consultation.py @@ -40,7 +40,9 @@ class PatientConsultationSerializer(serializers.ModelSerializer): id = serializers.CharField(source="external_id", read_only=True) - facility_name = serializers.CharField(source="facility.name", read_only=True) + facility_name = serializers.CharField( + source="facility.name", read_only=True + ) suggestion_text = ChoiceField( choices=PatientConsultation.SUGGESTION_CHOICES, read_only=True, @@ -59,10 +61,14 @@ class PatientConsultationSerializer(serializers.ModelSerializer): referred_to = ExternalIdSerializerField( queryset=Facility.objects.all(), required=False ) - patient = ExternalIdSerializerField(queryset=PatientRegistration.objects.all()) + patient = ExternalIdSerializerField( + queryset=PatientRegistration.objects.all() + ) facility = ExternalIdSerializerField(read_only=True) - assigned_to_object = UserAssignedSerializer(source="assigned_to", read_only=True) + assigned_to_object = UserAssignedSerializer( + source="assigned_to", read_only=True + ) assigned_to = serializers.PrimaryKeyRelatedField( queryset=User.objects.all(), required=False, allow_null=True @@ -74,10 +80,14 @@ class PatientConsultationSerializer(serializers.ModelSerializer): discharge_notes = serializers.CharField(read_only=True) action = ChoiceField( - choices=PatientRegistration.ActionChoices, write_only=True, required=False + choices=PatientRegistration.ActionChoices, + write_only=True, + required=False, ) - review_time = serializers.IntegerField(default=-1, write_only=True, required=False) + review_time = serializers.IntegerField( + default=-1, write_only=True, required=False + ) last_edited_by = UserBaseMinimumSerializer(read_only=True) created_by = UserBaseMinimumSerializer(read_only=True) @@ -107,10 +117,14 @@ def get_icd11_diagnoses_objects_by_ids(self, diagnoses_ids): return diagnosis_objects def get_icd11_diagnoses_object(self, consultation): - return self.get_icd11_diagnoses_objects_by_ids(consultation.icd11_diagnoses) + return self.get_icd11_diagnoses_objects_by_ids( + consultation.icd11_diagnoses + ) def get_icd11_provisional_diagnoses_object(self, consultation): - return self.get_icd11_diagnoses_objects_by_ids(consultation.icd11_provisional_diagnoses) + return self.get_icd11_diagnoses_objects_by_ids( + consultation.icd11_provisional_diagnoses + ) class Meta: model = PatientConsultation @@ -137,7 +151,11 @@ def update(self, instance, validated_data): if instance.discharge_date: raise ValidationError( - {"consultation": ["Discharged Consultation data cannot be updated"]} + { + "consultation": [ + "Discharged Consultation data cannot be updated" + ] + } ) if instance.suggestion == SuggestionChoices.OP: @@ -172,18 +190,27 @@ def update(self, instance, validated_data): consultation = super().update(instance, validated_data) try: - health_details_data = self.context["request"].data["new_health_details"] - vaccination_history = health_details_data.pop("vaccination_history", []) + health_details_data = self.context["request"].data[ + "new_health_details" + ] + vaccination_history = health_details_data.pop( + "vaccination_history", [] + ) PatientHealthDetails.objects.filter( id=consultation.last_health_details.id ).update(**self.context["request"].data["new_health_details"]) - consultation.last_health_details = PatientHealthDetails.objects.get( - id=consultation.last_health_details.id + consultation.last_health_details = ( + PatientHealthDetails.objects.get( + id=consultation.last_health_details.id + ) ) vaccines = [] for vaccine in vaccination_history: vaccines.append( - Vaccine(health_details=consultation.last_health_details, **vaccine) + Vaccine( + health_details=consultation.last_health_details, + **vaccine, + ) ) if vaccines: Vaccine.objects.bulk_create(vaccines, ignore_conflicts=True) @@ -198,7 +225,10 @@ def update(self, instance, validated_data): pass if "assigned_to" in validated_data: - if validated_data["assigned_to"] != _temp and validated_data["assigned_to"]: + if ( + validated_data["assigned_to"] != _temp + and validated_data["assigned_to"] + ): NotificationGenerator( event=Notification.Event.PATIENT_CONSULTATION_ASSIGNMENT, caused_by=self.context["request"].user, @@ -230,12 +260,16 @@ def create(self, validated_data): # Authorisation Check - allowed_facilities = get_home_facility_queryset(self.context["request"].user) + allowed_facilities = get_home_facility_queryset( + self.context["request"].user + ) if not allowed_facilities.filter( id=self.validated_data["patient"].facility.id ).exists(): raise ValidationError( - {"facility": "Consultation creates are only allowed in home facility"} + { + "facility": "Consultation creates are only allowed in home facility" + } ) # End Authorisation Checks @@ -254,7 +288,9 @@ def create(self, validated_data): if validated_data["patient"].last_consultation: if not validated_data["patient"].last_consultation.discharge_date: raise ValidationError( - {"consultation": "Exists please Edit Existing Consultation"} + { + "consultation": "Exists please Edit Existing Consultation" + } ) if "is_kasp" in validated_data: @@ -271,10 +307,13 @@ def create(self, validated_data): consultation.last_edited_by = self.context["request"].user consultation.save() - # Patient Health Details try: - health_details_data = self.context["request"].data["new_health_details"] - vaccination_history = health_details_data.pop("vaccination_history", []) + health_details_data = self.context["request"].data[ + "new_health_details" + ] + vaccination_history = health_details_data.pop( + "vaccination_history", [] + ) health_details = PatientHealthDetails( patient=consultation.patient, @@ -285,7 +324,9 @@ def create(self, validated_data): health_details.save() vaccines = [] for vaccine in vaccination_history: - vaccines.append(Vaccine(health_details=health_details, **vaccine)) + vaccines.append( + Vaccine(health_details=health_details, **vaccine) + ) if vaccines: Vaccine.objects.bulk_create(vaccines, ignore_conflicts=True) consultation.last_health_details = health_details @@ -305,10 +346,11 @@ def create(self, validated_data): ] } ) - if bed: consultation_bed = ConsultationBed( - bed=bed, consultation=consultation, start_date=consultation.created_date + bed=bed, + consultation=consultation, + start_date=consultation.created_date, ) consultation_bed.save() consultation.current_bed = consultation_bed @@ -327,7 +369,9 @@ def create(self, validated_data): if action != -1: patient.action = action if review_time > 0: - patient.review_time = localtime(now()) + timedelta(minutes=review_time) + patient.review_time = localtime(now()) + timedelta( + minutes=review_time + ) patient.save() NotificationGenerator( @@ -356,9 +400,9 @@ def validate(self, attrs): # TODO Add Bed Authorisation Validation if "suggestion" in validated: - if validated["suggestion"] is SuggestionChoices.R and not validated.get( - "referred_to" - ): + if validated[ + "suggestion" + ] is SuggestionChoices.R and not validated.get("referred_to"): raise ValidationError( { "referred_to": [ @@ -391,7 +435,11 @@ def validate(self, attrs): ) if validated["review_time"] <= 0: raise ValidationError( - {"review_time": ["This field value is must be greater than 0."]} + { + "review_time": [ + "This field value is must be greater than 0." + ] + } ) from care.facility.static_data.icd11 import ICDDiseases @@ -425,8 +473,12 @@ def validate(self, attrs): class PatientConsultationIDSerializer(serializers.ModelSerializer): - consultation_id = serializers.UUIDField(source="external_id", read_only=True) - patient_id = serializers.UUIDField(source="patient.external_id", read_only=True) + consultation_id = serializers.UUIDField( + source="external_id", read_only=True + ) + patient_id = serializers.UUIDField( + source="patient.external_id", read_only=True + ) class Meta: model = PatientConsultation diff --git a/care/facility/models/notification.py b/care/facility/models/notification.py index 912c82e289..d9e114788d 100644 --- a/care/facility/models/notification.py +++ b/care/facility/models/notification.py @@ -43,12 +43,26 @@ class Event(enum.Enum): EventChoices = [(e.value, e.name) for e in Event] intended_for = models.ForeignKey( - User, on_delete=models.SET_NULL, null=True, related_name="notification_intended_for", + User, + on_delete=models.SET_NULL, + null=True, + related_name="notification_intended_for", + ) + medium_sent = models.IntegerField( + choices=MediumChoices, default=Medium.SYSTEM.value + ) + caused_by = models.ForeignKey( + User, + on_delete=models.SET_NULL, + null=True, + related_name="notification_caused_by", ) - medium_sent = models.IntegerField(choices=MediumChoices, default=Medium.SYSTEM.value) - caused_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, related_name="notification_caused_by",) read_at = models.DateTimeField(null=True, blank=True) - event_type = models.IntegerField(choices=EventTypeChoices, default=EventType.SYSTEM_GENERATED.value) - event = models.IntegerField(choices=EventChoices, default=Event.MESSAGE.value) + event_type = models.IntegerField( + choices=EventTypeChoices, default=EventType.SYSTEM_GENERATED.value + ) + event = models.IntegerField( + choices=EventChoices, default=Event.MESSAGE.value + ) message = models.TextField(max_length=2000, null=True, default=None) caused_objects = JSONField(null=True, blank=True, default=dict) diff --git a/care/utils/notification_handler.py b/care/utils/notification_handler.py index c9b905c50e..f2449315f7 100644 --- a/care/utils/notification_handler.py +++ b/care/utils/notification_handler.py @@ -13,7 +13,10 @@ PatientRegistration, ) from care.facility.models.patient_consultation import PatientConsultation -from care.facility.models.patient_investigation import InvestigationSession, InvestigationValue +from care.facility.models.patient_investigation import ( + InvestigationSession, + InvestigationValue, +) from care.facility.models.shifting import ShiftingRequest from care.users.models import User from care.utils.sms.sendSMS import sendSMS @@ -64,15 +67,21 @@ def __init__( if not isinstance(event, Notification.Event): raise NotificationCreationException("Event Invalid") if not isinstance(caused_by, User): - raise NotificationCreationException("edited_by must be an instance of a user") + raise NotificationCreationException( + "edited_by must be an instance of a user" + ) if facility: if not isinstance(facility, Facility): - raise NotificationCreationException("facility must be an instance of Facility") + raise NotificationCreationException( + "facility must be an instance of Facility" + ) mediums = [] if notification_mediums: for medium in notification_mediums: if not isinstance(medium, Notification.Medium): - raise NotificationCreationException("Medium Type Invalid") + raise NotificationCreationException( + "Medium Type Invalid" + ) mediums.append(medium.value) data = { "event_type": event_type.value, @@ -118,16 +127,19 @@ def serialize_extra_data(self, extra_data): if not extra_data: return None for key in extra_data: - extra_data[key] = {"model_name": extra_data[key].__class__.__name__, "model_id": extra_data[key].id} + extra_data[key] = { + "model_name": extra_data[key].__class__.__name__, + "model_id": extra_data[key].id, + } return extra_data def deserialize_extra_data(self, extra_data): if not extra_data: return None for key in extra_data: - extra_data[key] = apps.get_model("facility.{}".format(extra_data[key]["model_name"])).objects.get( - id=extra_data[key]["model_id"] - ) + extra_data[key] = apps.get_model( + "facility.{}".format(extra_data[key]["model_name"]) + ).objects.get(id=extra_data[key]["model_id"]) return extra_data def generate_extra_users(self): @@ -137,16 +149,24 @@ def generate_extra_users(self): if isinstance(self.caused_object, PatientRegistration): if self.caused_object.last_consultation: if self.caused_object.last_consultation.assigned_to: - self.extra_users.append(self.caused_object.last_consultation.assigned_to.id) + self.extra_users.append( + self.caused_object.last_consultation.assigned_to.id + ) if isinstance(self.caused_object, InvestigationSession): if self.extra_data["consultation"].assigned_to: - self.extra_users.append(self.extra_data["consultation"].assigned_to.id) + self.extra_users.append( + self.extra_data["consultation"].assigned_to.id + ) if isinstance(self.caused_object, InvestigationValue): if self.caused_object.consultation.assigned_to: - self.extra_users.append(self.caused_object.consultation.assigned_to.id) + self.extra_users.append( + self.caused_object.consultation.assigned_to.id + ) if isinstance(self.caused_object, DailyRound): if self.caused_object.consultation.assigned_to: - self.extra_users.append(self.caused_object.consultation.assigned_to.id) + self.extra_users.append( + self.caused_object.consultation.assigned_to.id + ) def generate_system_message(self): message = "" @@ -185,22 +205,44 @@ def generate_system_message(self): ) ) elif isinstance(self.caused_object, PatientConsultation): - if self.event == Notification.Event.PATIENT_CONSULTATION_CREATED.value: - message = "Consultation for Patient {} was created by {}".format( - self.caused_object.patient.name, self.caused_by.get_full_name() + if ( + self.event + == Notification.Event.PATIENT_CONSULTATION_CREATED.value + ): + message = ( + "Consultation for Patient {} was created by {}".format( + self.caused_object.patient.name, + self.caused_by.get_full_name(), + ) ) - elif self.event == Notification.Event.PATIENT_CONSULTATION_UPDATED.value: - message = "Consultation for Patient {} was updated by {}".format( - self.caused_object.patient.name, self.caused_by.get_full_name() + elif ( + self.event + == Notification.Event.PATIENT_CONSULTATION_UPDATED.value + ): + message = ( + "Consultation for Patient {} was updated by {}".format( + self.caused_object.patient.name, + self.caused_by.get_full_name(), + ) ) - if self.event == Notification.Event.PATIENT_CONSULTATION_DELETED.value: - message = "Consultation for Patient {} was deleted by {}".format( - self.caused_object.patient.name, self.caused_by.get_full_name() + if ( + self.event + == Notification.Event.PATIENT_CONSULTATION_DELETED.value + ): + message = ( + "Consultation for Patient {} was deleted by {}".format( + self.caused_object.patient.name, + self.caused_by.get_full_name(), + ) ) elif isinstance(self.caused_object, InvestigationSession): - if self.event == Notification.Event.INVESTIGATION_SESSION_CREATED.value: + if ( + self.event + == Notification.Event.INVESTIGATION_SESSION_CREATED.value + ): message = "Investigation Session for Patient {} was created by {}".format( - self.extra_data["consultation"].patient.name, self.caused_by.get_full_name() + self.extra_data["consultation"].patient.name, + self.caused_by.get_full_name(), ) elif isinstance(self.caused_object, InvestigationValue): if self.event == Notification.Event.INVESTIGATION_UPDATED.value: @@ -210,13 +252,19 @@ def generate_system_message(self): self.caused_by.get_full_name(), ) elif isinstance(self.caused_object, DailyRound): - if self.event == Notification.Event.PATIENT_CONSULTATION_UPDATE_CREATED.value: + if ( + self.event + == Notification.Event.PATIENT_CONSULTATION_UPDATE_CREATED.value + ): message = "Consultation for Patient {} at facility {} was created by {}".format( self.caused_object.consultation.patient.name, self.caused_object.consultation.facility.name, self.caused_by.get_full_name(), ) - elif self.event == Notification.Event.PATIENT_CONSULTATION_UPDATE_UPDATED.value: + elif ( + self.event + == Notification.Event.PATIENT_CONSULTATION_UPDATE_UPDATED.value + ): message = "Consultation for Patient {} at facility {} was updated by {}".format( self.caused_object.consultation.patient.name, self.caused_object.consultation.facility.name, @@ -225,7 +273,8 @@ def generate_system_message(self): elif isinstance(self.caused_object, ShiftingRequest): if self.event == Notification.Event.SHIFTING_UPDATED.value: message = "Shifting for Patient {} was updated by {}".format( - self.caused_object.patient.name, self.caused_by.get_full_name(), + self.caused_object.patient.name, + self.caused_by.get_full_name(), ) return message @@ -268,40 +317,81 @@ def _get_default_medium(self): def generate_cause_objects(self): if isinstance(self.caused_object, PatientRegistration): - self.caused_objects["patient"] = str(self.caused_object.external_id) + self.caused_objects["patient"] = str( + self.caused_object.external_id + ) if self.caused_object.facility: - self.caused_objects["facility"] = str(self.caused_object.facility.external_id) + self.caused_objects["facility"] = str( + self.caused_object.facility.external_id + ) if isinstance(self.caused_object, PatientConsultation): - self.caused_objects["consultation"] = str(self.caused_object.external_id) - self.caused_objects["patient"] = str(self.caused_object.patient.external_id) + self.caused_objects["consultation"] = str( + self.caused_object.external_id + ) + self.caused_objects["patient"] = str( + self.caused_object.patient.external_id + ) if self.caused_object.patient.facility: - self.caused_objects["facility"] = str(self.caused_object.patient.facility.external_id) + self.caused_objects["facility"] = str( + self.caused_object.patient.facility.external_id + ) if isinstance(self.caused_object, InvestigationSession): - self.caused_objects["consultation"] = str(self.extra_data["consultation"].external_id) - self.caused_objects["patient"] = str(self.extra_data["consultation"].patient.external_id) + self.caused_objects["consultation"] = str( + self.extra_data["consultation"].external_id + ) + self.caused_objects["patient"] = str( + self.extra_data["consultation"].patient.external_id + ) if self.extra_data["consultation"].patient.facility: - self.caused_objects["facility"] = str(self.extra_data["consultation"].patient.facility.external_id) - self.caused_objects["session"] = str(self.caused_object.external_id) + self.caused_objects["facility"] = str( + self.extra_data[ + "consultation" + ].patient.facility.external_id + ) + self.caused_objects["session"] = str( + self.caused_object.external_id + ) if isinstance(self.caused_object, InvestigationValue): - self.caused_objects["consultation"] = str(self.caused_object.consultation.external_id) - self.caused_objects["patient"] = str(self.caused_object.consultation.patient.external_id) + self.caused_objects["consultation"] = str( + self.caused_object.consultation.external_id + ) + self.caused_objects["patient"] = str( + self.caused_object.consultation.patient.external_id + ) if self.caused_object.consultation.patient.facility: - self.caused_objects["facility"] = str(self.caused_object.consultation.patient.facility.external_id) - self.caused_objects["session"] = str(self.caused_object.session.external_id) - self.caused_objects["investigation"] = str(self.caused_object.investigation.external_id) + self.caused_objects["facility"] = str( + self.caused_object.consultation.patient.facility.external_id + ) + self.caused_objects["session"] = str( + self.caused_object.session.external_id + ) + self.caused_objects["investigation"] = str( + self.caused_object.investigation.external_id + ) if isinstance(self.caused_object, DailyRound): - self.caused_objects["consultation"] = str(self.caused_object.consultation.external_id) - self.caused_objects["patient"] = str(self.caused_object.consultation.patient.external_id) + self.caused_objects["consultation"] = str( + self.caused_object.consultation.external_id + ) + self.caused_objects["patient"] = str( + self.caused_object.consultation.patient.external_id + ) self.caused_objects["daily_round"] = str(self.caused_object.id) if self.caused_object.consultation.patient.facility: - self.caused_objects["facility"] = str(self.caused_object.consultation.facility.external_id) + self.caused_objects["facility"] = str( + self.caused_object.consultation.facility.external_id + ) if isinstance(self.caused_object, ShiftingRequest): - self.caused_objects["shifting"] = str(self.caused_object.external_id) + self.caused_objects["shifting"] = str( + self.caused_object.external_id + ) return True def generate_whatsapp_users(self): - if self.event == Notification.Event.PATIENT_CONSULTATION_ASSIGNMENT.value: + if ( + self.event + == Notification.Event.PATIENT_CONSULTATION_ASSIGNMENT.value + ): return [self.caused_object.assigned_to] raise Exception("Action Does not have associated users") @@ -309,7 +399,9 @@ def generate_system_users(self): users = [] extra_users = self.extra_users caused_user = self.caused_by - facility_users = FacilityUser.objects.filter(facility_id=self.facility.id) + facility_users = FacilityUser.objects.filter( + facility_id=self.facility.id + ) for facility_user in facility_users: if facility_user.user.id != caused_user.id: users.append(facility_user.user) @@ -337,18 +429,26 @@ def send_webpush_user(self, user, message): webpush( subscription_info={ "endpoint": user.pf_endpoint, - "keys": {"p256dh": user.pf_p256dh, "auth": user.pf_auth}, + "keys": { + "p256dh": user.pf_p256dh, + "auth": user.pf_auth, + }, }, data=message, vapid_private_key=settings.VAPID_PRIVATE_KEY, - vapid_claims={"sub": "mailto:info@coronasafe.network",}, + vapid_claims={ + "sub": "mailto:info@coronasafe.network", + }, ) except WebPushException as ex: print("Web Push Failed with Exception: {}", repr(ex)) if ex.response and ex.response.json(): extra = ex.response.json() print( - "Remote service replied with a {}:{}, {}", extra.code, extra.errno, extra.message, + "Remote service replied with a {}:{}, {}", + extra.code, + extra.errno, + extra.message, ) except Exception as e: print("Error When Doing WebPush", e) @@ -357,8 +457,15 @@ def generate(self): if not self.worker_initiated: return for medium in self.notification_mediums: - if medium == Notification.Medium.SMS.value and settings.SEND_SMS_NOTIFICATION: - sendSMS(self.generate_sms_phone_numbers(), self.generate_sms_message(), many=True) + if ( + medium == Notification.Medium.SMS.value + and settings.SEND_SMS_NOTIFICATION + ): + sendSMS( + self.generate_sms_phone_numbers(), + self.generate_sms_message(), + many=True, + ) elif medium == Notification.Medium.SYSTEM.value: if not self.message: self.message = self.generate_system_message() @@ -368,14 +475,26 @@ def generate(self): ) if not self.defer_notifications: self.send_webpush_user( - user, json.dumps({"external_id": str(notification_obj.external_id), "title": self.message}) + user, + json.dumps( + { + "external_id": str( + notification_obj.external_id + ), + "title": self.message, + } + ), ) - elif medium == Notification.Medium.WHATSAPP.value and settings.ENABLE_WHATSAPP: + elif ( + medium == Notification.Medium.WHATSAPP.value + and settings.ENABLE_WHATSAPP + ): for user in self.generate_whatsapp_users(): number = user.alt_phone_number message = self.generate_whatsapp_message() notification_obj = self.generate_message_for_user( user, message, Notification.Medium.WHATSAPP.value ) - sendWhatsappMessage(number, message, notification_obj.external_id) - + sendWhatsappMessage( + number, message, notification_obj.external_id + ) From 54ba51d8f93e174ca144d0ca9a79cba1ebc47b09 Mon Sep 17 00:00:00 2001 From: cp-Coder Date: Wed, 21 Sep 2022 20:18:25 +0530 Subject: [PATCH 32/44] Updated migrations and pre-commit config * Changed config file for flake8 to .flake8 * Removed all migrations for removing fields * Merged all the migrations for health details model into one migration * Refactored health details serializer * Reformatted the code --- .../api/serializers/patient_consultation.py | 24 ++- .../api/serializers/patient_health_details.py | 41 ++-- .../migrations/0309_patienthealthdetails.py | 70 ------ .../migrations/0310_auto_20220813_1123.py | 91 -------- .../migrations/0311_auto_20220831_0900.py | 87 -------- .../migrations/0313_auto_20220901_2213.py | 24 ++- .../migrations/0316_auto_20220831_0900.py | 199 ++++++++++++++++++ ...912_2244.py => 0317_auto_20220912_2244.py} | 42 +--- ...913_2021.py => 0318_auto_20220913_2021.py} | 4 +- care/facility/models/patient.py | 94 +++++---- 10 files changed, 313 insertions(+), 363 deletions(-) delete mode 100644 care/facility/migrations/0309_patienthealthdetails.py delete mode 100644 care/facility/migrations/0310_auto_20220813_1123.py delete mode 100644 care/facility/migrations/0311_auto_20220831_0900.py create mode 100644 care/facility/migrations/0316_auto_20220831_0900.py rename care/facility/migrations/{0316_auto_20220912_2244.py => 0317_auto_20220912_2244.py} (70%) rename care/facility/migrations/{0317_auto_20220913_2021.py => 0318_auto_20220913_2021.py} (92%) diff --git a/care/facility/api/serializers/patient_consultation.py b/care/facility/api/serializers/patient_consultation.py index 33e2137680..11b5aa45c4 100644 --- a/care/facility/api/serializers/patient_consultation.py +++ b/care/facility/api/serializers/patient_consultation.py @@ -1,11 +1,6 @@ from datetime import timedelta from django.utils.timezone import localtime, now -from care.facility.api.serializers.patient_health_details import ( - PatientHealthDetailsSerializer, -) - -from care.facility.models.patient import PatientHealthDetails, Vaccine from rest_framework import serializers from rest_framework.exceptions import ValidationError @@ -19,8 +14,17 @@ Facility, PatientRegistration, ) +from care.facility.api.serializers.patient_health_details import ( + PatientHealthDetailsSerializer, +) +from care.facility.models import ( + CATEGORY_CHOICES, + Facility, + PatientRegistration, +) from care.facility.models.bed import Bed, ConsultationBed from care.facility.models.notification import Notification +from care.facility.models.patient import PatientHealthDetails, Vaccine from care.facility.models.patient_base import ( DISCHARGE_REASON_CHOICES, SYMPTOM_CHOICES, @@ -199,11 +203,13 @@ def update(self, instance, validated_data): PatientHealthDetails.objects.filter( id=consultation.last_health_details.id ).update(**self.context["request"].data["new_health_details"]) + consultation.last_health_details = ( PatientHealthDetails.objects.get( id=consultation.last_health_details.id ) ) + vaccines = [] for vaccine in vaccination_history: vaccines.append( @@ -215,6 +221,7 @@ def update(self, instance, validated_data): if vaccines: Vaccine.objects.bulk_create(vaccines, ignore_conflicts=True) consultation.save(update_fields=["last_health_details"]) + NotificationGenerator( event=Notification.Event.PATIENT_HEALTH_DETAILS_UPDATED, caused_by=self.context["request"].user, @@ -322,6 +329,7 @@ def create(self, validated_data): **health_details_data, ) health_details.save() + vaccines = [] for vaccine in vaccination_history: vaccines.append( @@ -329,15 +337,17 @@ def create(self, validated_data): ) if vaccines: Vaccine.objects.bulk_create(vaccines, ignore_conflicts=True) + consultation.last_health_details = health_details consultation.save(update_fields=["last_health_details"]) + NotificationGenerator( event=Notification.Event.PATIENT_HEALTH_DETAILS_CREATED, caused_by=self.context["request"].user, caused_object=health_details, facility=consultation.patient.facility, ).generate() - except KeyError: + except KeyError as error: if consultation.patient.last_consultation is None: raise ValidationError( { @@ -345,7 +355,7 @@ def create(self, validated_data): "Please provide the health details of the patient" ] } - ) + ) from error if bed: consultation_bed = ConsultationBed( bed=bed, diff --git a/care/facility/api/serializers/patient_health_details.py b/care/facility/api/serializers/patient_health_details.py index 993f0378d7..95d9bcdadd 100644 --- a/care/facility/api/serializers/patient_health_details.py +++ b/care/facility/api/serializers/patient_health_details.py @@ -1,7 +1,6 @@ from django.forms import ChoiceField from rest_framework import serializers -from care.facility.api.serializers.facility import FacilityBasicInfoSerializer from care.facility.models import ( PatientConsultation, PatientHealthDetails, @@ -9,37 +8,31 @@ ) from care.facility.models.facility import Facility from care.facility.models.patient import Vaccine -from care.facility.models.patient_base import ( - BLOOD_GROUP_CHOICES, - VACCINE_CHOICES, -) +from care.facility.models.patient_base import BLOOD_GROUP_CHOICES, VACCINE_CHOICES from care.utils.serializer.external_id_field import ExternalIdSerializerField +class VaccinationHistorySerializer(serializers.ModelSerializer): + vaccine = serializers.ChoiceField(choices=VACCINE_CHOICES) + doses = serializers.IntegerField(required=False, default=0) + last_vaccinated_date = serializers.DateField() + + class Meta: + model = Vaccine + fields = ( + "vaccine", + "doses", + "last_vaccinated_date", + ) + + class PatientHealthDetailsSerializer(serializers.ModelSerializer): - class VaccinationHistorySerializer(serializers.Serializer): - vaccine = serializers.ChoiceField(choices=VACCINE_CHOICES) - doses = serializers.IntegerField(required=False, default=0) - last_vaccinated_date = serializers.DateField() - - class Meta: - model = Vaccine - fields = ( - "vaccine", - "doses", - "last_vaccinated_date", - ) id = serializers.CharField(source="external_id", read_only=True) - patient = ExternalIdSerializerField( - queryset=PatientRegistration.objects.all() - ) + patient = ExternalIdSerializerField(queryset=PatientRegistration.objects.all()) facility = ExternalIdSerializerField(queryset=Facility.objects.all()) - facility_object = FacilityBasicInfoSerializer( - source="facility", read_only=True - ) - created_in_consultation = ExternalIdSerializerField( + consultation = ExternalIdSerializerField( queryset=PatientConsultation.objects.all(), required=False ) blood_group = ChoiceField(choices=BLOOD_GROUP_CHOICES, required=True) diff --git a/care/facility/migrations/0309_patienthealthdetails.py b/care/facility/migrations/0309_patienthealthdetails.py deleted file mode 100644 index e517fdbfd1..0000000000 --- a/care/facility/migrations/0309_patienthealthdetails.py +++ /dev/null @@ -1,70 +0,0 @@ -import care.facility.models.mixins.permissions.patient -from django.db import migrations, models -import django.db.models.deletion -import uuid - - -class Migration(migrations.Migration): - - dependencies = [ - ("facility", "0308_auto_20220805_2247"), - ] - - operations = [ - migrations.CreateModel( - name="PatientHealthDetails", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "external_id", - models.UUIDField(db_index=True, default=uuid.uuid4, unique=True), - ), - ( - "created_date", - models.DateTimeField(auto_now_add=True, db_index=True, null=True), - ), - ( - "modified_date", - models.DateTimeField(auto_now=True, db_index=True, null=True), - ), - ("deleted", models.BooleanField(db_index=True, default=False)), - ( - "family_details", - models.TextField( - blank=True, default="", verbose_name="Patient's Family Details" - ), - ), - ( - "facility", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="health_details", - to="facility.Facility", - ), - ), - ( - "patient", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="health_details", - to="facility.PatientRegistration", - ), - ), - ], - options={ - "abstract": False, - }, - bases=( - models.Model, - care.facility.models.mixins.permissions.patient.PatientRelatedPermissionMixin, - ), - ), - ] diff --git a/care/facility/migrations/0310_auto_20220813_1123.py b/care/facility/migrations/0310_auto_20220813_1123.py deleted file mode 100644 index 5507589cd8..0000000000 --- a/care/facility/migrations/0310_auto_20220813_1123.py +++ /dev/null @@ -1,91 +0,0 @@ -from django.db import migrations, models - - -def populate_data(apps, schema_editor): - HealthDetails = apps.get_model("facility", "PatientHealthDetails") - PatientRegistration = apps.get_model("facility", "PatientRegistration") - patients = PatientRegistration.objects.all() - - health_details_objs = [] - for patient in patients: - has_allergy = False - allergies = "" - blood_group = None - - if patient.allergies: - has_allergy = True - allergies = patient.allergies - - if patient.blood_group is not None: - blood_group = patient.blood_group - - health_details_objs.append( - HealthDetails( - patient=patient, - facility=patient.facility, - has_allergy=has_allergy, - allergies=allergies, - blood_group=blood_group, - ) - ) - - HealthDetails.objects.bulk_create(health_details_objs) - - -class Migration(migrations.Migration): - - dependencies = [ - ("facility", "0309_patienthealthdetails"), - ] - - operations = [ - migrations.AddField( - model_name="patienthealthdetails", - name="allergies", - field=models.TextField( - blank=True, default="", verbose_name="Patient's Known Allergies" - ), - ), - migrations.AddField( - model_name="patienthealthdetails", - name="blood_group", - field=models.CharField( - choices=[ - ("A+", "A+"), - ("A-", "A-"), - ("B+", "B+"), - ("B-", "B-"), - ("AB+", "AB+"), - ("AB-", "AB-"), - ("O+", "O+"), - ("O-", "O-"), - ("UNK", "UNKNOWN"), - ], - max_length=4, - null=True, - verbose_name="Blood Group of Patient", - ), - ), - migrations.AddField( - model_name="patienthealthdetails", - name="has_allergy", - field=models.BooleanField(default=False), - ), - migrations.RunPython(populate_data, migrations.RunPython.noop), - migrations.RemoveField( - model_name="historicalpatientregistration", - name="allergies", - ), - migrations.RemoveField( - model_name="historicalpatientregistration", - name="blood_group", - ), - migrations.RemoveField( - model_name="patientregistration", - name="allergies", - ), - migrations.RemoveField( - model_name="patientregistration", - name="blood_group", - ), - ] diff --git a/care/facility/migrations/0311_auto_20220831_0900.py b/care/facility/migrations/0311_auto_20220831_0900.py deleted file mode 100644 index 5a0a0b77a4..0000000000 --- a/care/facility/migrations/0311_auto_20220831_0900.py +++ /dev/null @@ -1,87 +0,0 @@ -import django.core.validators -from django.db import migrations, models -import django.db.models.deletion - -from care.facility.models.patient import PatientHealthDetails, PatientConsultation - - -def populate_data(apps, schema_editor): - PatientConsultationModel = apps.get_model("facility", "PatientConsultation") - patients_cons = PatientConsultationModel.objects.all() - - for patient_cons in patients_cons: - try: - health_details = PatientHealthDetails.objects.get( - id=patient_cons.patient.id - ) - if patient_cons.weight: - health_details.weight = patient_cons.weight - - if patient_cons.height: - health_details.height = patient_cons.height - - health_details.created_in_consultation = PatientConsultation.objects.get( - id=patient_cons.id - ) - - health_details.save() - except PatientHealthDetails.DoesNotExist: - pass - - -class Migration(migrations.Migration): - - dependencies = [ - ("facility", "0310_auto_20220813_1123"), - ] - - operations = [ - migrations.AddField( - model_name="patienthealthdetails", - name="created_in_consultation", - field=models.ForeignKey( - null=True, - on_delete=django.db.models.deletion.CASCADE, - to="facility.PatientConsultation", - ), - ), - migrations.AddField( - model_name="patientconsultation", - name="last_health_details", - field=models.ForeignKey( - default=None, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to="facility.PatientHealthDetails", - ), - ), - migrations.AddField( - model_name="patienthealthdetails", - name="height", - field=models.FloatField( - default=None, - null=True, - validators=[django.core.validators.MinValueValidator(0)], - verbose_name="Patient's Height in CM", - ), - ), - migrations.AddField( - model_name="patienthealthdetails", - name="weight", - field=models.FloatField( - default=None, - null=True, - validators=[django.core.validators.MinValueValidator(0)], - verbose_name="Patient's Weight in KG", - ), - ), - migrations.RunPython(populate_data, migrations.RunPython.noop), - migrations.RemoveField( - model_name="patientconsultation", - name="height", - ), - migrations.RemoveField( - model_name="patientconsultation", - name="weight", - ), - ] diff --git a/care/facility/migrations/0313_auto_20220901_2213.py b/care/facility/migrations/0313_auto_20220901_2213.py index 5997316184..569d170cb7 100644 --- a/care/facility/migrations/0313_auto_20220901_2213.py +++ b/care/facility/migrations/0313_auto_20220901_2213.py @@ -6,17 +6,25 @@ class Migration(migrations.Migration): dependencies = [ - ('facility', '0312_patientconsultation_investigation'), + ("facility", "0312_patientconsultation_investigation"), ] operations = [ migrations.AlterField( - model_name='facility', - name='features', - field=multiselectfield.db.fields.MultiSelectField(blank=True, - choices=[(1, 'CT Scan Facility'), (2, 'Maternity Care'), - (3, 'X-Ray facility'), (4, 'Neonatal care'), - (5, 'Operation theater'), (6, 'Blood Bank')], - max_length=11, null=True), + model_name="facility", + name="features", + field=multiselectfield.db.fields.MultiSelectField( + blank=True, + choices=[ + (1, "CT Scan Facility"), + (2, "Maternity Care"), + (3, "X-Ray facility"), + (4, "Neonatal care"), + (5, "Operation theater"), + (6, "Blood Bank"), + ], + max_length=11, + null=True, + ), ), ] diff --git a/care/facility/migrations/0316_auto_20220831_0900.py b/care/facility/migrations/0316_auto_20220831_0900.py new file mode 100644 index 0000000000..daedd55f16 --- /dev/null +++ b/care/facility/migrations/0316_auto_20220831_0900.py @@ -0,0 +1,199 @@ +import uuid +from django.db import migrations, models +import django.db.models.deletion +import django.core.validators +import care.facility.models.mixins.permissions.patient + + +def populate_data(apps, schema_editor): + health_details = apps.get_model("facility", "PatientHealthDetails") + patient_registration = apps.get_model("facility", "PatientRegistration") + patients = patient_registration.objects.all() + + health_details_objs = [] + for patient in patients: + has_allergy = False + allergies = "" + blood_group = None + consultation = None + weight = 0.0 + height = 0.0 + + if patient.allergies: + has_allergy = True + allergies = patient.allergies + + if patient.blood_group is not None: + blood_group = patient.blood_group + + if patient.last_consultation is not None: + consultation = patient.last_consultation + + if patient.last_consultation.weight is not None: + weight = patient.last_consultation.weight + if patient.last_consultation.height is not None: + height = patient.last_consultation.height + + health_details_obj = health_details( + patient=patient, + facility=patient.last_consultation.facility, + weight=weight, + height=height, + consultation=consultation, + has_allergy=has_allergy, + allergies=allergies, + blood_group=blood_group, + ) + + if patient.last_consultation is not None: + patient.last_consultation.last_health_details = health_details_obj + + health_details_objs.append(health_details_obj) + + health_details.objects.bulk_create(health_details_objs) + + +class Migration(migrations.Migration): + + dependencies = [ + ("facility", "0315_merge_20220912_1001"), + ] + + operations = [ + migrations.CreateModel( + name="PatientHealthDetails", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "external_id", + models.UUIDField( + db_index=True, default=uuid.uuid4, unique=True + ), + ), + ( + "created_date", + models.DateTimeField( + auto_now_add=True, db_index=True, null=True + ), + ), + ( + "modified_date", + models.DateTimeField( + auto_now=True, db_index=True, null=True + ), + ), + ("deleted", models.BooleanField(db_index=True, default=False)), + ( + "family_details", + models.TextField( + blank=True, + default="", + verbose_name="Patient's Family Details", + ), + ), + ( + "facility", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="health_details", + to="facility.Facility", + ), + ), + ( + "patient", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="health_details", + to="facility.PatientRegistration", + ), + ), + ( + "has_allergy", + models.BooleanField(default=False), + ), + ( + "allergies", + models.TextField( + blank=True, + default="", + verbose_name="Patient's Known Allergies", + ), + ), + ( + "blood_group", + models.CharField( + choices=[ + ("A+", "A+"), + ("A-", "A-"), + ("B+", "B+"), + ("B-", "B-"), + ("AB+", "AB+"), + ("AB-", "AB-"), + ("O+", "O+"), + ("O-", "O-"), + ("UNK", "UNKNOWN"), + ], + max_length=4, + null=True, + verbose_name="Blood Group of Patient", + ), + ), + ( + "consultation", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="facility.PatientConsultation", + ), + ), + ( + "weight", + models.FloatField( + default=None, + null=True, + validators=[ + django.core.validators.MinValueValidator(0) + ], + verbose_name="Patient's Weight in KG", + ), + ), + ( + "height", + models.FloatField( + default=None, + null=True, + validators=[ + django.core.validators.MinValueValidator(0) + ], + verbose_name="Patient's Height in CM", + ), + ), + ], + options={ + "abstract": False, + }, + bases=( + models.Model, + care.facility.models.mixins.permissions.patient.PatientRelatedPermissionMixin, + ), + ), + migrations.AddField( + model_name="patientconsultation", + name="last_health_details", + field=models.ForeignKey( + default=None, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="facility.PatientHealthDetails", + ), + ), + migrations.RunPython(populate_data, migrations.RunPython.noop), + ] diff --git a/care/facility/migrations/0316_auto_20220912_2244.py b/care/facility/migrations/0317_auto_20220912_2244.py similarity index 70% rename from care/facility/migrations/0316_auto_20220912_2244.py rename to care/facility/migrations/0317_auto_20220912_2244.py index b5e2565c28..70234ed2e8 100644 --- a/care/facility/migrations/0316_auto_20220912_2244.py +++ b/care/facility/migrations/0317_auto_20220912_2244.py @@ -1,5 +1,3 @@ -# Generated by Django 2.2.11 on 2022-09-12 17:14 - import django.core.validators from django.db import migrations, models import django.db.models.deletion @@ -7,14 +5,14 @@ def create_vaccination_data(apps, schema_editor): - PatientHealthDetails = apps.get_model("facility", "PatientHealthDetails") - Vaccine = apps.get_model("facility", "Vaccine") - health_details_objs = PatientHealthDetails.objects.all() + patient_health_details = apps.get_model("facility", "PatientHealthDetails") + vaccine = apps.get_model("facility", "Vaccine") + health_details_objs = patient_health_details.objects.all() vaccine_objs = [] for health_detail in health_details_objs: vaccine_objs.append( - Vaccine( + vaccine( health_details=health_detail, vaccine=health_detail.patient.vaccine_name, doses=health_detail.patient.number_of_doses, @@ -22,7 +20,7 @@ def create_vaccination_data(apps, schema_editor): ) ) - Vaccine.objects.bulk_create(vaccine_objs) + vaccine.objects.bulk_create(vaccine_objs) class Migration(migrations.Migration): @@ -74,7 +72,9 @@ class Migration(migrations.Migration): ( "last_vaccinated_date", models.DateField( - blank=True, null=True, verbose_name="Date Last Vaccinated" + blank=True, + null=True, + verbose_name="Date Last Vaccinated", ), ), ("deleted", models.BooleanField(default=False)), @@ -97,29 +97,7 @@ class Migration(migrations.Migration): where=partial_index.PQ(deleted=False), ), ), - migrations.RunPython(create_vaccination_data, migrations.RunPython.noop), - migrations.RemoveField( - model_name="historicalpatientregistration", - name="last_vaccinated_date", - ), - migrations.RemoveField( - model_name="historicalpatientregistration", - name="number_of_doses", - ), - migrations.RemoveField( - model_name="historicalpatientregistration", - name="vaccine_name", - ), - migrations.RemoveField( - model_name="patientregistration", - name="last_vaccinated_date", - ), - migrations.RemoveField( - model_name="patientregistration", - name="number_of_doses", - ), - migrations.RemoveField( - model_name="patientregistration", - name="vaccine_name", + migrations.RunPython( + create_vaccination_data, migrations.RunPython.noop ), ] diff --git a/care/facility/migrations/0317_auto_20220913_2021.py b/care/facility/migrations/0318_auto_20220913_2021.py similarity index 92% rename from care/facility/migrations/0317_auto_20220913_2021.py rename to care/facility/migrations/0318_auto_20220913_2021.py index 271ec5f622..c658935744 100644 --- a/care/facility/migrations/0317_auto_20220913_2021.py +++ b/care/facility/migrations/0318_auto_20220913_2021.py @@ -1,12 +1,10 @@ -# Generated by Django 2.2.11 on 2022-09-13 14:51 - from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ("facility", "0316_auto_20220912_2244"), + ("facility", "0317_auto_20220912_2244"), ] operations = [ diff --git a/care/facility/models/patient.py b/care/facility/models/patient.py index 8747eac166..55542713b0 100644 --- a/care/facility/models/patient.py +++ b/care/facility/models/patient.py @@ -170,14 +170,6 @@ class TestTypeEnum(enum.Enum): verbose_name="Return Date from the Last Country if Travelled", ) - present_health = models.TextField( - default="", blank=True, verbose_name="Patient's Current Health Details" - ) - ongoing_medication = models.TextField( - default="", - blank=True, - verbose_name="Already pescribed medication if any", - ) has_SARI = models.BooleanField( default=False, verbose_name="Does the Patient Suffer from SARI" ) @@ -521,8 +513,8 @@ def save(self, *args, **kwargs) -> None: "countries_travelled": "Countries Patient has Travelled to", "date_of_return": "Return Date from the Last Country if Travelled", "is_migrant_worker": "Is the Patient a Migrant Worker", - "present_health": "Patient's Current Health Details", - "ongoing_medication": "Already pescribed medication if any", + # "present_health": "Patient's Current Health Details", + # "ongoing_medication": "Already pescribed medication if any", "has_SARI": "Does the Patient Suffer from SARI", "date_of_receipt_of_information": "Patient's information received date", "will_donate_blood": "Will Patient Donate Blood?", @@ -711,34 +703,6 @@ class ModeOfContactEnum(enum.IntEnum): objects = BaseManager() -class Disease(models.Model): - patient = models.ForeignKey( - PatientRegistration, - on_delete=models.CASCADE, - related_name="medical_history", - ) - disease = models.IntegerField(choices=DISEASE_CHOICES) - details = models.TextField(blank=True, null=True) - deleted = models.BooleanField(default=False) - - objects = BaseManager() - - class Meta: - indexes = [ - PartialIndex( - fields=["patient", "disease"], - unique=True, - where=PQ(deleted=False), - ) - ] - - def __str__(self): - return self.patient.name + " - " + self.get_disease_display() - - def get_disease_display(self): - return DISEASE_CHOICES[self.disease - 1][1] - - class FacilityPatientStatsHistory( FacilityBaseModel, FacilityRelatedPermissionMixin ): @@ -818,7 +782,7 @@ class PatientHealthDetails(PatientBaseModel, PatientRelatedPermissionMixin): verbose_name="Blood Group of Patient", ) - created_in_consultation = models.ForeignKey( + consultation = models.ForeignKey( PatientConsultation, on_delete=models.CASCADE, null=True, @@ -874,5 +838,53 @@ class Meta: ) ] - def __str__(self): - return self.patient.name + " - " + self.vaccine + def __str__(self): + return self.health_details.patient.name + " - " + self.vaccine + + +class MedicalHistory(PatientBaseModel, PatientRelatedPermissionMixin): + patient = models.ForeignKey( + PatientRegistration, + on_delete=models.CASCADE, + ) + present_health = models.TextField( + default="", blank=True, verbose_name="Patient's Current Health Details" + ) + ongoing_medication = models.TextField( + default="", + blank=True, + verbose_name="Already pescribed medication if any", + ) + + +class Disease(models.Model): + medical_history = models.ForeignKey( + MedicalHistory, + on_delete=models.CASCADE, + related_name="medical_history", + ) + disease_date = models.DateField(verbose_name="Disease Date") + disease = models.IntegerField(choices=DISEASE_CHOICES) + details = models.TextField(blank=True, null=True) + deleted = models.BooleanField(default=False) + + objects = BaseManager() + + class Meta: + indexes = [ + PartialIndex( + fields=["medical_history", "disease"], + unique=True, + where=PQ(deleted=False), + ) + ] + + def __str__(self): + return ( + self.medical_history.patient.name + + " - " + + self.get_disease_display() + ) + + def get_disease_display(self): + return DISEASE_CHOICES[self.disease - 1][1] From 1abf293001efbd5876dc3d187a9e01b6279fd5d1 Mon Sep 17 00:00:00 2001 From: cp-Coder Date: Wed, 21 Sep 2022 20:28:37 +0530 Subject: [PATCH 33/44] Updated health details serializer methods and reformatted the code --- care/facility/api/serializers/patient_consultation.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/care/facility/api/serializers/patient_consultation.py b/care/facility/api/serializers/patient_consultation.py index 11b5aa45c4..a654999823 100644 --- a/care/facility/api/serializers/patient_consultation.py +++ b/care/facility/api/serializers/patient_consultation.py @@ -226,7 +226,7 @@ def update(self, instance, validated_data): event=Notification.Event.PATIENT_HEALTH_DETAILS_UPDATED, caused_by=self.context["request"].user, caused_object=consultation.last_health_details, - facility=consultation.patient.facility, + facility=consultation.facility, ).generate() except KeyError: pass @@ -325,7 +325,7 @@ def create(self, validated_data): health_details = PatientHealthDetails( patient=consultation.patient, facility=consultation.facility, - created_in_consultation=consultation, + consultation=consultation, **health_details_data, ) health_details.save() @@ -345,7 +345,7 @@ def create(self, validated_data): event=Notification.Event.PATIENT_HEALTH_DETAILS_CREATED, caused_by=self.context["request"].user, caused_object=health_details, - facility=consultation.patient.facility, + facility=consultation.facility, ).generate() except KeyError as error: if consultation.patient.last_consultation is None: From 935bf6d476ae286aeb4b9e9d58027f8909cfab94 Mon Sep 17 00:00:00 2001 From: cp-Coder Date: Wed, 21 Sep 2022 23:50:47 +0530 Subject: [PATCH 34/44] Reverted the migrations * Added deleted fields with `#deprecated` text * Reformatted the code --- care/facility/models/patient.py | 116 ++++++++++--------- care/facility/models/patient_consultation.py | 38 +++++- 2 files changed, 97 insertions(+), 57 deletions(-) diff --git a/care/facility/models/patient.py b/care/facility/models/patient.py index 55542713b0..54d65a6cab 100644 --- a/care/facility/models/patient.py +++ b/care/facility/models/patient.py @@ -87,12 +87,8 @@ class TestTypeEnum(enum.Enum): TestTypeChoices = [(e.value, e.name) for e in TestTypeEnum] - source = models.IntegerField( - choices=SourceChoices, default=SourceEnum.CARE.value - ) - facility = models.ForeignKey( - "Facility", on_delete=models.SET_NULL, null=True - ) + source = models.IntegerField(choices=SourceChoices, default=SourceEnum.CARE.value) + facility = models.ForeignKey("Facility", on_delete=models.SET_NULL, null=True) nearest_facility = models.ForeignKey( "Facility", on_delete=models.SET_NULL, @@ -141,6 +137,14 @@ class TestTypeEnum(enum.Enum): default=False, verbose_name="Is the Patient a Medical Worker" ) + blood_group = models.CharField( + choices=BLOOD_GROUP_CHOICES, + null=True, + blank=False, + max_length=4, + verbose_name="Blood Group of Patient", + ) # deprecated + contact_with_confirmed_carrier = models.BooleanField( default=False, verbose_name="Confirmed Contact with a Covid19 Carrier" ) @@ -170,6 +174,24 @@ class TestTypeEnum(enum.Enum): verbose_name="Return Date from the Last Country if Travelled", ) + allergies = models.TextField( + default="", + blank=True, + verbose_name="Patient's Known Allergies", + ) # deprecated + + present_health = models.TextField( + default="", + blank=True, + verbose_name="Patient's Current Health Details", + ) # deprecated + + ongoing_medication = models.TextField( + default="", + blank=True, + verbose_name="Already pescribed medication if any", + ) # deprecated + has_SARI = models.BooleanField( default=False, verbose_name="Does the Patient Suffer from SARI" ) @@ -182,18 +204,14 @@ class TestTypeEnum(enum.Enum): max_length=255, default="", verbose_name="Ward of Patient", blank=False ) - ward = models.ForeignKey( - Ward, on_delete=models.SET_NULL, null=True, blank=True - ) + ward = models.ForeignKey(Ward, on_delete=models.SET_NULL, null=True, blank=True) local_body = models.ForeignKey( LocalBody, on_delete=models.SET_NULL, null=True, blank=True ) district = models.ForeignKey( District, on_delete=models.SET_NULL, null=True, blank=True ) - state = models.ForeignKey( - State, on_delete=models.SET_NULL, null=True, blank=True - ) + state = models.ForeignKey(State, on_delete=models.SET_NULL, null=True, blank=True) is_migrant_worker = models.BooleanField( default=False, @@ -252,9 +270,7 @@ class TestTypeEnum(enum.Enum): verbose_name="Patient's information received date", ) - test_id = models.CharField( - default="", max_length=100, null=True, blank=True - ) + test_id = models.CharField(default="", max_length=100, null=True, blank=True) # Issue #600 Care_Fe date_of_test = models.DateTimeField( @@ -345,6 +361,21 @@ class TestTypeEnum(enum.Enum): verbose_name="Is the Patient Vaccinated Against COVID-19", ) + number_of_doses = models.PositiveIntegerField( + default=0, + null=False, + blank=False, + validators=[MinValueValidator(0), MaxValueValidator(3)], + ) # deprecated + + vaccine_name = models.CharField( + choices=VACCINE_CHOICES, + default=None, + null=True, + blank=False, + max_length=15, + ) # deprecated + covin_id = models.CharField( max_length=15, default=None, @@ -353,6 +384,12 @@ class TestTypeEnum(enum.Enum): verbose_name="COVID-19 Vaccination ID", ) + last_vaccinated_date = models.DateTimeField( + null=True, + blank=True, + verbose_name="Date Last Vaccinated", + ) # deprecated + # Extras cluster_name = models.CharField( max_length=255, @@ -380,16 +417,12 @@ class TestTypeEnum(enum.Enum): related_name="root_patient_assigned_to", ) - history = HistoricalRecords( - excluded_fields=["patient_search_id", "meta_info"] - ) + history = HistoricalRecords(excluded_fields=["patient_search_id", "meta_info"]) objects = BaseManager() def __str__(self): - return "{} - {} - {}".format( - self.name, self.age, self.get_gender_display() - ) + return "{} - {} - {}".format(self.name, self.age, self.get_gender_display()) @property def tele_consultation_history(self): @@ -562,13 +595,9 @@ def save(self, *args, **kwargs) -> None: "last_consultation__deprecated_covid_category": ( lambda x: REVERSE_COVID_CATEGORY_CHOICES.get(x, "-") ), - "last_consultation__category": lambda x: REVERSE_CATEGORY_CHOICES.get( - x, "-" - ), + "last_consultation__category": lambda x: REVERSE_CATEGORY_CHOICES.get(x, "-"), "last_consultation__suggestion": ( - lambda x: PatientConsultation.REVERSE_SUGGESTION_CHOICES.get( - x, "-" - ) + lambda x: PatientConsultation.REVERSE_SUGGESTION_CHOICES.get(x, "-") ), "last_consultation__admitted": pretty_boolean, "last_consultation__current_bed__bed__bed_type": ( @@ -587,9 +616,7 @@ class PatientSearch(PatientBaseModel): year_of_birth = models.IntegerField() state_id = models.IntegerField() - facility = models.ForeignKey( - "Facility", on_delete=models.SET_NULL, null=True - ) + facility = models.ForeignKey("Facility", on_delete=models.SET_NULL, null=True) patient_external_id = EncryptedCharField(max_length=100, default="") allow_transfer = models.BooleanField(default=True) @@ -597,9 +624,7 @@ class PatientSearch(PatientBaseModel): class Meta: indexes = [ - models.Index( - fields=["year_of_birth", "date_of_birth", "phone_number"] - ), + models.Index(fields=["year_of_birth", "date_of_birth", "phone_number"]), models.Index(fields=["year_of_birth", "phone_number"]), ] @@ -607,8 +632,7 @@ class Meta: def has_read_permission(request): if ( request.user.is_superuser - or request.user.user_type - >= User.TYPE_VALUE_MAP["DistrictLabAdmin"] + or request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"] ): return True elif ( @@ -671,9 +695,7 @@ class ModeOfContactEnum(enum.IntEnum): TRAVELLED_TOGETHER_WITHOUT_HIGH_EXPOSURE = 9 RelationChoices = [(item.value, item.name) for item in RelationEnum] - ModeOfContactChoices = [ - (item.value, item.name) for item in ModeOfContactEnum - ] + ModeOfContactChoices = [(item.value, item.name) for item in ModeOfContactEnum] patient = models.ForeignKey( PatientRegistration, @@ -691,9 +713,7 @@ class ModeOfContactEnum(enum.IntEnum): date_of_first_contact = models.DateField(null=True) date_of_last_contact = models.DateField(null=True) - is_primary = models.BooleanField( - help_text="If false, then secondary contact" - ) + is_primary = models.BooleanField(help_text="If false, then secondary contact") condition_of_contact_is_symptomatic = models.BooleanField( help_text="While in contact, did the patient showing symptoms" ) @@ -703,9 +723,7 @@ class ModeOfContactEnum(enum.IntEnum): objects = BaseManager() -class FacilityPatientStatsHistory( - FacilityBaseModel, FacilityRelatedPermissionMixin -): +class FacilityPatientStatsHistory(FacilityBaseModel, FacilityRelatedPermissionMixin): facility = models.ForeignKey("Facility", on_delete=models.PROTECT) entry_date = models.DateField() num_patients_visited = models.IntegerField(default=0) @@ -734,9 +752,7 @@ class Meta: class PatientMobileOTP(BaseModel): is_used = models.BooleanField(default=False) - phone_number = models.CharField( - max_length=14, validators=[phone_number_regex] - ) + phone_number = models.CharField(max_length=14, validators=[phone_number_regex]) otp = models.CharField(max_length=10) @@ -880,11 +896,7 @@ class Meta: ] def __str__(self): - return ( - self.medical_history.patient.name - + " - " - + self.get_disease_display() - ) + return self.medical_history.patient.name + " - " + self.get_disease_display() def get_disease_display(self): return DISEASE_CHOICES[self.disease - 1][1] diff --git a/care/facility/models/patient_consultation.py b/care/facility/models/patient_consultation.py index 631885fc14..b2dae4df96 100644 --- a/care/facility/models/patient_consultation.py +++ b/care/facility/models/patient_consultation.py @@ -33,7 +33,9 @@ class PatientConsultation(PatientBaseModel, PatientRelatedPermissionMixin): REVERSE_SUGGESTION_CHOICES = reverse_choices(SUGGESTION_CHOICES) patient = models.ForeignKey( - "PatientRegistration", on_delete=models.CASCADE, related_name="consultations" + "PatientRegistration", + on_delete=models.CASCADE, + related_name="consultations", ) ip_no = models.CharField(max_length=100, default="", null=True, blank=True) @@ -101,7 +103,10 @@ class PatientConsultation(PatientBaseModel, PatientRelatedPermissionMixin): last_updated_by_telemedicine = models.BooleanField(default=False) # Deprecated assigned_to = models.ForeignKey( - User, on_delete=models.SET_NULL, null=True, related_name="patient_assigned_to" + User, + on_delete=models.SET_NULL, + null=True, + related_name="patient_assigned_to", ) verified_by = models.TextField(default="", null=True, blank=True) @@ -111,11 +116,17 @@ class PatientConsultation(PatientBaseModel, PatientRelatedPermissionMixin): ) last_edited_by = models.ForeignKey( - User, on_delete=models.SET_NULL, null=True, related_name="last_edited_user" + User, + on_delete=models.SET_NULL, + null=True, + related_name="last_edited_user", ) last_daily_round = models.ForeignKey( - "facility.DailyRound", on_delete=models.SET_NULL, null=True, default=None + "facility.DailyRound", + on_delete=models.SET_NULL, + null=True, + default=None, ) current_bed = models.ForeignKey( @@ -127,10 +138,27 @@ class PatientConsultation(PatientBaseModel, PatientRelatedPermissionMixin): ) last_health_details = models.ForeignKey( - "PatientHealthDetails", on_delete=models.SET_NULL, null=True, default=None + "PatientHealthDetails", + on_delete=models.SET_NULL, + null=True, + default=None, ) # Physical Information + height = models.FloatField( + default=None, + null=True, + verbose_name="Patient's Height in CM", + validators=[MinValueValidator(0)], + ) # deprecated + + weight = models.FloatField( + default=None, + null=True, + verbose_name="Patient's Weight in KG", + validators=[MinValueValidator(0)], + ) # deprecated + HBA1C = models.FloatField( default=None, null=True, From c629ab6e7ce2b97eb896747927249449baa4eb9c Mon Sep 17 00:00:00 2001 From: cp-Coder Date: Thu, 22 Sep 2022 19:04:51 +0530 Subject: [PATCH 35/44] Updated Migrations * Refactored the migrations * Updated migrations' dependencies * Added function to link health details with patient consultation --- .../migrations/0313_merge_20220831_1553.py | 14 ------------ .../migrations/0315_merge_20220912_1001.py | 14 ------------ ...1_0900.py => 0322_patienthealthdetails.py} | 22 ++++++++++++++----- ...20220912_2244.py => 0323_vaccine_model.py} | 2 +- ...913_2021.py => 0324_notification_model.py} | 2 +- 5 files changed, 19 insertions(+), 35 deletions(-) delete mode 100644 care/facility/migrations/0313_merge_20220831_1553.py delete mode 100644 care/facility/migrations/0315_merge_20220912_1001.py rename care/facility/migrations/{0316_auto_20220831_0900.py => 0322_patienthealthdetails.py} (90%) rename care/facility/migrations/{0317_auto_20220912_2244.py => 0323_vaccine_model.py} (98%) rename care/facility/migrations/{0318_auto_20220913_2021.py => 0324_notification_model.py} (96%) diff --git a/care/facility/migrations/0313_merge_20220831_1553.py b/care/facility/migrations/0313_merge_20220831_1553.py deleted file mode 100644 index 5fa8360c30..0000000000 --- a/care/facility/migrations/0313_merge_20220831_1553.py +++ /dev/null @@ -1,14 +0,0 @@ -# Generated by Django 2.2.11 on 2022-08-31 10:23 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('facility', '0312_patientconsultation_investigation'), - ('facility', '0311_auto_20220831_0900'), - ] - - operations = [ - ] diff --git a/care/facility/migrations/0315_merge_20220912_1001.py b/care/facility/migrations/0315_merge_20220912_1001.py deleted file mode 100644 index f5cb0dbb24..0000000000 --- a/care/facility/migrations/0315_merge_20220912_1001.py +++ /dev/null @@ -1,14 +0,0 @@ -# Generated by Django 2.2.11 on 2022-09-12 04:31 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('facility', '0314_patientconsultation_icd11_diagnoses'), - ('facility', '0313_merge_20220831_1553'), - ] - - operations = [ - ] diff --git a/care/facility/migrations/0316_auto_20220831_0900.py b/care/facility/migrations/0322_patienthealthdetails.py similarity index 90% rename from care/facility/migrations/0316_auto_20220831_0900.py rename to care/facility/migrations/0322_patienthealthdetails.py index daedd55f16..caa7e1f19a 100644 --- a/care/facility/migrations/0316_auto_20220831_0900.py +++ b/care/facility/migrations/0322_patienthealthdetails.py @@ -16,6 +16,7 @@ def populate_data(apps, schema_editor): allergies = "" blood_group = None consultation = None + facility = patient.facility weight = 0.0 height = 0.0 @@ -27,6 +28,7 @@ def populate_data(apps, schema_editor): blood_group = patient.blood_group if patient.last_consultation is not None: + facility = patient.last_consultation.facility consultation = patient.last_consultation if patient.last_consultation.weight is not None: @@ -36,7 +38,7 @@ def populate_data(apps, schema_editor): health_details_obj = health_details( patient=patient, - facility=patient.last_consultation.facility, + facility=facility, weight=weight, height=height, consultation=consultation, @@ -45,18 +47,27 @@ def populate_data(apps, schema_editor): blood_group=blood_group, ) - if patient.last_consultation is not None: - patient.last_consultation.last_health_details = health_details_obj - health_details_objs.append(health_details_obj) health_details.objects.bulk_create(health_details_objs) +def link_data(apps, schema_editor): + patient_consultation = apps.get_model("facility", "PatientConsultation") + health_details = apps.get_model("facility", "PatientHealthDetails") + patient_cons_objs = patient_consultation.objects.all() + + for patient_cons in patient_cons_objs: + patient_cons.last_health_details = health_details.objects.get( + id=patient_cons.patient.id + ) + patient_cons.save(update_fields=["last_health_details"]) + + class Migration(migrations.Migration): dependencies = [ - ("facility", "0315_merge_20220912_1001"), + ("facility", "0321_merge_20220921_2255"), ] operations = [ @@ -196,4 +207,5 @@ class Migration(migrations.Migration): ), ), migrations.RunPython(populate_data, migrations.RunPython.noop), + migrations.RunPython(link_data, migrations.RunPython.noop), ] diff --git a/care/facility/migrations/0317_auto_20220912_2244.py b/care/facility/migrations/0323_vaccine_model.py similarity index 98% rename from care/facility/migrations/0317_auto_20220912_2244.py rename to care/facility/migrations/0323_vaccine_model.py index 70234ed2e8..ce2892cd8c 100644 --- a/care/facility/migrations/0317_auto_20220912_2244.py +++ b/care/facility/migrations/0323_vaccine_model.py @@ -26,7 +26,7 @@ def create_vaccination_data(apps, schema_editor): class Migration(migrations.Migration): dependencies = [ - ("facility", "0315_merge_20220912_1001"), + ("facility", "0322_patienthealthdetails"), ] operations = [ diff --git a/care/facility/migrations/0318_auto_20220913_2021.py b/care/facility/migrations/0324_notification_model.py similarity index 96% rename from care/facility/migrations/0318_auto_20220913_2021.py rename to care/facility/migrations/0324_notification_model.py index c658935744..bcc602a7b4 100644 --- a/care/facility/migrations/0318_auto_20220913_2021.py +++ b/care/facility/migrations/0324_notification_model.py @@ -4,7 +4,7 @@ class Migration(migrations.Migration): dependencies = [ - ("facility", "0317_auto_20220912_2244"), + ("facility", "0323_vaccine_model"), ] operations = [ From ce730652f347bf3a2df072e7d0d4ae8ad1cd57b8 Mon Sep 17 00:00:00 2001 From: cp-Coder Date: Mon, 3 Oct 2022 17:24:02 +0530 Subject: [PATCH 36/44] Removed health details create/update notification --- .../migrations/0324_notification_model.py | 37 ----- care/facility/models/notification.py | 8 +- care/utils/notification_handler.py | 137 +++++------------- 3 files changed, 35 insertions(+), 147 deletions(-) delete mode 100644 care/facility/migrations/0324_notification_model.py diff --git a/care/facility/migrations/0324_notification_model.py b/care/facility/migrations/0324_notification_model.py deleted file mode 100644 index bcc602a7b4..0000000000 --- a/care/facility/migrations/0324_notification_model.py +++ /dev/null @@ -1,37 +0,0 @@ -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("facility", "0323_vaccine_model"), - ] - - operations = [ - migrations.AlterField( - model_name="notification", - name="event", - field=models.IntegerField( - choices=[ - (0, "MESSAGE"), - (20, "PATIENT_CREATED"), - (30, "PATIENT_UPDATED"), - (40, "PATIENT_DELETED"), - (42, "PATIENT_HEALTH_DETAILS_CREATED"), - (44, "PATIENT_HEALTH_DETAILS_UPDATED"), - (50, "PATIENT_CONSULTATION_CREATED"), - (60, "PATIENT_CONSULTATION_UPDATED"), - (70, "PATIENT_CONSULTATION_DELETED"), - (80, "INVESTIGATION_SESSION_CREATED"), - (90, "INVESTIGATION_UPDATED"), - (100, "PATIENT_FILE_UPLOAD_CREATED"), - (110, "CONSULTATION_FILE_UPLOAD_CREATED"), - (120, "PATIENT_CONSULTATION_UPDATE_CREATED"), - (130, "PATIENT_CONSULTATION_UPDATE_UPDATED"), - (140, "PATIENT_CONSULTATION_ASSIGNMENT"), - (200, "SHIFTING_UPDATED"), - ], - default=0, - ), - ), - ] diff --git a/care/facility/models/notification.py b/care/facility/models/notification.py index d9e114788d..a43b1a4cb8 100644 --- a/care/facility/models/notification.py +++ b/care/facility/models/notification.py @@ -1,10 +1,10 @@ import enum +from django.contrib.postgres.fields import JSONField from django.db import models from care.facility.models import FacilityBaseModel from care.users.models import User -from django.contrib.postgres.fields import JSONField class Notification(FacilityBaseModel): @@ -26,8 +26,6 @@ class Event(enum.Enum): PATIENT_CREATED = 20 PATIENT_UPDATED = 30 PATIENT_DELETED = 40 - PATIENT_HEALTH_DETAILS_CREATED = 42 - PATIENT_HEALTH_DETAILS_UPDATED = 44 PATIENT_CONSULTATION_CREATED = 50 PATIENT_CONSULTATION_UPDATED = 60 PATIENT_CONSULTATION_DELETED = 70 @@ -61,8 +59,6 @@ class Event(enum.Enum): event_type = models.IntegerField( choices=EventTypeChoices, default=EventType.SYSTEM_GENERATED.value ) - event = models.IntegerField( - choices=EventChoices, default=Event.MESSAGE.value - ) + event = models.IntegerField(choices=EventChoices, default=Event.MESSAGE.value) message = models.TextField(max_length=2000, null=True, default=None) caused_objects = JSONField(null=True, blank=True, default=dict) diff --git a/care/utils/notification_handler.py b/care/utils/notification_handler.py index f2449315f7..0a8f593b68 100644 --- a/care/utils/notification_handler.py +++ b/care/utils/notification_handler.py @@ -8,10 +8,7 @@ from care.facility.models.daily_round import DailyRound from care.facility.models.facility import Facility, FacilityUser from care.facility.models.notification import Notification -from care.facility.models.patient import ( - PatientHealthDetails, - PatientRegistration, -) +from care.facility.models.patient import PatientRegistration from care.facility.models.patient_consultation import PatientConsultation from care.facility.models.patient_investigation import ( InvestigationSession, @@ -79,9 +76,7 @@ def __init__( if notification_mediums: for medium in notification_mediums: if not isinstance(medium, Notification.Medium): - raise NotificationCreationException( - "Medium Type Invalid" - ) + raise NotificationCreationException("Medium Type Invalid") mediums.append(medium.value) data = { "event_type": event_type.value, @@ -154,19 +149,13 @@ def generate_extra_users(self): ) if isinstance(self.caused_object, InvestigationSession): if self.extra_data["consultation"].assigned_to: - self.extra_users.append( - self.extra_data["consultation"].assigned_to.id - ) + self.extra_users.append(self.extra_data["consultation"].assigned_to.id) if isinstance(self.caused_object, InvestigationValue): if self.caused_object.consultation.assigned_to: - self.extra_users.append( - self.caused_object.consultation.assigned_to.id - ) + self.extra_users.append(self.caused_object.consultation.assigned_to.id) if isinstance(self.caused_object, DailyRound): if self.caused_object.consultation.assigned_to: - self.extra_users.append( - self.caused_object.consultation.assigned_to.id - ) + self.extra_users.append(self.caused_object.consultation.assigned_to.id) def generate_system_message(self): message = "" @@ -183,67 +172,30 @@ def generate_system_message(self): message = "Patient {} was deleted by {}".format( self.caused_object.name, self.caused_by.get_full_name() ) - elif isinstance(self.caused_object, PatientHealthDetails): - if ( - self.event - == Notification.Event.PATIENT_HEALTH_DETAILS_CREATED.value - ): - message = ( - "Health Details for Patient {} was created by {}".format( - self.caused_object.patient.name, - self.caused_object.get_full_name(), - ) - ) - if ( - self.event - == Notification.Event.PATIENT_HEALTH_DETAILS_UPDATED.value - ): - message = ( - "Health Details for Patient {} was updated by {}".format( - self.caused_object.patient.name, - self.caused_object.get_full_name(), - ) - ) elif isinstance(self.caused_object, PatientConsultation): - if ( - self.event - == Notification.Event.PATIENT_CONSULTATION_CREATED.value - ): - message = ( - "Consultation for Patient {} was created by {}".format( - self.caused_object.patient.name, - self.caused_by.get_full_name(), - ) + if self.event == Notification.Event.PATIENT_CONSULTATION_CREATED.value: + message = "Consultation for Patient {} was created by {}".format( + self.caused_object.patient.name, + self.caused_by.get_full_name(), ) - elif ( - self.event - == Notification.Event.PATIENT_CONSULTATION_UPDATED.value - ): - message = ( - "Consultation for Patient {} was updated by {}".format( - self.caused_object.patient.name, - self.caused_by.get_full_name(), - ) + elif self.event == Notification.Event.PATIENT_CONSULTATION_UPDATED.value: + message = "Consultation for Patient {} was updated by {}".format( + self.caused_object.patient.name, + self.caused_by.get_full_name(), ) - if ( - self.event - == Notification.Event.PATIENT_CONSULTATION_DELETED.value - ): + if self.event == Notification.Event.PATIENT_CONSULTATION_DELETED.value: + message = "Consultation for Patient {} was deleted by {}".format( + self.caused_object.patient.name, + self.caused_by.get_full_name(), + ) + elif isinstance(self.caused_object, InvestigationSession): + if self.event == Notification.Event.INVESTIGATION_SESSION_CREATED.value: message = ( - "Consultation for Patient {} was deleted by {}".format( - self.caused_object.patient.name, + "Investigation Session for Patient {} was created by {}".format( + self.extra_data["consultation"].patient.name, self.caused_by.get_full_name(), ) ) - elif isinstance(self.caused_object, InvestigationSession): - if ( - self.event - == Notification.Event.INVESTIGATION_SESSION_CREATED.value - ): - message = "Investigation Session for Patient {} was created by {}".format( - self.extra_data["consultation"].patient.name, - self.caused_by.get_full_name(), - ) elif isinstance(self.caused_object, InvestigationValue): if self.event == Notification.Event.INVESTIGATION_UPDATED.value: message = "Investigation Value for {} for Patient {} was updated by {}".format( @@ -317,20 +269,14 @@ def _get_default_medium(self): def generate_cause_objects(self): if isinstance(self.caused_object, PatientRegistration): - self.caused_objects["patient"] = str( - self.caused_object.external_id - ) + self.caused_objects["patient"] = str(self.caused_object.external_id) if self.caused_object.facility: self.caused_objects["facility"] = str( self.caused_object.facility.external_id ) if isinstance(self.caused_object, PatientConsultation): - self.caused_objects["consultation"] = str( - self.caused_object.external_id - ) - self.caused_objects["patient"] = str( - self.caused_object.patient.external_id - ) + self.caused_objects["consultation"] = str(self.caused_object.external_id) + self.caused_objects["patient"] = str(self.caused_object.patient.external_id) if self.caused_object.patient.facility: self.caused_objects["facility"] = str( self.caused_object.patient.facility.external_id @@ -344,13 +290,9 @@ def generate_cause_objects(self): ) if self.extra_data["consultation"].patient.facility: self.caused_objects["facility"] = str( - self.extra_data[ - "consultation" - ].patient.facility.external_id + self.extra_data["consultation"].patient.facility.external_id ) - self.caused_objects["session"] = str( - self.caused_object.external_id - ) + self.caused_objects["session"] = str(self.caused_object.external_id) if isinstance(self.caused_object, InvestigationValue): self.caused_objects["consultation"] = str( self.caused_object.consultation.external_id @@ -362,9 +304,7 @@ def generate_cause_objects(self): self.caused_objects["facility"] = str( self.caused_object.consultation.patient.facility.external_id ) - self.caused_objects["session"] = str( - self.caused_object.session.external_id - ) + self.caused_objects["session"] = str(self.caused_object.session.external_id) self.caused_objects["investigation"] = str( self.caused_object.investigation.external_id ) @@ -381,17 +321,12 @@ def generate_cause_objects(self): self.caused_object.consultation.facility.external_id ) if isinstance(self.caused_object, ShiftingRequest): - self.caused_objects["shifting"] = str( - self.caused_object.external_id - ) + self.caused_objects["shifting"] = str(self.caused_object.external_id) return True def generate_whatsapp_users(self): - if ( - self.event - == Notification.Event.PATIENT_CONSULTATION_ASSIGNMENT.value - ): + if self.event == Notification.Event.PATIENT_CONSULTATION_ASSIGNMENT.value: return [self.caused_object.assigned_to] raise Exception("Action Does not have associated users") @@ -399,9 +334,7 @@ def generate_system_users(self): users = [] extra_users = self.extra_users caused_user = self.caused_by - facility_users = FacilityUser.objects.filter( - facility_id=self.facility.id - ) + facility_users = FacilityUser.objects.filter(facility_id=self.facility.id) for facility_user in facility_users: if facility_user.user.id != caused_user.id: users.append(facility_user.user) @@ -478,9 +411,7 @@ def generate(self): user, json.dumps( { - "external_id": str( - notification_obj.external_id - ), + "external_id": str(notification_obj.external_id), "title": self.message, } ), @@ -495,6 +426,4 @@ def generate(self): notification_obj = self.generate_message_for_user( user, message, Notification.Medium.WHATSAPP.value ) - sendWhatsappMessage( - number, message, notification_obj.external_id - ) + sendWhatsappMessage(number, message, notification_obj.external_id) From 2514012fe99059b21087797ac3cbedc429a61482 Mon Sep 17 00:00:00 2001 From: cp-Coder Date: Mon, 3 Oct 2022 17:25:09 +0530 Subject: [PATCH 37/44] Removed medical history API * Reverted back to medical history used * Removed new MedicalHistory and Disease model * Reformatted the code --- care/facility/api/serializers/patient.py | 23 +++---- care/facility/models/patient.py | 80 ++++++++++-------------- 2 files changed, 42 insertions(+), 61 deletions(-) diff --git a/care/facility/api/serializers/patient.py b/care/facility/api/serializers/patient.py index a89be7a950..da2be7bdaa 100644 --- a/care/facility/api/serializers/patient.py +++ b/care/facility/api/serializers/patient.py @@ -25,10 +25,7 @@ PatientSearch, ) from care.facility.models.notification import Notification -from care.facility.models.patient_base import ( - DISEASE_STATUS_CHOICES, - DiseaseStatusEnum, -) +from care.facility.models.patient_base import DISEASE_STATUS_CHOICES, DiseaseStatusEnum from care.facility.models.patient_consultation import PatientConsultation from care.facility.models.patient_external_test import PatientExternalTest from care.facility.models.patient_tele_consultation import PatientTeleConsultation @@ -71,7 +68,8 @@ class PatientListSerializer(serializers.ModelSerializer): last_consultation = PatientConsultationSerializer(read_only=True) disease_status = ChoiceField( - choices=DISEASE_STATUS_CHOICES, default=DiseaseStatusEnum.SUSPECTED.value + choices=DISEASE_STATUS_CHOICES, + default=DiseaseStatusEnum.SUSPECTED.value, ) source = ChoiceField(choices=PatientRegistration.SourceChoices) @@ -150,7 +148,8 @@ class Meta: default=PatientRegistration.SourceEnum.CARE.value, ) disease_status = ChoiceField( - choices=DISEASE_STATUS_CHOICES, default=DiseaseStatusEnum.SUSPECTED.value + choices=DISEASE_STATUS_CHOICES, + default=DiseaseStatusEnum.SUSPECTED.value, ) meta_info = PatientMetaInfoSerializer(required=False, allow_null=True) @@ -183,7 +182,11 @@ class Meta: "external_id", ) include = ("contacted_patients",) - read_only = TIMESTAMP_FIELDS + ("last_edited", "created_by", "is_active") + read_only = TIMESTAMP_FIELDS + ( + "last_edited", + "created_by", + "is_active", + ) # def get_last_consultation(self, obj): # last_consultation = PatientConsultation.objects.filter(patient=obj).last() @@ -214,12 +217,6 @@ def validate(self, attrs): {"non_field_errors": ["Either age or date_of_birth should be passed"]} ) - if validated.get("is_vaccinated"): - if validated.get("number_of_doses") == 0: - raise serializers.ValidationError("Number of doses cannot be 0") - if validated.get("vaccine_name") is None: - raise serializers.ValidationError("Vaccine name cannot be null") - return validated def check_external_entry(self, srf_id): diff --git a/care/facility/models/patient.py b/care/facility/models/patient.py index 54d65a6cab..7f341eb521 100644 --- a/care/facility/models/patient.py +++ b/care/facility/models/patient.py @@ -184,13 +184,13 @@ class TestTypeEnum(enum.Enum): default="", blank=True, verbose_name="Patient's Current Health Details", - ) # deprecated + ) ongoing_medication = models.TextField( default="", blank=True, verbose_name="Already pescribed medication if any", - ) # deprecated + ) has_SARI = models.BooleanField( default=False, verbose_name="Does the Patient Suffer from SARI" @@ -546,8 +546,8 @@ def save(self, *args, **kwargs) -> None: "countries_travelled": "Countries Patient has Travelled to", "date_of_return": "Return Date from the Last Country if Travelled", "is_migrant_worker": "Is the Patient a Migrant Worker", - # "present_health": "Patient's Current Health Details", - # "ongoing_medication": "Already pescribed medication if any", + "present_health": "Patient's Current Health Details", + "ongoing_medication": "Already pescribed medication if any", "has_SARI": "Does the Patient Suffer from SARI", "date_of_receipt_of_information": "Patient's information received date", "will_donate_blood": "Will Patient Donate Blood?", @@ -723,6 +723,34 @@ class ModeOfContactEnum(enum.IntEnum): objects = BaseManager() +class Disease(models.Model): + patient = models.ForeignKey( + PatientRegistration, + on_delete=models.CASCADE, + related_name="medical_history", + ) + disease = models.IntegerField(choices=DISEASE_CHOICES) + details = models.TextField(blank=True, null=True) + deleted = models.BooleanField(default=False) + + objects = BaseManager() + + class Meta: + indexes = [ + PartialIndex( + fields=["patient", "disease"], + unique=True, + where=PQ(deleted=False), + ) + ] + + def __str__(self): + return self.patient.name + " - " + self.get_disease_display() + + def get_disease_display(self): + return DISEASE_CHOICES[self.disease - 1][1] + + class FacilityPatientStatsHistory(FacilityBaseModel, FacilityRelatedPermissionMixin): facility = models.ForeignKey("Facility", on_delete=models.PROTECT) entry_date = models.DateField() @@ -856,47 +884,3 @@ class Meta: def __str__(self): return self.health_details.patient.name + " - " + self.vaccine - - -class MedicalHistory(PatientBaseModel, PatientRelatedPermissionMixin): - patient = models.ForeignKey( - PatientRegistration, - on_delete=models.CASCADE, - ) - present_health = models.TextField( - default="", blank=True, verbose_name="Patient's Current Health Details" - ) - ongoing_medication = models.TextField( - default="", - blank=True, - verbose_name="Already pescribed medication if any", - ) - - -class Disease(models.Model): - medical_history = models.ForeignKey( - MedicalHistory, - on_delete=models.CASCADE, - related_name="medical_history", - ) - disease_date = models.DateField(verbose_name="Disease Date") - disease = models.IntegerField(choices=DISEASE_CHOICES) - details = models.TextField(blank=True, null=True) - deleted = models.BooleanField(default=False) - - objects = BaseManager() - - class Meta: - indexes = [ - PartialIndex( - fields=["medical_history", "disease"], - unique=True, - where=PQ(deleted=False), - ) - ] - - def __str__(self): - return self.medical_history.patient.name + " - " + self.get_disease_display() - - def get_disease_display(self): - return DISEASE_CHOICES[self.disease - 1][1] From 26f58bf97070d2454396e355fd4c6296234f157b Mon Sep 17 00:00:00 2001 From: cp-Coder Date: Mon, 3 Oct 2022 17:27:58 +0530 Subject: [PATCH 38/44] Updated the logic of Health Details API * Switched to serializer to create/update health details record * Added create/update method in health details serializer * Added auth while creating health details record * Switched to validated_data for health details data * Reformatted the code --- .../api/serializers/patient_consultation.py | 188 ++++++++---------- .../api/serializers/patient_health_details.py | 33 ++- 2 files changed, 110 insertions(+), 111 deletions(-) diff --git a/care/facility/api/serializers/patient_consultation.py b/care/facility/api/serializers/patient_consultation.py index a654999823..525ddfa447 100644 --- a/care/facility/api/serializers/patient_consultation.py +++ b/care/facility/api/serializers/patient_consultation.py @@ -8,17 +8,12 @@ from care.facility.api.serializers.bed import ConsultationBedSerializer from care.facility.api.serializers.daily_round import DailyRoundSerializer from care.facility.api.serializers.facility import FacilityBasicInfoSerializer -from care.facility.models import ( - CATEGORY_CHOICES, - COVID_CATEGORY_CHOICES, - Facility, - PatientRegistration, -) from care.facility.api.serializers.patient_health_details import ( PatientHealthDetailsSerializer, ) from care.facility.models import ( CATEGORY_CHOICES, + COVID_CATEGORY_CHOICES, Facility, PatientRegistration, ) @@ -44,9 +39,7 @@ class PatientConsultationSerializer(serializers.ModelSerializer): id = serializers.CharField(source="external_id", read_only=True) - facility_name = serializers.CharField( - source="facility.name", read_only=True - ) + facility_name = serializers.CharField(source="facility.name", read_only=True) suggestion_text = ChoiceField( choices=PatientConsultation.SUGGESTION_CHOICES, read_only=True, @@ -65,14 +58,10 @@ class PatientConsultationSerializer(serializers.ModelSerializer): referred_to = ExternalIdSerializerField( queryset=Facility.objects.all(), required=False ) - patient = ExternalIdSerializerField( - queryset=PatientRegistration.objects.all() - ) + patient = ExternalIdSerializerField(queryset=PatientRegistration.objects.all()) facility = ExternalIdSerializerField(read_only=True) - assigned_to_object = UserAssignedSerializer( - source="assigned_to", read_only=True - ) + assigned_to_object = UserAssignedSerializer(source="assigned_to", read_only=True) assigned_to = serializers.PrimaryKeyRelatedField( queryset=User.objects.all(), required=False, allow_null=True @@ -89,14 +78,21 @@ class PatientConsultationSerializer(serializers.ModelSerializer): required=False, ) - review_time = serializers.IntegerField( - default=-1, write_only=True, required=False - ) + review_time = serializers.IntegerField(default=-1, write_only=True, required=False) last_edited_by = UserBaseMinimumSerializer(read_only=True) created_by = UserBaseMinimumSerializer(read_only=True) last_daily_round = DailyRoundSerializer(read_only=True) - last_health_details = PatientHealthDetailsSerializer(read_only=True) + health_details = ExternalIdSerializerField( + queryset=PatientHealthDetails.objects.all(), required=False + ) + + health_details_object = PatientHealthDetailsSerializer( + source="last_health_details", + read_only=True, + ) + + new_health_details = PatientHealthDetailsSerializer(required=False) current_bed = ConsultationBedSerializer(read_only=True) @@ -121,9 +117,7 @@ def get_icd11_diagnoses_objects_by_ids(self, diagnoses_ids): return diagnosis_objects def get_icd11_diagnoses_object(self, consultation): - return self.get_icd11_diagnoses_objects_by_ids( - consultation.icd11_diagnoses - ) + return self.get_icd11_diagnoses_objects_by_ids(consultation.icd11_diagnoses) def get_icd11_provisional_diagnoses_object(self, consultation): return self.get_icd11_diagnoses_objects_by_ids( @@ -139,7 +133,7 @@ class Meta: "created_by", "kasp_enabled_date", ) - exclude = ("deleted", "external_id") + exclude = ("deleted", "external_id", "last_health_details") def validate_bed_number(self, bed_number): try: @@ -155,11 +149,7 @@ def update(self, instance, validated_data): if instance.discharge_date: raise ValidationError( - { - "consultation": [ - "Discharged Consultation data cannot be updated" - ] - } + {"consultation": ["Discharged Consultation data cannot be updated"]} ) if instance.suggestion == SuggestionChoices.OP: @@ -190,52 +180,44 @@ def update(self, instance, validated_data): validated_data["kasp_enabled_date"] = localtime(now()) _temp = instance.assigned_to + health_details_data = validated_data.pop("new_health_details", None) consultation = super().update(instance, validated_data) try: - health_details_data = self.context["request"].data[ - "new_health_details" - ] - vaccination_history = health_details_data.pop( - "vaccination_history", [] + vaccination_history = health_details_data.pop("vaccination_history", []) + + serializer = PatientHealthDetailsSerializer( + data={ + "patient": consultation.patient.external_id.hex, + "facility": consultation.facility.external_id.hex, + "consultation": consultation.external_id.hex, + **health_details_data, + }, + context={ + "request": self.context["request"], + }, ) - PatientHealthDetails.objects.filter( - id=consultation.last_health_details.id - ).update(**self.context["request"].data["new_health_details"]) + serializer.is_valid(raise_exception=True) + serializer.save() - consultation.last_health_details = ( - PatientHealthDetails.objects.get( - id=consultation.last_health_details.id - ) - ) + health_details = PatientHealthDetails.objects.filter( + consultation=consultation + ).latest("external_id") + + consultation.health_details = health_details vaccines = [] for vaccine in vaccination_history: - vaccines.append( - Vaccine( - health_details=consultation.last_health_details, - **vaccine, - ) - ) + vaccines.append(Vaccine(health_details=health_details, **vaccine)) if vaccines: Vaccine.objects.bulk_create(vaccines, ignore_conflicts=True) - consultation.save(update_fields=["last_health_details"]) - NotificationGenerator( - event=Notification.Event.PATIENT_HEALTH_DETAILS_UPDATED, - caused_by=self.context["request"].user, - caused_object=consultation.last_health_details, - facility=consultation.facility, - ).generate() except KeyError: pass if "assigned_to" in validated_data: - if ( - validated_data["assigned_to"] != _temp - and validated_data["assigned_to"] - ): + if validated_data["assigned_to"] != _temp and validated_data["assigned_to"]: NotificationGenerator( event=Notification.Event.PATIENT_CONSULTATION_ASSIGNMENT, caused_by=self.context["request"].user, @@ -267,16 +249,12 @@ def create(self, validated_data): # Authorisation Check - allowed_facilities = get_home_facility_queryset( - self.context["request"].user - ) + allowed_facilities = get_home_facility_queryset(self.context["request"].user) if not allowed_facilities.filter( id=self.validated_data["patient"].facility.id ).exists(): raise ValidationError( - { - "facility": "Consultation creates are only allowed in home facility" - } + {"facility": "Consultation creates are only allowed in home facility"} ) # End Authorisation Checks @@ -295,9 +273,7 @@ def create(self, validated_data): if validated_data["patient"].last_consultation: if not validated_data["patient"].last_consultation.discharge_date: raise ValidationError( - { - "consultation": "Exists please Edit Existing Consultation" - } + {"consultation": "Exists please Edit Existing Consultation"} ) if "is_kasp" in validated_data: @@ -305,6 +281,7 @@ def create(self, validated_data): validated_data["kasp_enabled_date"] = localtime(now()) bed = validated_data.pop("bed", None) + health_details_data = validated_data.pop("new_health_details", None) validated_data["facility_id"] = validated_data[ "patient" @@ -315,47 +292,50 @@ def create(self, validated_data): consultation.save() try: - health_details_data = self.context["request"].data[ - "new_health_details" - ] - vaccination_history = health_details_data.pop( - "vaccination_history", [] + vaccination_history = health_details_data.pop("vaccination_history", []) + + serializer = PatientHealthDetailsSerializer( + data={ + "patient": consultation.patient.external_id.hex, + "facility": consultation.facility.external_id.hex, + "consultation": consultation.external_id.hex, + **health_details_data, + }, + context={ + "request": self.context["request"], + }, ) + serializer.is_valid(raise_exception=True) + serializer.save() - health_details = PatientHealthDetails( - patient=consultation.patient, - facility=consultation.facility, - consultation=consultation, - **health_details_data, - ) - health_details.save() + health_details = PatientHealthDetails.objects.filter( + consultation=consultation + ).latest("external_id") + + consultation.health_details = health_details vaccines = [] for vaccine in vaccination_history: - vaccines.append( - Vaccine(health_details=health_details, **vaccine) - ) + vaccines.append(Vaccine(health_details=health_details, **vaccine)) if vaccines: Vaccine.objects.bulk_create(vaccines, ignore_conflicts=True) - consultation.last_health_details = health_details - consultation.save(update_fields=["last_health_details"]) - - NotificationGenerator( - event=Notification.Event.PATIENT_HEALTH_DETAILS_CREATED, - caused_by=self.context["request"].user, - caused_object=health_details, - facility=consultation.facility, - ).generate() except KeyError as error: if consultation.patient.last_consultation is None: raise ValidationError( { - "last_health_details": [ + "health_details": [ "Please provide the health details of the patient" ] } ) from error + + else: + consultation.last_health_details = ( + consultation.patient.last_consultation.last_health_details + ) + consultation.save(update_fields=["last_health_details"]) + if bed: consultation_bed = ConsultationBed( bed=bed, @@ -379,9 +359,7 @@ def create(self, validated_data): if action != -1: patient.action = action if review_time > 0: - patient.review_time = localtime(now()) + timedelta( - minutes=review_time - ) + patient.review_time = localtime(now()) + timedelta(minutes=review_time) patient.save() NotificationGenerator( @@ -410,9 +388,9 @@ def validate(self, attrs): # TODO Add Bed Authorisation Validation if "suggestion" in validated: - if validated[ - "suggestion" - ] is SuggestionChoices.R and not validated.get("referred_to"): + if validated["suggestion"] is SuggestionChoices.R and not validated.get( + "referred_to" + ): raise ValidationError( { "referred_to": [ @@ -445,11 +423,7 @@ def validate(self, attrs): ) if validated["review_time"] <= 0: raise ValidationError( - { - "review_time": [ - "This field value is must be greater than 0." - ] - } + {"review_time": ["This field value is must be greater than 0."]} ) from care.facility.static_data.icd11 import ICDDiseases @@ -483,12 +457,8 @@ def validate(self, attrs): class PatientConsultationIDSerializer(serializers.ModelSerializer): - consultation_id = serializers.UUIDField( - source="external_id", read_only=True - ) - patient_id = serializers.UUIDField( - source="patient.external_id", read_only=True - ) + consultation_id = serializers.UUIDField(source="external_id", read_only=True) + patient_id = serializers.UUIDField(source="patient.external_id", read_only=True) class Meta: model = PatientConsultation diff --git a/care/facility/api/serializers/patient_health_details.py b/care/facility/api/serializers/patient_health_details.py index 95d9bcdadd..adf1e09a22 100644 --- a/care/facility/api/serializers/patient_health_details.py +++ b/care/facility/api/serializers/patient_health_details.py @@ -9,6 +9,7 @@ from care.facility.models.facility import Facility from care.facility.models.patient import Vaccine from care.facility.models.patient_base import BLOOD_GROUP_CHOICES, VACCINE_CHOICES +from care.utils.queryset.facility import get_home_facility_queryset from care.utils.serializer.external_id_field import ExternalIdSerializerField @@ -30,8 +31,12 @@ class PatientHealthDetailsSerializer(serializers.ModelSerializer): id = serializers.CharField(source="external_id", read_only=True) - patient = ExternalIdSerializerField(queryset=PatientRegistration.objects.all()) - facility = ExternalIdSerializerField(queryset=Facility.objects.all()) + patient = ExternalIdSerializerField( + queryset=PatientRegistration.objects.all(), required=False + ) + facility = ExternalIdSerializerField( + queryset=Facility.objects.all(), required=False + ) consultation = ExternalIdSerializerField( queryset=PatientConsultation.objects.all(), required=False ) @@ -44,3 +49,27 @@ class PatientHealthDetailsSerializer(serializers.ModelSerializer): class Meta: model = PatientHealthDetails exclude = ("deleted", "external_id") + + def create(self, validated_data): + consultation = validated_data["consultation"] + allowed_facilities = get_home_facility_queryset(self.context["request"].user) + if not allowed_facilities.filter( + id=self.validated_data["patient"].facility.id + ).exists(): + raise serializers.ValidationError( + { + "patient": "Patient Health Details creates are only allowed in home facility" + } + ) + + health_details = super().create(validated_data) + consultation.last_health_details = health_details + consultation.save(update_fields=["last_health_details"]) + return health_details + + def update(self, instance, validated_data): + consultation = validated_data["consultation"] + health_details = super().update(instance, validated_data) + consultation.last_health_details = health_details + consultation.save(update_fields=["last_health_details"]) + return health_details From 689107e9fd6f05ad6a65cf9f18d9ee4365729ee6 Mon Sep 17 00:00:00 2001 From: cp-Coder Date: Wed, 5 Oct 2022 17:15:01 +0530 Subject: [PATCH 39/44] Updated Vaccination field of health details API * Added vaccination in health details model as jsonfield * Added schema for vaccintion field * Removed vaccine model and its references --- .../api/serializers/patient_consultation.py | 55 +++------- .../api/serializers/patient_health_details.py | 21 +--- .../migrations/0322_patienthealthdetails.py | 44 ++++++++ .../facility/migrations/0323_vaccine_model.py | 103 ------------------ .../models/json_schema/consultation.py | 23 +++- care/facility/models/patient.py | 46 ++------ 6 files changed, 90 insertions(+), 202 deletions(-) delete mode 100644 care/facility/migrations/0323_vaccine_model.py diff --git a/care/facility/api/serializers/patient_consultation.py b/care/facility/api/serializers/patient_consultation.py index 525ddfa447..91b1019d43 100644 --- a/care/facility/api/serializers/patient_consultation.py +++ b/care/facility/api/serializers/patient_consultation.py @@ -19,7 +19,7 @@ ) from care.facility.models.bed import Bed, ConsultationBed from care.facility.models.notification import Notification -from care.facility.models.patient import PatientHealthDetails, Vaccine +from care.facility.models.patient import PatientHealthDetails from care.facility.models.patient_base import ( DISCHARGE_REASON_CHOICES, SYMPTOM_CHOICES, @@ -84,7 +84,9 @@ class PatientConsultationSerializer(serializers.ModelSerializer): created_by = UserBaseMinimumSerializer(read_only=True) last_daily_round = DailyRoundSerializer(read_only=True) health_details = ExternalIdSerializerField( - queryset=PatientHealthDetails.objects.all(), required=False + queryset=PatientHealthDetails.objects.all(), + source="last_health_details", + required=False, ) health_details_object = PatientHealthDetailsSerializer( @@ -144,7 +146,6 @@ def validate_bed_number(self, bed_number): return bed_number def update(self, instance, validated_data): - instance.last_edited_by = self.context["request"].user if instance.discharge_date: @@ -185,9 +186,12 @@ def update(self, instance, validated_data): consultation = super().update(instance, validated_data) try: - vaccination_history = health_details_data.pop("vaccination_history", []) + last_health_details_object = PatientHealthDetails.objects.get( + consultation=consultation + ) serializer = PatientHealthDetailsSerializer( + last_health_details_object, data={ "patient": consultation.patient.external_id.hex, "facility": consultation.facility.external_id.hex, @@ -201,18 +205,6 @@ def update(self, instance, validated_data): serializer.is_valid(raise_exception=True) serializer.save() - health_details = PatientHealthDetails.objects.filter( - consultation=consultation - ).latest("external_id") - - consultation.health_details = health_details - - vaccines = [] - for vaccine in vaccination_history: - vaccines.append(Vaccine(health_details=health_details, **vaccine)) - if vaccines: - Vaccine.objects.bulk_create(vaccines, ignore_conflicts=True) - except KeyError: pass @@ -292,8 +284,6 @@ def create(self, validated_data): consultation.save() try: - vaccination_history = health_details_data.pop("vaccination_history", []) - serializer = PatientHealthDetailsSerializer( data={ "patient": consultation.patient.external_id.hex, @@ -308,18 +298,6 @@ def create(self, validated_data): serializer.is_valid(raise_exception=True) serializer.save() - health_details = PatientHealthDetails.objects.filter( - consultation=consultation - ).latest("external_id") - - consultation.health_details = health_details - - vaccines = [] - for vaccine in vaccination_history: - vaccines.append(Vaccine(health_details=health_details, **vaccine)) - if vaccines: - Vaccine.objects.bulk_create(vaccines, ignore_conflicts=True) - except KeyError as error: if consultation.patient.last_consultation is None: raise ValidationError( @@ -330,11 +308,10 @@ def create(self, validated_data): } ) from error - else: - consultation.last_health_details = ( - consultation.patient.last_consultation.last_health_details - ) - consultation.save(update_fields=["last_health_details"]) + consultation.last_health_details = ( + consultation.patient.last_consultation.last_health_details + ) + consultation.save(update_fields=["last_health_details"]) if bed: consultation_bed = ConsultationBed( @@ -431,27 +408,27 @@ def validate(self, attrs): for diagnosis in validated["icd11_diagnoses"]: try: ICDDiseases.by.id[diagnosis] - except BaseException: + except BaseException as error: raise ValidationError( { "icd11_diagnoses": [ f"{diagnosis} is not a valid ICD 11 Diagnosis ID" ] } - ) + ) from error if "icd11_provisional_diagnoses" in validated: for diagnosis in validated["icd11_provisional_diagnoses"]: try: ICDDiseases.by.id[diagnosis] - except BaseException: + except BaseException as error: raise ValidationError( { "icd11_provisional_diagnoses": [ f"{diagnosis} is not a valid ICD 11 Diagnosis ID" ] } - ) + ) from error return validated diff --git a/care/facility/api/serializers/patient_health_details.py b/care/facility/api/serializers/patient_health_details.py index adf1e09a22..c8fdd7534a 100644 --- a/care/facility/api/serializers/patient_health_details.py +++ b/care/facility/api/serializers/patient_health_details.py @@ -7,26 +7,11 @@ PatientRegistration, ) from care.facility.models.facility import Facility -from care.facility.models.patient import Vaccine -from care.facility.models.patient_base import BLOOD_GROUP_CHOICES, VACCINE_CHOICES +from care.facility.models.patient_base import BLOOD_GROUP_CHOICES from care.utils.queryset.facility import get_home_facility_queryset from care.utils.serializer.external_id_field import ExternalIdSerializerField -class VaccinationHistorySerializer(serializers.ModelSerializer): - vaccine = serializers.ChoiceField(choices=VACCINE_CHOICES) - doses = serializers.IntegerField(required=False, default=0) - last_vaccinated_date = serializers.DateField() - - class Meta: - model = Vaccine - fields = ( - "vaccine", - "doses", - "last_vaccinated_date", - ) - - class PatientHealthDetailsSerializer(serializers.ModelSerializer): id = serializers.CharField(source="external_id", read_only=True) @@ -42,10 +27,6 @@ class PatientHealthDetailsSerializer(serializers.ModelSerializer): ) blood_group = ChoiceField(choices=BLOOD_GROUP_CHOICES, required=True) - vaccination_history = serializers.ListSerializer( - child=VaccinationHistorySerializer(), required=False - ) - class Meta: model = PatientHealthDetails exclude = ("deleted", "external_id") diff --git a/care/facility/migrations/0322_patienthealthdetails.py b/care/facility/migrations/0322_patienthealthdetails.py index caa7e1f19a..8667516ab0 100644 --- a/care/facility/migrations/0322_patienthealthdetails.py +++ b/care/facility/migrations/0322_patienthealthdetails.py @@ -3,6 +3,8 @@ import django.db.models.deletion import django.core.validators import care.facility.models.mixins.permissions.patient +import django.contrib.postgres.fields.jsonb +import care.utils.models.validators def populate_data(apps, schema_editor): @@ -45,6 +47,13 @@ def populate_data(apps, schema_editor): has_allergy=has_allergy, allergies=allergies, blood_group=blood_group, + vaccination_history=[ + { + "vaccine": patient.vaccine_name, + "doses": patient.number_of_doses, + "last_vaccinated_date": patient.last_vaccinated_date, + } + ], ) health_details_objs.append(health_details_obj) @@ -187,6 +196,41 @@ class Migration(migrations.Migration): verbose_name="Patient's Height in CM", ), ), + ( + "vaccination_history", + django.contrib.postgres.fields.jsonb.JSONField( + default=list, + validators=[ + care.utils.models.validators.JSONFieldSchemaValidator( + { + "$schema": "http://json-schema.org/draft-07/schema#", + "items": [ + { + "additionalProperties": False, + "properties": { + "doses": { + "default": 0, + "type": "number", + }, + "last_vaccinated_date": { + "type": "string" + }, + "vaccine": {"type": "string"}, + }, + "required": [ + "vaccine", + "doses", + "last_vaccinated_date", + ], + "type": "object", + } + ], + "type": "array", + } + ) + ], + ), + ), ], options={ "abstract": False, diff --git a/care/facility/migrations/0323_vaccine_model.py b/care/facility/migrations/0323_vaccine_model.py deleted file mode 100644 index ce2892cd8c..0000000000 --- a/care/facility/migrations/0323_vaccine_model.py +++ /dev/null @@ -1,103 +0,0 @@ -import django.core.validators -from django.db import migrations, models -import django.db.models.deletion -import partial_index - - -def create_vaccination_data(apps, schema_editor): - patient_health_details = apps.get_model("facility", "PatientHealthDetails") - vaccine = apps.get_model("facility", "Vaccine") - health_details_objs = patient_health_details.objects.all() - vaccine_objs = [] - - for health_detail in health_details_objs: - vaccine_objs.append( - vaccine( - health_details=health_detail, - vaccine=health_detail.patient.vaccine_name, - doses=health_detail.patient.number_of_doses, - last_vaccinated_date=health_detail.patient.last_vaccinated_date, - ) - ) - - vaccine.objects.bulk_create(vaccine_objs) - - -class Migration(migrations.Migration): - - dependencies = [ - ("facility", "0322_patienthealthdetails"), - ] - - operations = [ - migrations.CreateModel( - name="Vaccine", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "vaccine", - models.CharField( - choices=[ - ("CoviShield", "COVISHIELD"), - ("Covaxin", "COVAXIN"), - ("Sputnik", "SPUTNIK"), - ("Moderna", "MODERNA"), - ("Pfizer", "PFIZER"), - ("Janssen", "JANSSEN"), - ("Sinovac", "SINOVAC"), - ], - default=None, - max_length=20, - null=True, - ), - ), - ( - "doses", - models.PositiveIntegerField( - default=0, - validators=[ - django.core.validators.MinValueValidator(0), - django.core.validators.MaxValueValidator(3), - ], - ), - ), - ( - "last_vaccinated_date", - models.DateField( - blank=True, - null=True, - verbose_name="Date Last Vaccinated", - ), - ), - ("deleted", models.BooleanField(default=False)), - ( - "health_details", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="vaccination_history", - to="facility.PatientHealthDetails", - ), - ), - ], - ), - migrations.AddIndex( - model_name="vaccine", - index=partial_index.PartialIndex( - fields=["health_details", "vaccine"], - name="facility_va_health__0b97b3_partial", - unique=True, - where=partial_index.PQ(deleted=False), - ), - ), - migrations.RunPython( - create_vaccination_data, migrations.RunPython.noop - ), - ] diff --git a/care/facility/models/json_schema/consultation.py b/care/facility/models/json_schema/consultation.py index 95caa25db4..812e252956 100644 --- a/care/facility/models/json_schema/consultation.py +++ b/care/facility/models/json_schema/consultation.py @@ -1,7 +1,7 @@ from care.facility.models.json_schema.common import DATETIME_REGEX LINES_CATHETERS = { - "$schema": f"http://json-schema.org/draft-07/schema#", + "$schema": "http://json-schema.org/draft-07/schema#", "type": "array", "items": [ { @@ -17,3 +17,24 @@ } ], } + +VACCINATION = { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "array", + "items": [ + { + "type": "object", + "properties": { + "vaccine": {"type": "string"}, + "doses": {"type": "number", "default": 0}, + "last_vaccinated_date": {"type": "string"}, + }, + "additionalProperties": False, + "required": [ + "vaccine", + "doses", + "last_vaccinated_date", + ], + } + ], +} diff --git a/care/facility/models/patient.py b/care/facility/models/patient.py index 7f341eb521..26e9398004 100644 --- a/care/facility/models/patient.py +++ b/care/facility/models/patient.py @@ -1,6 +1,7 @@ import datetime import enum +from django.contrib.postgres.fields import JSONField as JsonField from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models from fernet_fields import EncryptedCharField, EncryptedIntegerField @@ -19,6 +20,7 @@ Ward, pretty_boolean, ) +from care.facility.models.json_schema.consultation import VACCINATION from care.facility.models.mixins.permissions.facility import ( FacilityRelatedPermissionMixin, ) @@ -45,6 +47,7 @@ ) from care.utils.models.base import BaseManager, BaseModel from care.utils.models.jsonfield import JSONField +from care.utils.models.validators import JSONFieldSchemaValidator class PatientRegistration(PatientBaseModel, PatientPermissionMixin): @@ -832,6 +835,10 @@ class PatientHealthDetails(PatientBaseModel, PatientRelatedPermissionMixin): null=True, ) + vaccination_history = JsonField( + default=dict, validators=[JSONFieldSchemaValidator(VACCINATION)] + ) + height = models.FloatField( default=None, null=True, @@ -845,42 +852,3 @@ class PatientHealthDetails(PatientBaseModel, PatientRelatedPermissionMixin): verbose_name="Patient's Weight in KG", validators=[MinValueValidator(0)], ) - - -class Vaccine(models.Model): - health_details = models.ForeignKey( - PatientHealthDetails, - on_delete=models.CASCADE, - related_name="vaccination_history", - ) - vaccine = models.CharField( - choices=VACCINE_CHOICES, - default=None, - null=True, - blank=False, - max_length=20, - ) - doses = models.PositiveIntegerField( - default=0, - null=False, - blank=False, - validators=[MinValueValidator(0), MaxValueValidator(3)], - ) - last_vaccinated_date = models.DateField( - null=True, blank=True, verbose_name="Date Last Vaccinated" - ) - - deleted = models.BooleanField(default=False) - objects = BaseManager() - - class Meta: - indexes = [ - PartialIndex( - fields=["health_details", "vaccine"], - unique=True, - where=PQ(deleted=False), - ) - ] - - def __str__(self): - return self.health_details.patient.name + " - " + self.vaccine From 75d5d0c118da11c58473683022131f26f2ad478b Mon Sep 17 00:00:00 2001 From: cp-Coder Date: Wed, 5 Oct 2022 17:24:11 +0530 Subject: [PATCH 40/44] Updated value of health_details field in consultation API --- .../api/serializers/patient_consultation.py | 70 ++++++++++++++----- 1 file changed, 52 insertions(+), 18 deletions(-) diff --git a/care/facility/api/serializers/patient_consultation.py b/care/facility/api/serializers/patient_consultation.py index 91b1019d43..6bead9ea92 100644 --- a/care/facility/api/serializers/patient_consultation.py +++ b/care/facility/api/serializers/patient_consultation.py @@ -39,7 +39,9 @@ class PatientConsultationSerializer(serializers.ModelSerializer): id = serializers.CharField(source="external_id", read_only=True) - facility_name = serializers.CharField(source="facility.name", read_only=True) + facility_name = serializers.CharField( + source="facility.name", read_only=True + ) suggestion_text = ChoiceField( choices=PatientConsultation.SUGGESTION_CHOICES, read_only=True, @@ -58,10 +60,14 @@ class PatientConsultationSerializer(serializers.ModelSerializer): referred_to = ExternalIdSerializerField( queryset=Facility.objects.all(), required=False ) - patient = ExternalIdSerializerField(queryset=PatientRegistration.objects.all()) + patient = ExternalIdSerializerField( + queryset=PatientRegistration.objects.all() + ) facility = ExternalIdSerializerField(read_only=True) - assigned_to_object = UserAssignedSerializer(source="assigned_to", read_only=True) + assigned_to_object = UserAssignedSerializer( + source="assigned_to", read_only=True + ) assigned_to = serializers.PrimaryKeyRelatedField( queryset=User.objects.all(), required=False, allow_null=True @@ -78,7 +84,9 @@ class PatientConsultationSerializer(serializers.ModelSerializer): required=False, ) - review_time = serializers.IntegerField(default=-1, write_only=True, required=False) + review_time = serializers.IntegerField( + default=-1, write_only=True, required=False + ) last_edited_by = UserBaseMinimumSerializer(read_only=True) created_by = UserBaseMinimumSerializer(read_only=True) @@ -119,7 +127,9 @@ def get_icd11_diagnoses_objects_by_ids(self, diagnoses_ids): return diagnosis_objects def get_icd11_diagnoses_object(self, consultation): - return self.get_icd11_diagnoses_objects_by_ids(consultation.icd11_diagnoses) + return self.get_icd11_diagnoses_objects_by_ids( + consultation.icd11_diagnoses + ) def get_icd11_provisional_diagnoses_object(self, consultation): return self.get_icd11_diagnoses_objects_by_ids( @@ -150,7 +160,11 @@ def update(self, instance, validated_data): if instance.discharge_date: raise ValidationError( - {"consultation": ["Discharged Consultation data cannot be updated"]} + { + "consultation": [ + "Discharged Consultation data cannot be updated" + ] + } ) if instance.suggestion == SuggestionChoices.OP: @@ -209,7 +223,10 @@ def update(self, instance, validated_data): pass if "assigned_to" in validated_data: - if validated_data["assigned_to"] != _temp and validated_data["assigned_to"]: + if ( + validated_data["assigned_to"] != _temp + and validated_data["assigned_to"] + ): NotificationGenerator( event=Notification.Event.PATIENT_CONSULTATION_ASSIGNMENT, caused_by=self.context["request"].user, @@ -241,12 +258,16 @@ def create(self, validated_data): # Authorisation Check - allowed_facilities = get_home_facility_queryset(self.context["request"].user) + allowed_facilities = get_home_facility_queryset( + self.context["request"].user + ) if not allowed_facilities.filter( id=self.validated_data["patient"].facility.id ).exists(): raise ValidationError( - {"facility": "Consultation creates are only allowed in home facility"} + { + "facility": "Consultation creates are only allowed in home facility" + } ) # End Authorisation Checks @@ -265,7 +286,9 @@ def create(self, validated_data): if validated_data["patient"].last_consultation: if not validated_data["patient"].last_consultation.discharge_date: raise ValidationError( - {"consultation": "Exists please Edit Existing Consultation"} + { + "consultation": "Exists please Edit Existing Consultation" + } ) if "is_kasp" in validated_data: @@ -296,7 +319,8 @@ def create(self, validated_data): }, ) serializer.is_valid(raise_exception=True) - serializer.save() + health_details = serializer.save() + consultation.health_details = health_details.external_id.hex except KeyError as error: if consultation.patient.last_consultation is None: @@ -336,7 +360,9 @@ def create(self, validated_data): if action != -1: patient.action = action if review_time > 0: - patient.review_time = localtime(now()) + timedelta(minutes=review_time) + patient.review_time = localtime(now()) + timedelta( + minutes=review_time + ) patient.save() NotificationGenerator( @@ -365,9 +391,9 @@ def validate(self, attrs): # TODO Add Bed Authorisation Validation if "suggestion" in validated: - if validated["suggestion"] is SuggestionChoices.R and not validated.get( - "referred_to" - ): + if validated[ + "suggestion" + ] is SuggestionChoices.R and not validated.get("referred_to"): raise ValidationError( { "referred_to": [ @@ -400,7 +426,11 @@ def validate(self, attrs): ) if validated["review_time"] <= 0: raise ValidationError( - {"review_time": ["This field value is must be greater than 0."]} + { + "review_time": [ + "This field value is must be greater than 0." + ] + } ) from care.facility.static_data.icd11 import ICDDiseases @@ -434,8 +464,12 @@ def validate(self, attrs): class PatientConsultationIDSerializer(serializers.ModelSerializer): - consultation_id = serializers.UUIDField(source="external_id", read_only=True) - patient_id = serializers.UUIDField(source="patient.external_id", read_only=True) + consultation_id = serializers.UUIDField( + source="external_id", read_only=True + ) + patient_id = serializers.UUIDField( + source="patient.external_id", read_only=True + ) class Meta: model = PatientConsultation From 04a5067d306551c5d6a3a7f6ea19e7c45d8c2682 Mon Sep 17 00:00:00 2001 From: cp-Coder Date: Wed, 5 Oct 2022 17:43:04 +0530 Subject: [PATCH 41/44] Added migration to resolve conflicting migrations --- .../migrations/0323_merge_20221005_1742.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 care/facility/migrations/0323_merge_20221005_1742.py diff --git a/care/facility/migrations/0323_merge_20221005_1742.py b/care/facility/migrations/0323_merge_20221005_1742.py new file mode 100644 index 0000000000..b55835ddb6 --- /dev/null +++ b/care/facility/migrations/0323_merge_20221005_1742.py @@ -0,0 +1,14 @@ +# Generated by Django 2.2.11 on 2022-10-05 12:12 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('facility', '0322_fileupload_is_archived'), + ('facility', '0322_patienthealthdetails'), + ] + + operations = [ + ] From c82415653c78b0abc4a401fc55a4f9353e6a1d92 Mon Sep 17 00:00:00 2001 From: cp-Coder Date: Wed, 19 Oct 2022 11:54:41 +0530 Subject: [PATCH 42/44] Updated health_details and medical_history API * Added vaccine, medical history nad disease model * Added the migrations with function to migrate the data * Added required serializers * Reformatted the code --- care/facility/admin.py | 4 +- .../api/serializers/medical_history.py | 79 ++++++++ care/facility/api/serializers/patient.py | 12 +- .../api/serializers/patient_consultation.py | 153 ++++++++------ .../api/serializers/patient_health_details.py | 80 ++++++-- .../migrations/0322_patienthealthdetails.py | 111 ++++++----- .../migrations/0324_auto_20221014_1535.py | 187 ++++++++++++++++++ .../0325_medicalhistory_consultation.py | 19 ++ .../models/json_schema/consultation.py | 21 -- care/facility/models/patient.py | 135 ++++++++----- care/facility/models/patient_consultation.py | 8 + .../tasks/patient/discharge_report.py | 9 +- care/utils/tests/test_base.py | 9 +- 13 files changed, 615 insertions(+), 212 deletions(-) create mode 100644 care/facility/api/serializers/medical_history.py create mode 100644 care/facility/migrations/0324_auto_20221014_1535.py create mode 100644 care/facility/migrations/0325_medicalhistory_consultation.py diff --git a/care/facility/admin.py b/care/facility/admin.py index 945bbfab8a..ee73ef2139 100644 --- a/care/facility/admin.py +++ b/care/facility/admin.py @@ -11,7 +11,7 @@ from .models import ( Building, - Disease, + Diseases, Facility, FacilityCapacity, FacilityInventoryItem, @@ -201,7 +201,7 @@ class FacilityUserAdmin(DjangoQLSearchMixin, admin.ModelAdmin, ExportCsvMixin): admin.site.register(PatientRegistration, PatientAdmin) admin.site.register(PatientTeleConsultation) admin.site.register(PatientSample, PatientSampleAdmin) -admin.site.register(Disease) +admin.site.register(Diseases) admin.site.register(FacilityInventoryUnit) admin.site.register(FacilityInventoryUnitConverter) admin.site.register(FacilityInventoryItem) diff --git a/care/facility/api/serializers/medical_history.py b/care/facility/api/serializers/medical_history.py new file mode 100644 index 0000000000..802e8cf0c6 --- /dev/null +++ b/care/facility/api/serializers/medical_history.py @@ -0,0 +1,79 @@ +from django.db import transaction +from rest_framework import serializers + +from care.facility.models import MedicalHistory, PatientRegistration +from care.facility.models.patient import Diseases +from care.facility.models.patient_consultation import PatientConsultation +from care.utils.serializer.external_id_field import ExternalIdSerializerField + + +class DiseaseSerializer(serializers.ModelSerializer): + date = serializers.DateField() + + class Meta: + model = Diseases + fields = ( + "disease", + "details", + "date", + "precision", + ) + + +class MedicalHistorySerializer(serializers.ModelSerializer): + id = serializers.CharField(source="external_id", read_only=True) + + patient = ExternalIdSerializerField( + queryset=PatientRegistration.objects.all(), required=False + ) + + consultation = ExternalIdSerializerField( + queryset=PatientConsultation.objects.all(), required=False + ) + + patient_diseases = serializers.ListSerializer( + child=DiseaseSerializer(), + required=False, + ) + + class Meta: + model = MedicalHistory + exclude = ("deleted", "external_id") + + def create(self, validated_data): + with transaction.atomic(): + consultation = validated_data["consultation"] + patient_diseases = validated_data.pop("patient_diseases", []) + medical_history = super().create(validated_data) + diseases = [] + for disease in patient_diseases: + diseases.append( + Diseases( + medical_history=medical_history, + **disease, + ) + ) + if diseases: + Diseases.objects.bulk_create(diseases, ignore_conflicts=True) + consultation.last_medical_history = medical_history + consultation.save(update_fields=["last_medical_history"]) + return medical_history + + def update(self, instance, validated_data): + with transaction.atomic(): + patient_diseases = validated_data.pop("patient_diseases", []) + medical_history = super().update(instance, validated_data) + Diseases.objects.filter(medical_history=medical_history).update( + deleted=True + ) + diseases = [] + for disease in patient_diseases: + diseases.append( + Diseases( + medical_history=medical_history, + **disease, + ) + ) + if diseases: + Diseases.objects.bulk_create(diseases, ignore_conflicts=True) + return medical_history diff --git a/care/facility/api/serializers/patient.py b/care/facility/api/serializers/patient.py index da2be7bdaa..80b983f3bc 100644 --- a/care/facility/api/serializers/patient.py +++ b/care/facility/api/serializers/patient.py @@ -15,7 +15,7 @@ from care.facility.models import ( DISEASE_CHOICES, GENDER_CHOICES, - Disease, + Diseases, Facility, FacilityPatientStatsHistory, PatientContactDetails, @@ -259,9 +259,9 @@ def create(self, validated_data): diseases = [] for disease in medical_history: - diseases.append(Disease(patient=patient, **disease)) + diseases.append(Diseases(patient=patient, **disease)) if diseases: - Disease.objects.bulk_create(diseases, ignore_conflicts=True) + Diseases.objects.bulk_create(diseases, ignore_conflicts=True) if meta_info: meta_info_obj = PatientMetaInfo.objects.create(**meta_info) @@ -305,12 +305,12 @@ def update(self, instance, validated_data): self.check_external_entry(validated_data["srf_id"]) patient = super().update(instance, validated_data) - Disease.objects.filter(patient=patient).update(deleted=True) + Diseases.objects.filter(patient=patient).update(deleted=True) diseases = [] for disease in medical_history: - diseases.append(Disease(patient=patient, **disease)) + diseases.append(Diseases(patient=patient, **disease)) if diseases: - Disease.objects.bulk_create(diseases, ignore_conflicts=True) + Diseases.objects.bulk_create(diseases, ignore_conflicts=True) if meta_info: for key, value in meta_info.items(): diff --git a/care/facility/api/serializers/patient_consultation.py b/care/facility/api/serializers/patient_consultation.py index 6bead9ea92..87bba3baa3 100644 --- a/care/facility/api/serializers/patient_consultation.py +++ b/care/facility/api/serializers/patient_consultation.py @@ -8,6 +8,7 @@ from care.facility.api.serializers.bed import ConsultationBedSerializer from care.facility.api.serializers.daily_round import DailyRoundSerializer from care.facility.api.serializers.facility import FacilityBasicInfoSerializer +from care.facility.api.serializers.medical_history import MedicalHistorySerializer from care.facility.api.serializers.patient_health_details import ( PatientHealthDetailsSerializer, ) @@ -19,7 +20,7 @@ ) from care.facility.models.bed import Bed, ConsultationBed from care.facility.models.notification import Notification -from care.facility.models.patient import PatientHealthDetails +from care.facility.models.patient import MedicalHistory, PatientHealthDetails from care.facility.models.patient_base import ( DISCHARGE_REASON_CHOICES, SYMPTOM_CHOICES, @@ -39,9 +40,7 @@ class PatientConsultationSerializer(serializers.ModelSerializer): id = serializers.CharField(source="external_id", read_only=True) - facility_name = serializers.CharField( - source="facility.name", read_only=True - ) + facility_name = serializers.CharField(source="facility.name", read_only=True) suggestion_text = ChoiceField( choices=PatientConsultation.SUGGESTION_CHOICES, read_only=True, @@ -60,14 +59,10 @@ class PatientConsultationSerializer(serializers.ModelSerializer): referred_to = ExternalIdSerializerField( queryset=Facility.objects.all(), required=False ) - patient = ExternalIdSerializerField( - queryset=PatientRegistration.objects.all() - ) + patient = ExternalIdSerializerField(queryset=PatientRegistration.objects.all()) facility = ExternalIdSerializerField(read_only=True) - assigned_to_object = UserAssignedSerializer( - source="assigned_to", read_only=True - ) + assigned_to_object = UserAssignedSerializer(source="assigned_to", read_only=True) assigned_to = serializers.PrimaryKeyRelatedField( queryset=User.objects.all(), required=False, allow_null=True @@ -84,16 +79,13 @@ class PatientConsultationSerializer(serializers.ModelSerializer): required=False, ) - review_time = serializers.IntegerField( - default=-1, write_only=True, required=False - ) + review_time = serializers.IntegerField(default=-1, write_only=True, required=False) last_edited_by = UserBaseMinimumSerializer(read_only=True) created_by = UserBaseMinimumSerializer(read_only=True) last_daily_round = DailyRoundSerializer(read_only=True) - health_details = ExternalIdSerializerField( + last_health_details = ExternalIdSerializerField( queryset=PatientHealthDetails.objects.all(), - source="last_health_details", required=False, ) @@ -104,6 +96,18 @@ class PatientConsultationSerializer(serializers.ModelSerializer): new_health_details = PatientHealthDetailsSerializer(required=False) + last_medical_history = ExternalIdSerializerField( + queryset=MedicalHistory.objects.all(), + required=False, + ) + + medical_history_object = MedicalHistorySerializer( + source="last_medical_history", + read_only=True, + ) + + new_medical_history = MedicalHistorySerializer(required=False) + current_bed = ConsultationBedSerializer(read_only=True) bed = ExternalIdSerializerField(queryset=Bed.objects.all(), required=False) @@ -127,9 +131,7 @@ def get_icd11_diagnoses_objects_by_ids(self, diagnoses_ids): return diagnosis_objects def get_icd11_diagnoses_object(self, consultation): - return self.get_icd11_diagnoses_objects_by_ids( - consultation.icd11_diagnoses - ) + return self.get_icd11_diagnoses_objects_by_ids(consultation.icd11_diagnoses) def get_icd11_provisional_diagnoses_object(self, consultation): return self.get_icd11_diagnoses_objects_by_ids( @@ -145,7 +147,10 @@ class Meta: "created_by", "kasp_enabled_date", ) - exclude = ("deleted", "external_id", "last_health_details") + exclude = ( + "deleted", + "external_id", + ) def validate_bed_number(self, bed_number): try: @@ -160,11 +165,7 @@ def update(self, instance, validated_data): if instance.discharge_date: raise ValidationError( - { - "consultation": [ - "Discharged Consultation data cannot be updated" - ] - } + {"consultation": ["Discharged Consultation data cannot be updated"]} ) if instance.suggestion == SuggestionChoices.OP: @@ -196,6 +197,7 @@ def update(self, instance, validated_data): _temp = instance.assigned_to health_details_data = validated_data.pop("new_health_details", None) + medical_history_data = validated_data.pop("new_medical_history", None) consultation = super().update(instance, validated_data) @@ -204,7 +206,7 @@ def update(self, instance, validated_data): consultation=consultation ) - serializer = PatientHealthDetailsSerializer( + health_details_serializer = PatientHealthDetailsSerializer( last_health_details_object, data={ "patient": consultation.patient.external_id.hex, @@ -216,17 +218,32 @@ def update(self, instance, validated_data): "request": self.context["request"], }, ) - serializer.is_valid(raise_exception=True) - serializer.save() + health_details_serializer.is_valid(raise_exception=True) + health_details_serializer.save() + + last_medical_history_object = MedicalHistory.objects.get( + consultation=consultation + ) + + medical_history_serializer = MedicalHistorySerializer( + last_medical_history_object, + data={ + "patient": consultation.patient.external_id.hex, + "consultation": consultation.external_id.hex, + **medical_history_data, + }, + context={ + "request": self.context["request"], + }, + ) + medical_history_serializer.is_valid(raise_exception=True) + medical_history_serializer.save() except KeyError: pass if "assigned_to" in validated_data: - if ( - validated_data["assigned_to"] != _temp - and validated_data["assigned_to"] - ): + if validated_data["assigned_to"] != _temp and validated_data["assigned_to"]: NotificationGenerator( event=Notification.Event.PATIENT_CONSULTATION_ASSIGNMENT, caused_by=self.context["request"].user, @@ -258,16 +275,12 @@ def create(self, validated_data): # Authorisation Check - allowed_facilities = get_home_facility_queryset( - self.context["request"].user - ) + allowed_facilities = get_home_facility_queryset(self.context["request"].user) if not allowed_facilities.filter( id=self.validated_data["patient"].facility.id ).exists(): raise ValidationError( - { - "facility": "Consultation creates are only allowed in home facility" - } + {"facility": "Consultation creates are only allowed in home facility"} ) # End Authorisation Checks @@ -286,9 +299,7 @@ def create(self, validated_data): if validated_data["patient"].last_consultation: if not validated_data["patient"].last_consultation.discharge_date: raise ValidationError( - { - "consultation": "Exists please Edit Existing Consultation" - } + {"consultation": "Exists please Edit Existing Consultation"} ) if "is_kasp" in validated_data: @@ -296,8 +307,9 @@ def create(self, validated_data): validated_data["kasp_enabled_date"] = localtime(now()) bed = validated_data.pop("bed", None) - health_details_data = validated_data.pop("new_health_details", None) - + print(validated_data) + health_details_data = validated_data.pop("new_health_details", []) + medical_history_data = validated_data.pop("new_medical_history", []) validated_data["facility_id"] = validated_data[ "patient" ].facility_id # Coercing facility as the patient's facility @@ -307,7 +319,7 @@ def create(self, validated_data): consultation.save() try: - serializer = PatientHealthDetailsSerializer( + health_details_serializer = PatientHealthDetailsSerializer( data={ "patient": consultation.patient.external_id.hex, "facility": consultation.facility.external_id.hex, @@ -318,9 +330,20 @@ def create(self, validated_data): "request": self.context["request"], }, ) - serializer.is_valid(raise_exception=True) - health_details = serializer.save() - consultation.health_details = health_details.external_id.hex + health_details_serializer.is_valid(raise_exception=True) + health_details = health_details_serializer.save() + consultation.last_health_details = health_details + + medical_history_serializer = MedicalHistorySerializer( + data={ + "patient": consultation.patient.external_id.hex, + "consultation": consultation.external_id.hex, + **medical_history_data, + } + ) + medical_history_serializer.is_valid(raise_exception=True) + medical_history = medical_history_serializer.save() + consultation.last_medical_history = medical_history except KeyError as error: if consultation.patient.last_consultation is None: @@ -328,14 +351,24 @@ def create(self, validated_data): { "health_details": [ "Please provide the health details of the patient" + ], + }, + { + "medical_history": [ + "Please provide the medical history of the patient" ] - } + }, ) from error consultation.last_health_details = ( consultation.patient.last_consultation.last_health_details ) - consultation.save(update_fields=["last_health_details"]) + consultation.last_medical_history = ( + consultation.patient.last_consultation.last_medical_history + ) + consultation.save( + update_fields=["last_health_details", "last_medical_history"] + ) if bed: consultation_bed = ConsultationBed( @@ -360,9 +393,7 @@ def create(self, validated_data): if action != -1: patient.action = action if review_time > 0: - patient.review_time = localtime(now()) + timedelta( - minutes=review_time - ) + patient.review_time = localtime(now()) + timedelta(minutes=review_time) patient.save() NotificationGenerator( @@ -391,9 +422,9 @@ def validate(self, attrs): # TODO Add Bed Authorisation Validation if "suggestion" in validated: - if validated[ - "suggestion" - ] is SuggestionChoices.R and not validated.get("referred_to"): + if validated["suggestion"] is SuggestionChoices.R and not validated.get( + "referred_to" + ): raise ValidationError( { "referred_to": [ @@ -426,11 +457,7 @@ def validate(self, attrs): ) if validated["review_time"] <= 0: raise ValidationError( - { - "review_time": [ - "This field value is must be greater than 0." - ] - } + {"review_time": ["This field value is must be greater than 0."]} ) from care.facility.static_data.icd11 import ICDDiseases @@ -464,12 +491,8 @@ def validate(self, attrs): class PatientConsultationIDSerializer(serializers.ModelSerializer): - consultation_id = serializers.UUIDField( - source="external_id", read_only=True - ) - patient_id = serializers.UUIDField( - source="patient.external_id", read_only=True - ) + consultation_id = serializers.UUIDField(source="external_id", read_only=True) + patient_id = serializers.UUIDField(source="patient.external_id", read_only=True) class Meta: model = PatientConsultation diff --git a/care/facility/api/serializers/patient_health_details.py b/care/facility/api/serializers/patient_health_details.py index c8fdd7534a..360bc00eea 100644 --- a/care/facility/api/serializers/patient_health_details.py +++ b/care/facility/api/serializers/patient_health_details.py @@ -1,3 +1,4 @@ +from django.db import transaction from django.forms import ChoiceField from rest_framework import serializers @@ -7,11 +8,22 @@ PatientRegistration, ) from care.facility.models.facility import Facility -from care.facility.models.patient_base import BLOOD_GROUP_CHOICES +from care.facility.models.patient import VaccinationHistory +from care.facility.models.patient_base import BLOOD_GROUP_CHOICES, VACCINE_CHOICES from care.utils.queryset.facility import get_home_facility_queryset from care.utils.serializer.external_id_field import ExternalIdSerializerField +class VaccinationHistorySerializer(serializers.ModelSerializer): + vaccine = serializers.ChoiceField(choices=VACCINE_CHOICES) + doses = serializers.IntegerField(required=False, default=0) + date = serializers.DateField() + + class Meta: + model = VaccinationHistory + fields = ("vaccine", "doses", "date", "precision") + + class PatientHealthDetailsSerializer(serializers.ModelSerializer): id = serializers.CharField(source="external_id", read_only=True) @@ -27,30 +39,60 @@ class PatientHealthDetailsSerializer(serializers.ModelSerializer): ) blood_group = ChoiceField(choices=BLOOD_GROUP_CHOICES, required=True) + vaccination_history = serializers.ListSerializer( + child=VaccinationHistorySerializer(), required=False + ) + class Meta: model = PatientHealthDetails exclude = ("deleted", "external_id") def create(self, validated_data): - consultation = validated_data["consultation"] - allowed_facilities = get_home_facility_queryset(self.context["request"].user) - if not allowed_facilities.filter( - id=self.validated_data["patient"].facility.id - ).exists(): - raise serializers.ValidationError( - { - "patient": "Patient Health Details creates are only allowed in home facility" - } + with transaction.atomic(): + consultation = validated_data["consultation"] + vaccination_history = validated_data.pop("vaccination_history", []) + allowed_facilities = get_home_facility_queryset( + self.context["request"].user ) + if not allowed_facilities.filter( + id=self.validated_data["patient"].facility.id + ).exists(): + raise serializers.ValidationError( + { + "patient": "Patient Health Details creates are only allowed in home facility" + } + ) - health_details = super().create(validated_data) - consultation.last_health_details = health_details - consultation.save(update_fields=["last_health_details"]) - return health_details + health_details = super().create(validated_data) + vaccines = [] + for vaccine in vaccination_history: + vaccines.append( + VaccinationHistory( + health_details=consultation.last_health_details, + **vaccine, + ) + ) + if vaccines: + VaccinationHistory.objects.bulk_create(vaccines, ignore_conflicts=True) + consultation.last_health_details = health_details + consultation.save(update_fields=["last_health_details"]) + return health_details def update(self, instance, validated_data): - consultation = validated_data["consultation"] - health_details = super().update(instance, validated_data) - consultation.last_health_details = health_details - consultation.save(update_fields=["last_health_details"]) - return health_details + with transaction.atomic(): + vaccination_history = validated_data.pop("vaccination_history", []) + health_details = super().update(instance, validated_data) + VaccinationHistory.objects.filter(health_details=health_details).update( + deleted=True + ) + vaccines = [] + for vaccine in vaccination_history: + vaccines.append( + VaccinationHistory( + health_details=health_details, + **vaccine, + ) + ) + if vaccines: + VaccinationHistory.objects.bulk_create(vaccines, ignore_conflicts=True) + return health_details diff --git a/care/facility/migrations/0322_patienthealthdetails.py b/care/facility/migrations/0322_patienthealthdetails.py index 8667516ab0..afaf8f7fc2 100644 --- a/care/facility/migrations/0322_patienthealthdetails.py +++ b/care/facility/migrations/0322_patienthealthdetails.py @@ -2,12 +2,12 @@ from django.db import migrations, models import django.db.models.deletion import django.core.validators +import partial_index import care.facility.models.mixins.permissions.patient -import django.contrib.postgres.fields.jsonb import care.utils.models.validators -def populate_data(apps, schema_editor): +def create_health_details(apps, schema_editor): health_details = apps.get_model("facility", "PatientHealthDetails") patient_registration = apps.get_model("facility", "PatientRegistration") patients = patient_registration.objects.all() @@ -47,13 +47,6 @@ def populate_data(apps, schema_editor): has_allergy=has_allergy, allergies=allergies, blood_group=blood_group, - vaccination_history=[ - { - "vaccine": patient.vaccine_name, - "doses": patient.number_of_doses, - "last_vaccinated_date": patient.last_vaccinated_date, - } - ], ) health_details_objs.append(health_details_obj) @@ -73,6 +66,30 @@ def link_data(apps, schema_editor): patient_cons.save(update_fields=["last_health_details"]) +def create_vaccination_history(apps, schema_editor): + vaccination_history = apps.get_model("facility", "VaccinationHistory") + patient_registration = apps.get_model("facility", "PatientRegistration") + patients = patient_registration.objects.all() + + vaccine_objs = [] + for patient in patients: + if ( + patient.vaccine_name + and patient.last_consultation.last_health_details + ): + vaccine_objs.append( + vaccination_history( + health_details=patient.last_consultation.last_health_details, + vaccine=patient.vaccine_name, + doses=patient.number_of_doses, + date=patient.last_vaccinated_date, + precision=0, + ) + ) + + vaccination_history.objects.bulk_create(vaccine_objs) + + class Migration(migrations.Migration): dependencies = [ @@ -196,41 +213,6 @@ class Migration(migrations.Migration): verbose_name="Patient's Height in CM", ), ), - ( - "vaccination_history", - django.contrib.postgres.fields.jsonb.JSONField( - default=list, - validators=[ - care.utils.models.validators.JSONFieldSchemaValidator( - { - "$schema": "http://json-schema.org/draft-07/schema#", - "items": [ - { - "additionalProperties": False, - "properties": { - "doses": { - "default": 0, - "type": "number", - }, - "last_vaccinated_date": { - "type": "string" - }, - "vaccine": {"type": "string"}, - }, - "required": [ - "vaccine", - "doses", - "last_vaccinated_date", - ], - "type": "object", - } - ], - "type": "array", - } - ) - ], - ), - ), ], options={ "abstract": False, @@ -240,6 +222,33 @@ class Migration(migrations.Migration): care.facility.models.mixins.permissions.patient.PatientRelatedPermissionMixin, ), ), + migrations.CreateModel( + name="VaccinationHistory", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("vaccine", models.CharField(max_length=100)), + ("doses", models.IntegerField(default=0)), + ("date", models.DateField(blank=True, null=True)), + ("precision", models.IntegerField(default=0)), + ("deleted", models.BooleanField(default=False)), + ( + "health_details", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="vaccination_history", + to="facility.PatientHealthDetails", + ), + ), + ], + ), migrations.AddField( model_name="patientconsultation", name="last_health_details", @@ -250,6 +259,18 @@ class Migration(migrations.Migration): to="facility.PatientHealthDetails", ), ), - migrations.RunPython(populate_data, migrations.RunPython.noop), + migrations.AddIndex( + model_name="vaccinationhistory", + index=partial_index.PartialIndex( + fields=["health_details", "vaccine"], + name="facility_va_health__5bb62d_partial", + unique=True, + where=partial_index.PQ(deleted=False), + ), + ), + migrations.RunPython(create_health_details, migrations.RunPython.noop), migrations.RunPython(link_data, migrations.RunPython.noop), + migrations.RunPython( + create_vaccination_history, migrations.RunPython.noop + ), ] diff --git a/care/facility/migrations/0324_auto_20221014_1535.py b/care/facility/migrations/0324_auto_20221014_1535.py new file mode 100644 index 0000000000..658d3736cc --- /dev/null +++ b/care/facility/migrations/0324_auto_20221014_1535.py @@ -0,0 +1,187 @@ +# Generated by Django 2.2.11 on 2022-10-14 10:05 + +import datetime +from django.db import migrations, models +import django.db.models.deletion +import partial_index +import uuid +import care.facility.models.mixins.permissions.patient +from care.facility.models.patient_base import DISEASE_CHOICES + + +def create_medical_history(apps, schema_editor): + patient_registration = apps.get_model("facility", "PatientRegistration") + patients = patient_registration.objects.all() + + medical_history = apps.get_model("facility", "MedicalHistory") + medical_history_objs = [] + for patient in patients: + medical_history_objs.append( + medical_history( + patient=patient, + ongoing_medication=patient.ongoing_medication, + present_health=patient.present_health, + ) + ) + + medical_history.objects.bulk_create(medical_history_objs) + + +def link_data(apps, schema_editor): + patient_consultation = apps.get_model("facility", "PatientConsultation") + medical_history = apps.get_model("facility", "MedicalHistory") + patient_cons_objs = patient_consultation.objects.all() + + for patient_cons in patient_cons_objs: + patient_cons.last_medical_history = medical_history.objects.get( + id=patient_cons.patient.id + ) + patient_cons.save(update_fields=["last_medical_history"]) + + +def create_disease_history(apps, schema_editor): + disease_history = apps.get_model("facility", "Diseases") + disease_model = apps.get_model("facility", "Disease") + diseases = disease_model.objects.filter(deleted=False) + + disease_objs = [] + for disease in diseases: + disease_objs.append( + disease_history( + patient=disease.patient, + disease=DISEASE_CHOICES[disease.disease], + details=disease.details, + date=datetime.date.today(), + ) + ) + + disease_history.objects.bulk_create(disease_objs) + + +class Migration(migrations.Migration): + + dependencies = [ + ("facility", "0323_merge_20221005_1742"), + ] + + operations = [ + migrations.CreateModel( + name="MedicalHistory", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "external_id", + models.UUIDField( + db_index=True, default=uuid.uuid4, unique=True + ), + ), + ( + "created_date", + models.DateTimeField( + auto_now_add=True, db_index=True, null=True + ), + ), + ( + "modified_date", + models.DateTimeField( + auto_now=True, db_index=True, null=True + ), + ), + ("deleted", models.BooleanField(db_index=True, default=False)), + ( + "ongoing_medication", + models.TextField( + blank=True, + default="", + verbose_name="Already pescribed medication if any", + ), + ), + ( + "present_health", + models.TextField( + blank=True, + default="", + verbose_name="Patient's Current Health Details", + ), + ), + ( + "patient", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="facility.PatientRegistration", + ), + ), + ], + options={ + "abstract": False, + }, + bases=( + models.Model, + care.facility.models.mixins.permissions.patient.PatientRelatedPermissionMixin, + ), + ), + migrations.CreateModel( + name="Diseases", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("disease", models.CharField(max_length=100)), + ("details", models.TextField(blank=True, null=True)), + ("date", models.DateField(blank=True, null=True)), + ("precision", models.IntegerField(default=0)), + ("deleted", models.BooleanField(default=False)), + ( + "medical_history", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="patient_diseases", + to="facility.MedicalHistory", + ), + ), + ], + ), + migrations.AddField( + model_name="patientconsultation", + name="last_medical_history", + field=models.ForeignKey( + default=None, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="facility.MedicalHistory", + ), + ), + migrations.RunPython( + create_medical_history, migrations.RunPython.noop + ), + migrations.RunPython(link_data, migrations.RunPython.noop), + migrations.RunPython( + create_disease_history, migrations.RunPython.noop + ), + migrations.DeleteModel( + name="Disease", + ), + migrations.AddIndex( + model_name="diseases", + index=partial_index.PartialIndex( + fields=["medical_history", "disease"], + name="facility_di_medical_1af9d2_partial", + unique=True, + where=partial_index.PQ(deleted=False), + ), + ), + ] diff --git a/care/facility/migrations/0325_medicalhistory_consultation.py b/care/facility/migrations/0325_medicalhistory_consultation.py new file mode 100644 index 0000000000..cb7e413668 --- /dev/null +++ b/care/facility/migrations/0325_medicalhistory_consultation.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2.11 on 2022-10-18 04:05 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('facility', '0324_auto_20221014_1535'), + ] + + operations = [ + migrations.AddField( + model_name='medicalhistory', + name='consultation', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='facility.PatientConsultation'), + ), + ] diff --git a/care/facility/models/json_schema/consultation.py b/care/facility/models/json_schema/consultation.py index 812e252956..91e68402c2 100644 --- a/care/facility/models/json_schema/consultation.py +++ b/care/facility/models/json_schema/consultation.py @@ -17,24 +17,3 @@ } ], } - -VACCINATION = { - "$schema": "http://json-schema.org/draft-07/schema#", - "type": "array", - "items": [ - { - "type": "object", - "properties": { - "vaccine": {"type": "string"}, - "doses": {"type": "number", "default": 0}, - "last_vaccinated_date": {"type": "string"}, - }, - "additionalProperties": False, - "required": [ - "vaccine", - "doses", - "last_vaccinated_date", - ], - } - ], -} diff --git a/care/facility/models/patient.py b/care/facility/models/patient.py index 26e9398004..cd0772a6e0 100644 --- a/care/facility/models/patient.py +++ b/care/facility/models/patient.py @@ -1,7 +1,6 @@ import datetime import enum -from django.contrib.postgres.fields import JSONField as JsonField from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models from fernet_fields import EncryptedCharField, EncryptedIntegerField @@ -9,7 +8,6 @@ from simple_history.models import HistoricalRecords from care.facility.models import ( - DISEASE_CHOICES, DiseaseStatusEnum, District, Facility, @@ -20,7 +18,6 @@ Ward, pretty_boolean, ) -from care.facility.models.json_schema.consultation import VACCINATION from care.facility.models.mixins.permissions.facility import ( FacilityRelatedPermissionMixin, ) @@ -47,7 +44,6 @@ ) from care.utils.models.base import BaseManager, BaseModel from care.utils.models.jsonfield import JSONField -from care.utils.models.validators import JSONFieldSchemaValidator class PatientRegistration(PatientBaseModel, PatientPermissionMixin): @@ -187,13 +183,13 @@ class TestTypeEnum(enum.Enum): default="", blank=True, verbose_name="Patient's Current Health Details", - ) + ) # deprecated ongoing_medication = models.TextField( default="", blank=True, verbose_name="Already pescribed medication if any", - ) + ) # deprecated has_SARI = models.BooleanField( default=False, verbose_name="Does the Patient Suffer from SARI" @@ -549,12 +545,12 @@ def save(self, *args, **kwargs) -> None: "countries_travelled": "Countries Patient has Travelled to", "date_of_return": "Return Date from the Last Country if Travelled", "is_migrant_worker": "Is the Patient a Migrant Worker", - "present_health": "Patient's Current Health Details", - "ongoing_medication": "Already pescribed medication if any", "has_SARI": "Does the Patient Suffer from SARI", "date_of_receipt_of_information": "Patient's information received date", "will_donate_blood": "Will Patient Donate Blood?", "fit_for_blood_donation": "Is Patient Fit for Blood Donation?", + # "present_health": "Patient's Current Health Details", + # "ongoing_medication": "Already pescribed medication if any", "date_of_test": "Date of Sample Test", "srf_id": "SRF Test Id", # IDSP Data @@ -726,34 +722,6 @@ class ModeOfContactEnum(enum.IntEnum): objects = BaseManager() -class Disease(models.Model): - patient = models.ForeignKey( - PatientRegistration, - on_delete=models.CASCADE, - related_name="medical_history", - ) - disease = models.IntegerField(choices=DISEASE_CHOICES) - details = models.TextField(blank=True, null=True) - deleted = models.BooleanField(default=False) - - objects = BaseManager() - - class Meta: - indexes = [ - PartialIndex( - fields=["patient", "disease"], - unique=True, - where=PQ(deleted=False), - ) - ] - - def __str__(self): - return self.patient.name + " - " + self.get_disease_display() - - def get_disease_display(self): - return DISEASE_CHOICES[self.disease - 1][1] - - class FacilityPatientStatsHistory(FacilityBaseModel, FacilityRelatedPermissionMixin): facility = models.ForeignKey("Facility", on_delete=models.PROTECT) entry_date = models.DateField() @@ -812,6 +780,12 @@ class PatientHealthDetails(PatientBaseModel, PatientRelatedPermissionMixin): Facility, on_delete=models.CASCADE, related_name="health_details" ) + consultation = models.ForeignKey( + PatientConsultation, + on_delete=models.CASCADE, + null=True, + ) + family_details = models.TextField( default="", blank=True, verbose_name="Patient's Family Details" ) @@ -829,16 +803,6 @@ class PatientHealthDetails(PatientBaseModel, PatientRelatedPermissionMixin): verbose_name="Blood Group of Patient", ) - consultation = models.ForeignKey( - PatientConsultation, - on_delete=models.CASCADE, - null=True, - ) - - vaccination_history = JsonField( - default=dict, validators=[JSONFieldSchemaValidator(VACCINATION)] - ) - height = models.FloatField( default=None, null=True, @@ -852,3 +816,82 @@ class PatientHealthDetails(PatientBaseModel, PatientRelatedPermissionMixin): verbose_name="Patient's Weight in KG", validators=[MinValueValidator(0)], ) + + +class VaccinationHistory(models.Model): + health_details = models.ForeignKey( + PatientHealthDetails, + on_delete=models.CASCADE, + related_name="vaccination_history", + ) + vaccine = models.CharField(max_length=100) + doses = models.IntegerField(default=0) + date = models.DateField(null=True, blank=True) + precision = models.IntegerField(default=0) + + deleted = models.BooleanField(default=False) + objects = BaseManager() + + class Meta: + indexes = [ + PartialIndex( + fields=["health_details", "vaccine"], + unique=True, + where=PQ(deleted=False), + ) + ] + + def __str__(self): + return self.vaccine + " " + self.doses + + +class MedicalHistory(PatientBaseModel, PatientRelatedPermissionMixin): + patient = models.ForeignKey( + PatientRegistration, + on_delete=models.CASCADE, + ) + + consultation = models.ForeignKey( + PatientConsultation, + on_delete=models.CASCADE, + null=True, + ) + + ongoing_medication = models.TextField( + default="", + blank=True, + verbose_name="Already pescribed medication if any", + ) + + present_health = models.TextField( + default="", + blank=True, + verbose_name="Patient's Current Health Details", + ) + + +class Diseases(models.Model): + medical_history = models.ForeignKey( + MedicalHistory, + on_delete=models.CASCADE, + related_name="patient_diseases", + ) + disease = models.CharField(max_length=100) + details = models.TextField(null=True, blank=True) + date = models.DateField(null=True, blank=True) + precision = models.IntegerField(default=0) + + deleted = models.BooleanField(default=False) + objects = BaseManager() + + class Meta: + indexes = [ + PartialIndex( + fields=["medical_history", "disease"], + unique=True, + where=PQ(deleted=False), + ) + ] + + def __str__(self): + return self.patient.name + " " + self.disease diff --git a/care/facility/models/patient_consultation.py b/care/facility/models/patient_consultation.py index b2dae4df96..ec17b7786f 100644 --- a/care/facility/models/patient_consultation.py +++ b/care/facility/models/patient_consultation.py @@ -143,6 +143,14 @@ class PatientConsultation(PatientBaseModel, PatientRelatedPermissionMixin): null=True, default=None, ) + + last_medical_history = models.ForeignKey( + "MedicalHistory", + on_delete=models.SET_NULL, + null=True, + default=None, + ) + # Physical Information height = models.FloatField( diff --git a/care/facility/tasks/patient/discharge_report.py b/care/facility/tasks/patient/discharge_report.py index 0000760c8b..8ca3149532 100644 --- a/care/facility/tasks/patient/discharge_report.py +++ b/care/facility/tasks/patient/discharge_report.py @@ -1,5 +1,4 @@ import datetime -import logging import random import string import time @@ -14,12 +13,12 @@ from care.facility.models import ( DailyRound, + Diseases, + DiseaseStatusEnum, + InvestigationValue, PatientConsultation, PatientRegistration, PatientSample, - Disease, - InvestigationValue, - DiseaseStatusEnum, ) @@ -34,7 +33,7 @@ def generate_discharge_report(patient_id, email): consultations = PatientConsultation.objects.filter(patient=patient).order_by( "-created_date" ) - diseases = Disease.objects.filter(patient=patient) + diseases = Diseases.objects.filter(patient=patient) if consultations.exists(): consultation = consultations.first() samples = PatientSample.objects.filter( diff --git a/care/utils/tests/test_base.py b/care/utils/tests/test_base.py index bc160026b1..276b9d2323 100644 --- a/care/utils/tests/test_base.py +++ b/care/utils/tests/test_base.py @@ -14,7 +14,7 @@ COVID_CATEGORY_CHOICES, DISEASE_CHOICES_MAP, SYMPTOM_CHOICES, - Disease, + Diseases, DiseaseStatusEnum, Facility, LocalBody, @@ -109,7 +109,7 @@ def create_patient(cls, **kwargs): patient = PatientRegistration.objects.create(**patient_data) diseases = [ - Disease.objects.create( + Diseases.objects.create( patient=patient, disease=DISEASE_CHOICES_MAP[mh["disease"]], details=mh["details"], @@ -325,7 +325,10 @@ def get_district_representation(self, district: District): def get_state_representation(self, state: State): if state is None: return {"state": None, "state_object": None} - return {"state": state.id, "state_object": {"id": state.id, "name": state.name}} + return { + "state": state.id, + "state_object": {"id": state.id, "name": state.name}, + } def _convert_to_matchable_types(self, d): def dict_to_matching_type(d: dict): From 91e0f06f2790d32ea2663ef44a47e6b6f65e5028 Mon Sep 17 00:00:00 2001 From: cp-Coder Date: Wed, 19 Oct 2022 12:00:02 +0530 Subject: [PATCH 43/44] Fix migrations --- .../migrations/0326_merge_20221019_1159.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 care/facility/migrations/0326_merge_20221019_1159.py diff --git a/care/facility/migrations/0326_merge_20221019_1159.py b/care/facility/migrations/0326_merge_20221019_1159.py new file mode 100644 index 0000000000..13b55f380e --- /dev/null +++ b/care/facility/migrations/0326_merge_20221019_1159.py @@ -0,0 +1,14 @@ +# Generated by Django 2.2.11 on 2022-10-19 06:29 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('facility', '0323_fileupload_archive_reason'), + ('facility', '0325_medicalhistory_consultation'), + ] + + operations = [ + ] From 1485db83c87d25d59ad462408b34cdb7e80bbc56 Mon Sep 17 00:00:00 2001 From: cp-Coder Date: Wed, 19 Oct 2022 18:46:09 +0530 Subject: [PATCH 44/44] Fixed the bug in health details serializer --- .../api/serializers/patient_health_details.py | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/care/facility/api/serializers/patient_health_details.py b/care/facility/api/serializers/patient_health_details.py index 360bc00eea..c5beaaf8ea 100644 --- a/care/facility/api/serializers/patient_health_details.py +++ b/care/facility/api/serializers/patient_health_details.py @@ -9,7 +9,10 @@ ) from care.facility.models.facility import Facility from care.facility.models.patient import VaccinationHistory -from care.facility.models.patient_base import BLOOD_GROUP_CHOICES, VACCINE_CHOICES +from care.facility.models.patient_base import ( + BLOOD_GROUP_CHOICES, + VACCINE_CHOICES, +) from care.utils.queryset.facility import get_home_facility_queryset from care.utils.serializer.external_id_field import ExternalIdSerializerField @@ -68,12 +71,14 @@ def create(self, validated_data): for vaccine in vaccination_history: vaccines.append( VaccinationHistory( - health_details=consultation.last_health_details, + health_details=health_details, **vaccine, ) ) if vaccines: - VaccinationHistory.objects.bulk_create(vaccines, ignore_conflicts=True) + VaccinationHistory.objects.bulk_create( + vaccines, ignore_conflicts=True + ) consultation.last_health_details = health_details consultation.save(update_fields=["last_health_details"]) return health_details @@ -82,9 +87,9 @@ def update(self, instance, validated_data): with transaction.atomic(): vaccination_history = validated_data.pop("vaccination_history", []) health_details = super().update(instance, validated_data) - VaccinationHistory.objects.filter(health_details=health_details).update( - deleted=True - ) + VaccinationHistory.objects.filter( + health_details=health_details + ).update(deleted=True) vaccines = [] for vaccine in vaccination_history: vaccines.append( @@ -94,5 +99,7 @@ def update(self, instance, validated_data): ) ) if vaccines: - VaccinationHistory.objects.bulk_create(vaccines, ignore_conflicts=True) + VaccinationHistory.objects.bulk_create( + vaccines, ignore_conflicts=True + ) return health_details