From a0d0299dfa6ca1878ba7bc05a452815cdeab0719 Mon Sep 17 00:00:00 2001 From: Ashesh3 <3626859+Ashesh3@users.noreply.github.com> Date: Tue, 20 Feb 2024 12:29:51 +0530 Subject: [PATCH 1/8] Add assigned_clinicians field to PatientConsultation model --- ...patientconsultation_assigned_clinicians.py | 21 +++++++++++++++++++ care/facility/models/patient_consultation.py | 4 ++++ 2 files changed, 25 insertions(+) create mode 100644 care/facility/migrations/0415_patientconsultation_assigned_clinicians.py diff --git a/care/facility/migrations/0415_patientconsultation_assigned_clinicians.py b/care/facility/migrations/0415_patientconsultation_assigned_clinicians.py new file mode 100644 index 0000000000..3ea1661228 --- /dev/null +++ b/care/facility/migrations/0415_patientconsultation_assigned_clinicians.py @@ -0,0 +1,21 @@ +# Generated by Django 4.2.8 on 2024-02-20 06:57 + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ("facility", "0414_remove_bed_old_name"), + ] + + operations = [ + migrations.AddField( + model_name="patientconsultation", + name="assigned_clinicians", + field=models.ManyToManyField( + related_name="patient_assigned_clinician", to=settings.AUTH_USER_MODEL + ), + ), + ] diff --git a/care/facility/models/patient_consultation.py b/care/facility/models/patient_consultation.py index a16c08e2ae..c37622196f 100644 --- a/care/facility/models/patient_consultation.py +++ b/care/facility/models/patient_consultation.py @@ -168,6 +168,10 @@ class PatientConsultation(PatientBaseModel, ConsultationRelatedPermissionMixin): related_name="patient_assigned_to", ) + assigned_clinicians = models.ManyToManyField( + User, related_name="patient_assigned_clinician" + ) + medico_legal_case = models.BooleanField(default=False) deprecated_verified_by = models.TextField( From b88959371edebf9f756b22a39e7ac304b8dbdbab Mon Sep 17 00:00:00 2001 From: Ashesh3 <3626859+Ashesh3@users.noreply.github.com> Date: Wed, 21 Feb 2024 19:15:30 +0530 Subject: [PATCH 2/8] Migrate consultation's assigned_to to assigned_clinicians --- care/facility/api/serializers/daily_round.py | 7 ++- care/facility/api/serializers/file_upload.py | 23 ++++---- .../api/serializers/patient_consultation.py | 49 +++++++++++++---- care/facility/api/viewsets/patient.py | 55 ++++++++++--------- .../api/viewsets/patient_consultation.py | 12 ++-- .../api/viewsets/patient_investigation.py | 4 +- .../migrations/0416_auto_20240220_1233.py | 38 +++++++++++++ care/facility/models/daily_round.py | 4 +- .../models/mixins/permissions/patient.py | 12 +++- care/facility/models/patient_consultation.py | 15 +++-- care/users/api/viewsets/users.py | 9 ++- care/utils/notification_handler.py | 24 ++++---- care/utils/queryset/consultation.py | 2 +- care/utils/queryset/patient.py | 4 +- 14 files changed, 177 insertions(+), 81 deletions(-) create mode 100644 care/facility/migrations/0416_auto_20240220_1233.py diff --git a/care/facility/api/serializers/daily_round.py b/care/facility/api/serializers/daily_round.py index 1ceffb0777..05b69631b0 100644 --- a/care/facility/api/serializers/daily_round.py +++ b/care/facility/api/serializers/daily_round.py @@ -141,7 +141,10 @@ def update(self, instance, validated_data): patient.save() validated_data["last_updated_by_telemedicine"] = False - if self.context["request"].user == instance.consultation.assigned_to: + if ( + self.context["request"].user + in instance.consultation.assigned_clinicians.all() + ): validated_data["last_updated_by_telemedicine"] = True instance.consultation.save(update_fields=["last_updated_by_telemedicine"]) @@ -271,7 +274,7 @@ def create(self, validated_data): if ( self.context["request"].user - == validated_data["consultation"].assigned_to + in validated_data["consultation"].assigned_clinicians.all() ): validated_data["created_by_telemedicine"] = True validated_data["last_updated_by_telemedicine"] = True diff --git a/care/facility/api/serializers/file_upload.py b/care/facility/api/serializers/file_upload.py index 962688da7a..feb7367f2d 100644 --- a/care/facility/api/serializers/file_upload.py +++ b/care/facility/api/serializers/file_upload.py @@ -26,9 +26,10 @@ def check_permissions(file_type, associating_id, user, action="create"): if user == patient.assigned_to: return patient.id if patient.last_consultation: - if patient.last_consultation.assigned_to: - if user == patient.last_consultation.assigned_to: - return patient.id + if patient.last_consultation.assigned_clinicians.filter( + id=user.id + ).exists(): + return patient.id if not has_facility_permission(user, patient.facility): raise Exception("No Permission") return patient.id @@ -44,9 +45,8 @@ def check_permissions(file_type, associating_id, user, action="create"): if consultation.patient.assigned_to: if user == consultation.patient.assigned_to: return consultation.id - if consultation.assigned_to: - if user == consultation.assigned_to: - return consultation.id + if consultation.assigned_clinicians.filter(id=user.id).exists(): + return consultation.id if not ( has_facility_permission(user, consultation.patient.facility) or has_facility_permission(user, consultation.facility) @@ -60,7 +60,7 @@ def check_permissions(file_type, associating_id, user, action="create"): and user == consultation.patient.assigned_to ): return consultation.external_id - if consultation.assigned_to and user == consultation.assigned_to: + if consultation.assigned_clinicians.filter(id=user.id).exists(): return consultation.external_id if not ( has_facility_permission(user, consultation.patient.facility) @@ -74,10 +74,11 @@ def check_permissions(file_type, associating_id, user, action="create"): if patient.assigned_to: if user == patient.assigned_to: return sample.id - if sample.consultation: - if sample.consultation.assigned_to: - if user == sample.consultation.assigned_to: - return sample.id + if ( + sample.consultation + and sample.consultation.assigned_clinicians.filter(id=user.id).exists() + ): + return sample.id if sample.testing_facility: if has_facility_permission( user, diff --git a/care/facility/api/serializers/patient_consultation.py b/care/facility/api/serializers/patient_consultation.py index b3759ee369..050b0ff193 100644 --- a/care/facility/api/serializers/patient_consultation.py +++ b/care/facility/api/serializers/patient_consultation.py @@ -104,9 +104,20 @@ class PatientConsultationSerializer(serializers.ModelSerializer): 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 + ) # deprecated assigned_to = serializers.PrimaryKeyRelatedField( queryset=User.objects.all(), required=False, allow_null=True + ) # deprecated + + assigned_clinicians_object = UserBaseMinimumSerializer( + many=True, read_only=True, source="assigned_clinicians" + ) + assigned_clinicians = serializers.PrimaryKeyRelatedField( + queryset=User.objects.all(), + many=True, + required=False, ) treating_physician_object = UserBaseMinimumSerializer( @@ -229,15 +240,19 @@ def update(self, instance, validated_data): patient.review_time = None patient.save() - validated_data["last_updated_by_telemedicine"] = ( - self.context["request"].user == instance.assigned_to - ) + validated_data[ + "last_updated_by_telemedicine" + ] = instance.assigned_clinicians.filter( + id=self.context["request"].user.id + ).exists() if "is_kasp" in validated_data: if validated_data["is_kasp"] and (not instance.is_kasp): validated_data["kasp_enabled_date"] = localtime(now()) - _temp = instance.assigned_to + _old_clinicians = list( + instance.assigned_clinicians.values_list("id", flat=True) + ) consultation = super().update(instance, validated_data) @@ -249,8 +264,14 @@ def update(self, instance, validated_data): old_instance, ) - if "assigned_to" in validated_data: - if validated_data["assigned_to"] != _temp and validated_data["assigned_to"]: + if "assigned_clinicians" in validated_data: + _new_clinicians = list( + instance.assigned_clinicians.values_list("id", flat=True) + ) + if ( + set(_old_clinicians) != set(_new_clinicians) + and validated_data["assigned_clinicians"] + ): NotificationGenerator( event=Notification.Event.PATIENT_CONSULTATION_ASSIGNMENT, caused_by=self.context["request"].user, @@ -346,8 +367,11 @@ def create(self, validated_data): if validated_data["patient"].last_consultation: if ( - self.context["request"].user - == validated_data["patient"].last_consultation.assigned_to + validated_data["patient"] + .last_consultation.assigned_clinicians.filter( + id=self.context["request"].user.id + ) + .exists() ): raise ValidationError( { @@ -370,7 +394,9 @@ def create(self, validated_data): validated_data["facility_id"] = validated_data[ "patient" ].facility_id # Coercing facility as the patient's facility + assigned_clinicians = validated_data.pop("assigned_clinicians", []) consultation = super().create(validated_data) + consultation.assigned_clinicians.set(assigned_clinicians) consultation.created_by = self.context["request"].user consultation.last_edited_by = self.context["request"].user patient = consultation.patient @@ -441,7 +467,7 @@ def create(self, validated_data): consultation.created_date, ) - if consultation.assigned_to: + if consultation.assigned_clinicians.exists(): NotificationGenerator( event=Notification.Event.PATIENT_CONSULTATION_ASSIGNMENT, caused_by=self.context["request"].user, @@ -686,7 +712,10 @@ def validate(self, attrs): def update(self, instance: PatientConsultation, validated_data): old_instance = copy(instance) + assigned_clinicians = validated_data.pop("assigned_clinicians", None) with transaction.atomic(): + if assigned_clinicians is not None: + instance.assigned_clinicians.set(assigned_clinicians) instance = super().update(instance, validated_data) patient: PatientRegistration = instance.patient patient.is_active = False diff --git a/care/facility/api/viewsets/patient.py b/care/facility/api/viewsets/patient.py index 517cb3dc51..939f300408 100644 --- a/care/facility/api/viewsets/patient.py +++ b/care/facility/api/viewsets/patient.py @@ -5,7 +5,7 @@ from django.conf import settings from django.contrib.postgres.search import TrigramSimilarity from django.db import models -from django.db.models import Case, OuterRef, Q, Subquery, When +from django.db.models import Case, OuterRef, Prefetch, Q, Subquery, When from django_filters import rest_framework as filters from djqscsv import render_to_csv_response from drf_spectacular.utils import extend_schema, extend_schema_view @@ -199,8 +199,8 @@ def filter_by_bed_type(self, queryset, name, value): field_name="last_consultation__new_discharge_reason", choices=NewDischargeReasonEnum.choices, ) - last_consultation_assigned_to = filters.NumberFilter( - field_name="last_consultation__assigned_to" + last_consultation_assigned_clinician = filters.NumberFilter( + field_name="last_consultation__assigned_clinicians__id" ) last_consultation_is_telemedicine = filters.BooleanFilter( field_name="last_consultation__is_telemedicine" @@ -272,7 +272,7 @@ def filter_queryset(self, request, queryset, view): elif view.action != "transfer": allowed_facilities = get_accessible_facilities(request.user) q_filters = Q(facility__id__in=allowed_facilities) - q_filters |= Q(last_consultation__assigned_to=request.user) + q_filters |= Q(last_consultation__assigned_clinicians=request.user) q_filters |= Q(assigned_to=request.user) queryset = queryset.filter(q_filters) @@ -331,25 +331,28 @@ class PatientViewSet( ] permission_classes = (IsAuthenticated, DRYPermissions) lookup_field = "external_id" - queryset = PatientRegistration.objects.all().select_related( - "local_body", - "district", - "state", - "ward", - "assigned_to", - "facility", - "facility__ward", - "facility__local_body", - "facility__district", - "facility__state", - # "nearest_facility", - # "nearest_facility__local_body", - # "nearest_facility__district", - # "nearest_facility__state", - "last_consultation", - "last_consultation__assigned_to", - "last_edited", - "created_by", + queryset = ( + PatientRegistration.objects.all() + .select_related( + "local_body", + "district", + "state", + "ward", + "assigned_to", + "facility", + "facility__ward", + "facility__local_body", + "facility__district", + "facility__state", + # "nearest_facility", + # "nearest_facility__local_body", + # "nearest_facility__district", + # "nearest_facility__state", + "last_consultation", + "last_edited", + "created_by", + ) + .prefetch_related(Prefetch("last_consultation__assigned_clinicians")) ) ordering_fields = [ "facility__name", @@ -705,7 +708,9 @@ def get_queryset(self): else: allowed_facilities = get_accessible_facilities(user) q_filters = Q(patient_note__patient__facility__id__in=allowed_facilities) - q_filters |= Q(patient_note__patient__last_consultation__assigned_to=user) + q_filters |= Q( + patient_note__patient__last_consultation__assigned_clinicians=user + ) q_filters |= Q(patient_note__patient__assigned_to=user) q_filters |= Q(patient_note__created_by=user) queryset = queryset.filter(q_filters) @@ -756,7 +761,7 @@ def get_queryset(self): else: allowed_facilities = get_accessible_facilities(user) q_filters = Q(patient__facility__id__in=allowed_facilities) - q_filters |= Q(patient__last_consultation__assigned_to=user) + q_filters |= Q(patient__last_consultation__assigned_clinicians=user) q_filters |= Q(patient__assigned_to=user) q_filters |= Q(created_by=user) queryset = queryset.filter(q_filters) diff --git a/care/facility/api/viewsets/patient_consultation.py b/care/facility/api/viewsets/patient_consultation.py index d147ea611d..6c9e48a29c 100644 --- a/care/facility/api/viewsets/patient_consultation.py +++ b/care/facility/api/viewsets/patient_consultation.py @@ -74,10 +74,14 @@ def get_permissions(self): def get_queryset(self): if self.serializer_class == PatientConsultationSerializer: self.queryset = self.queryset.prefetch_related( - "assigned_to", Prefetch( - "assigned_to__skills", - queryset=Skill.objects.filter(userskill__deleted=False), + "assigned_clinicians", + queryset=User.objects.prefetch_related( + Prefetch( + "skills", + queryset=Skill.objects.filter(userskill__deleted=False), + ) + ), ), "current_bed", "current_bed__bed", @@ -96,7 +100,7 @@ def get_queryset(self): ) allowed_facilities = get_accessible_facilities(self.request.user) applied_filters = Q(patient__facility__id__in=allowed_facilities) - applied_filters |= Q(assigned_to=self.request.user) + applied_filters |= Q(assigned_clinicians=self.request.user) applied_filters |= Q(patient__assigned_to=self.request.user) return self.queryset.filter(applied_filters) diff --git a/care/facility/api/viewsets/patient_investigation.py b/care/facility/api/viewsets/patient_investigation.py index bb9682abff..2f977f858c 100644 --- a/care/facility/api/viewsets/patient_investigation.py +++ b/care/facility/api/viewsets/patient_investigation.py @@ -139,7 +139,7 @@ def get_queryset(self): ) allowed_facilities = get_accessible_facilities(self.request.user) filters = Q(consultation__patient__facility_id__in=allowed_facilities) - filters |= Q(consultation__assigned_to=self.request.user) + filters |= Q(consultation__assigned_clinicians=self.request.user) filters |= Q(consultation__patient__assigned_to=self.request.user) return queryset.filter(filters) @@ -184,7 +184,7 @@ def get_queryset(self): filters = Q( consultation__patient__facility__users__id__exact=self.request.user.id ) - filters |= Q(consultation__assigned_to=self.request.user) + filters |= Q(consultation__assigned_clinicians=self.request.user) filters |= Q(consultation__patient__assigned_to=self.request.user) return queryset.filter(filters).distinct("id") diff --git a/care/facility/migrations/0416_auto_20240220_1233.py b/care/facility/migrations/0416_auto_20240220_1233.py new file mode 100644 index 0000000000..8c45869173 --- /dev/null +++ b/care/facility/migrations/0416_auto_20240220_1233.py @@ -0,0 +1,38 @@ +# Generated by Django 4.2.8 on 2024-02-20 07:03 + +from django.db import migrations + + +def transfer_assigned_to_to_clinicians(apps, schema_editor): + PatientConsultation = apps.get_model("facility", "PatientConsultation") + + for patient_consultation in PatientConsultation.objects.all(): + if patient_consultation.assigned_to: + # Direct assignment to ManyToManyField not possible; need to use .add() + patient_consultation.assigned_clinicians.add( + patient_consultation.assigned_to + ) + + +def reverse_transfer(apps, schema_editor): + PatientConsultation = apps.get_model("facility", "PatientConsultation") + consultations_to_update = [] + + for consultation in PatientConsultation.objects.prefetch_related( + "assigned_clinicians" + ).all(): + clinicians = list(consultation.assigned_clinicians.all()) + consultation.assigned_to = clinicians[0] if clinicians else None + consultations_to_update.append(consultation) + + PatientConsultation.objects.bulk_update(consultations_to_update, ["assigned_to"]) + + +class Migration(migrations.Migration): + dependencies = [ + ("facility", "0415_patientconsultation_assigned_clinicians"), + ] + + operations = [ + migrations.RunPython(transfer_assigned_to_to_clinicians, reverse_transfer), + ] diff --git a/care/facility/models/daily_round.py b/care/facility/models/daily_round.py index 7140623e37..ad9cb16414 100644 --- a/care/facility/models/daily_round.py +++ b/care/facility/models/daily_round.py @@ -531,7 +531,7 @@ def has_read_permission(request): return request.user.is_superuser or ( (request.user in consultation.patient.facility.users.all()) or ( - request.user == consultation.assigned_to + consultation.assigned_clinicians.filter(id=request.user.id).exists() or request.user == consultation.patient.assigned_to ) or ( @@ -566,7 +566,7 @@ def has_object_read_permission(self, request): and request.user in self.consultation.patient.facility.users.all() ) or ( - self.consultation.assigned_to == request.user + self.assigned_clinicians.filter(id=request.user.id).exists() or request.user == self.consultation.patient.assigned_to ) or ( diff --git a/care/facility/models/mixins/permissions/patient.py b/care/facility/models/mixins/permissions/patient.py index 9cbd335df2..3ca9552a51 100644 --- a/care/facility/models/mixins/permissions/patient.py +++ b/care/facility/models/mixins/permissions/patient.py @@ -20,7 +20,9 @@ def has_object_read_permission(self, request): doctor_allowed = False if self.last_consultation: doctor_allowed = ( - self.last_consultation.assigned_to == request.user + self.last_consultation.assigned_clinicians.filter( + id=request.user.id + ).exists() or request.user == self.assigned_to ) return request.user.is_superuser or ( @@ -60,7 +62,9 @@ def has_object_write_permission(self, request): doctor_allowed = False if self.last_consultation: doctor_allowed = ( - self.last_consultation.assigned_to == request.user + self.last_consultation.assigned_clinicians.filter( + id=request.user.id + ).exists() or request.user == self.assigned_to ) @@ -99,7 +103,9 @@ def has_object_update_permission(self, request): doctor_allowed = False if self.last_consultation: doctor_allowed = ( - self.last_consultation.assigned_to == request.user + self.last_consultation.assigned_clinicians.filter( + id=request.user.id + ).exists() or request.user == self.assigned_to ) diff --git a/care/facility/models/patient_consultation.py b/care/facility/models/patient_consultation.py index c37622196f..74309d8225 100644 --- a/care/facility/models/patient_consultation.py +++ b/care/facility/models/patient_consultation.py @@ -166,7 +166,7 @@ class PatientConsultation(PatientBaseModel, ConsultationRelatedPermissionMixin): on_delete=models.SET_NULL, null=True, related_name="patient_assigned_to", - ) + ) # Deprecated assigned_clinicians = models.ManyToManyField( User, related_name="patient_assigned_clinician" @@ -303,16 +303,21 @@ def has_write_permission(request): def has_object_read_permission(self, request): if not super().has_object_read_permission(request): return False + + is_assigned_clinician = self.assigned_clinicians.filter( + id=request.user.id + ).exists() + is_patient_assigned_to = ( + request.user == self.patient.assigned_to + ) # patient.assigned_to is for assigning volunteer to patient + return ( request.user.is_superuser or ( self.patient.facility and request.user in self.patient.facility.users.all() ) - or ( - self.assigned_to == request.user - or request.user == self.patient.assigned_to - ) + or (is_assigned_clinician or is_patient_assigned_to) or ( request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"] and ( diff --git a/care/users/api/viewsets/users.py b/care/users/api/viewsets/users.py index 9fa5651f53..272a0d4826 100644 --- a/care/users/api/viewsets/users.py +++ b/care/users/api/viewsets/users.py @@ -63,8 +63,13 @@ def get_user_type( value, ): if value: - if value in INVERSE_USER_TYPE: - return queryset.filter(user_type=INVERSE_USER_TYPE[value]) + user_types = value.split(",") + filtered_query = queryset.filter( + user_type__in=[ + INVERSE_USER_TYPE.get(user_type) for user_type in user_types + ] + ) + return filtered_query return queryset user_type = filters.CharFilter(method="get_user_type", field_name="user_type") diff --git a/care/utils/notification_handler.py b/care/utils/notification_handler.py index bbb910fb7f..107b9a2283 100644 --- a/care/utils/notification_handler.py +++ b/care/utils/notification_handler.py @@ -144,23 +144,23 @@ def deserialize_extra_data(self, extra_data): def generate_extra_users(self): if isinstance(self.caused_object, PatientConsultation): - if self.caused_object.assigned_to: - self.extra_users.append(self.caused_object.assigned_to.id) + for clinician in self.caused_object.assigned_clinicians.all(): + self.extra_users.append(clinician.id) 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 - ) + for ( + clinician + ) in self.caused_object.last_consultation.assigned_clinicians.all(): + self.extra_users.append(clinician.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) + for clinician in self.extra_data["consultation"].assigned_clinicians.all(): + self.extra_users.append(clinician.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) + for clinician in self.caused_object.consultation.assigned_clinicians.all(): + self.extra_users.append(clinician.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) + for clinician in self.caused_object.consultation.assigned_clinicians.all(): + self.extra_users.append(clinician.id) def generate_system_message(self): message = "" diff --git a/care/utils/queryset/consultation.py b/care/utils/queryset/consultation.py index 49dc632d77..1ec55aa99c 100644 --- a/care/utils/queryset/consultation.py +++ b/care/utils/queryset/consultation.py @@ -23,7 +23,7 @@ def get_consultation_queryset(user): allowed_facilities = get_accessible_facilities(user) q_filters = Q(facility__id__in=allowed_facilities) q_filters |= Q(patient__facility__id__in=allowed_facilities) - q_filters |= Q(assigned_to=user) + q_filters |= Q(assigned_clinicians=user) q_filters |= Q(patient__assigned_to=user) queryset = queryset.filter(q_filters) return queryset diff --git a/care/utils/queryset/patient.py b/care/utils/queryset/patient.py index dfade629d9..fa12b63cee 100644 --- a/care/utils/queryset/patient.py +++ b/care/utils/queryset/patient.py @@ -15,7 +15,7 @@ def get_patient_queryset(user): queryset = queryset.filter(facility__district=user.district) else: q_filters = Q(facility__id=user.home_facility) - q_filters |= Q(last_consultation__assigned_to=user) + q_filters |= Q(last_consultation__assigned_clinicians=user) q_filters |= Q(assigned_to=user) queryset = queryset.filter(q_filters) return queryset @@ -32,7 +32,7 @@ def get_patient_notes_queryset(user): else: allowed_facilities = get_accessible_facilities(user) - q_filters = Q(last_consultation__assigned_to=user) + q_filters = Q(last_consultation__assigned_clinicians=user) q_filters |= Q(assigned_to=user) if user.user_type >= User.TYPE_VALUE_MAP["Doctor"]: q_filters |= Q(facility__id__in=allowed_facilities) From 2b9a6c0165b01f35de89a388e1e649d2ad3ee55e Mon Sep 17 00:00:00 2001 From: Ashesh3 <3626859+Ashesh3@users.noreply.github.com> Date: Wed, 21 Feb 2024 20:18:31 +0530 Subject: [PATCH 3/8] Optimize queries --- care/facility/api/serializers/daily_round.py | 6 ++---- care/facility/models/daily_round.py | 4 +++- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/care/facility/api/serializers/daily_round.py b/care/facility/api/serializers/daily_round.py index 05b69631b0..7891c53ace 100644 --- a/care/facility/api/serializers/daily_round.py +++ b/care/facility/api/serializers/daily_round.py @@ -141,9 +141,8 @@ def update(self, instance, validated_data): patient.save() validated_data["last_updated_by_telemedicine"] = False - if ( + if instance.consultation.assigned_clinicians.contains( self.context["request"].user - in instance.consultation.assigned_clinicians.all() ): validated_data["last_updated_by_telemedicine"] = True instance.consultation.save(update_fields=["last_updated_by_telemedicine"]) @@ -272,9 +271,8 @@ def create(self, validated_data): validated_data["created_by_telemedicine"] = False validated_data["last_updated_by_telemedicine"] = False - if ( + if validated_data["consultation"].assigned_clinicians.contains( self.context["request"].user - in validated_data["consultation"].assigned_clinicians.all() ): validated_data["created_by_telemedicine"] = True validated_data["last_updated_by_telemedicine"] = True diff --git a/care/facility/models/daily_round.py b/care/facility/models/daily_round.py index ad9cb16414..540ca0e081 100644 --- a/care/facility/models/daily_round.py +++ b/care/facility/models/daily_round.py @@ -566,7 +566,9 @@ def has_object_read_permission(self, request): and request.user in self.consultation.patient.facility.users.all() ) or ( - self.assigned_clinicians.filter(id=request.user.id).exists() + self.consultation.assigned_clinicians.filter( + id=request.user.id + ).exists() or request.user == self.consultation.patient.assigned_to ) or ( From 98fb9d50bac941ac25b7013eb3b61668ea601fa2 Mon Sep 17 00:00:00 2001 From: Ashesh3 <3626859+Ashesh3@users.noreply.github.com> Date: Tue, 19 Mar 2024 16:17:14 +0530 Subject: [PATCH 4/8] update migrations --- .../{0416_auto_20240220_1233.py => 0422_auto_20240220_1233.py} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename care/facility/migrations/{0416_auto_20240220_1233.py => 0422_auto_20240220_1233.py} (94%) diff --git a/care/facility/migrations/0416_auto_20240220_1233.py b/care/facility/migrations/0422_auto_20240220_1233.py similarity index 94% rename from care/facility/migrations/0416_auto_20240220_1233.py rename to care/facility/migrations/0422_auto_20240220_1233.py index 8c45869173..44fad4a0f4 100644 --- a/care/facility/migrations/0416_auto_20240220_1233.py +++ b/care/facility/migrations/0422_auto_20240220_1233.py @@ -30,7 +30,7 @@ def reverse_transfer(apps, schema_editor): class Migration(migrations.Migration): dependencies = [ - ("facility", "0415_patientconsultation_assigned_clinicians"), + ("facility", "0421_merge_20240318_1434"), ] operations = [ From 73df661fbbede88e440051b9e8507d4eae970267 Mon Sep 17 00:00:00 2001 From: Ashesh3 <3626859+Ashesh3@users.noreply.github.com> Date: Thu, 21 Mar 2024 14:01:08 +0530 Subject: [PATCH 5/8] Remove assigned_clinicians field from PatientConsultation model and transfer assigned_to values to ConsultationClinician model --- ...patientconsultation_assigned_clinicians.py | 21 ------------------- .../migrations/0422_auto_20240220_1233.py | 19 +++++++++++------ care/facility/models/patient_consultation.py | 4 ---- care/users/api/viewsets/users.py | 1 + 4 files changed, 14 insertions(+), 31 deletions(-) delete mode 100644 care/facility/migrations/0415_patientconsultation_assigned_clinicians.py diff --git a/care/facility/migrations/0415_patientconsultation_assigned_clinicians.py b/care/facility/migrations/0415_patientconsultation_assigned_clinicians.py deleted file mode 100644 index 3ea1661228..0000000000 --- a/care/facility/migrations/0415_patientconsultation_assigned_clinicians.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 4.2.8 on 2024-02-20 06:57 - -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ("facility", "0414_remove_bed_old_name"), - ] - - operations = [ - migrations.AddField( - model_name="patientconsultation", - name="assigned_clinicians", - field=models.ManyToManyField( - related_name="patient_assigned_clinician", to=settings.AUTH_USER_MODEL - ), - ), - ] diff --git a/care/facility/migrations/0422_auto_20240220_1233.py b/care/facility/migrations/0422_auto_20240220_1233.py index 44fad4a0f4..7cf8dc0b67 100644 --- a/care/facility/migrations/0422_auto_20240220_1233.py +++ b/care/facility/migrations/0422_auto_20240220_1233.py @@ -5,13 +5,20 @@ def transfer_assigned_to_to_clinicians(apps, schema_editor): PatientConsultation = apps.get_model("facility", "PatientConsultation") + ConsultationClinician = apps.get_model("facility", "ConsultationClinician") + clinician_links = [] - for patient_consultation in PatientConsultation.objects.all(): - if patient_consultation.assigned_to: - # Direct assignment to ManyToManyField not possible; need to use .add() - patient_consultation.assigned_clinicians.add( - patient_consultation.assigned_to - ) + consultations = PatientConsultation.objects.filter( + assigned_to__isnull=False + ).select_related("assigned_to") + + for consultation in consultations: + link = ConsultationClinician( + consultation=consultation, clinician=consultation.assigned_to + ) + clinician_links.append(link) + + ConsultationClinician.objects.bulk_create(clinician_links) def reverse_transfer(apps, schema_editor): diff --git a/care/facility/models/patient_consultation.py b/care/facility/models/patient_consultation.py index 293d948c47..69ccaf45c9 100644 --- a/care/facility/models/patient_consultation.py +++ b/care/facility/models/patient_consultation.py @@ -168,10 +168,6 @@ class PatientConsultation(PatientBaseModel, ConsultationRelatedPermissionMixin): related_name="patient_assigned_to", ) # Deprecated - assigned_clinicians = models.ManyToManyField( - User, related_name="patient_assigned_clinician" - ) - assigned_clinicians = models.ManyToManyField( User, related_name="patient_assigned_clinician", diff --git a/care/users/api/viewsets/users.py b/care/users/api/viewsets/users.py index 16c8e7a409..b5932fe596 100644 --- a/care/users/api/viewsets/users.py +++ b/care/users/api/viewsets/users.py @@ -62,6 +62,7 @@ def get_user_type( field_name, value, ): + # TODO: use https://django-filter.readthedocs.io/en/stable/ref/filters.html#multiplechoicefilter if value: user_types = value.split(",") filtered_query = queryset.filter( From 0951a557cdf096fceea1936789b5c01897ddea67 Mon Sep 17 00:00:00 2001 From: Ashesh3 <3626859+Ashesh3@users.noreply.github.com> Date: Sun, 31 Mar 2024 19:23:28 +0530 Subject: [PATCH 6/8] Refactor generate_extra_users method to optimize database queries --- care/utils/notification_handler.py | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/care/utils/notification_handler.py b/care/utils/notification_handler.py index 107b9a2283..eac9581c76 100644 --- a/care/utils/notification_handler.py +++ b/care/utils/notification_handler.py @@ -143,23 +143,40 @@ def deserialize_extra_data(self, extra_data): return extra_data def generate_extra_users(self): + assigned_clinicians_fields = ("id", "local_body", "district", "state") if isinstance(self.caused_object, PatientConsultation): - for clinician in self.caused_object.assigned_clinicians.all(): + for clinician in self.caused_object.assigned_clinicians.all().only( + *assigned_clinicians_fields + ): self.extra_users.append(clinician.id) if isinstance(self.caused_object, PatientRegistration): if self.caused_object.last_consultation: for ( clinician - ) in self.caused_object.last_consultation.assigned_clinicians.all(): + ) in self.caused_object.last_consultation.assigned_clinicians.all().only( + *assigned_clinicians_fields + ): self.extra_users.append(clinician.id) if isinstance(self.caused_object, InvestigationSession): - for clinician in self.extra_data["consultation"].assigned_clinicians.all(): + for clinician in ( + self.extra_data["consultation"] + .assigned_clinicians.all() + .only(*assigned_clinicians_fields) + ): self.extra_users.append(clinician.id) if isinstance(self.caused_object, InvestigationValue): - for clinician in self.caused_object.consultation.assigned_clinicians.all(): + for ( + clinician + ) in self.caused_object.consultation.assigned_clinicians.all().only( + *assigned_clinicians_fields + ): self.extra_users.append(clinician.id) if isinstance(self.caused_object, DailyRound): - for clinician in self.caused_object.consultation.assigned_clinicians.all(): + for ( + clinician + ) in self.caused_object.consultation.assigned_clinicians.all().only( + *assigned_clinicians_fields + ): self.extra_users.append(clinician.id) def generate_system_message(self): From 36a233e678ad2febc47ef2d26c5bf8f87979c750 Mon Sep 17 00:00:00 2001 From: Ashesh3 <3626859+Ashesh3@users.noreply.github.com> Date: Sun, 31 Mar 2024 19:24:30 +0530 Subject: [PATCH 7/8] Update migrations --- .../{0422_auto_20240220_1233.py => 0424_auto_20240220_1233.py} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename care/facility/migrations/{0422_auto_20240220_1233.py => 0424_auto_20240220_1233.py} (95%) diff --git a/care/facility/migrations/0422_auto_20240220_1233.py b/care/facility/migrations/0424_auto_20240220_1233.py similarity index 95% rename from care/facility/migrations/0422_auto_20240220_1233.py rename to care/facility/migrations/0424_auto_20240220_1233.py index 7cf8dc0b67..03247707b8 100644 --- a/care/facility/migrations/0422_auto_20240220_1233.py +++ b/care/facility/migrations/0424_auto_20240220_1233.py @@ -37,7 +37,7 @@ def reverse_transfer(apps, schema_editor): class Migration(migrations.Migration): dependencies = [ - ("facility", "0421_merge_20240318_1434"), + ("facility", "0423_patientconsultation_consent_records_and_more"), ] operations = [ From f9fe51259c05597cd8ab42a2b840000561275514 Mon Sep 17 00:00:00 2001 From: Ashesh3 <3626859+Ashesh3@users.noreply.github.com> Date: Wed, 24 Apr 2024 15:45:02 +0530 Subject: [PATCH 8/8] Update migration --- .../{0424_auto_20240220_1233.py => 0429_auto_20240220_1233.py} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename care/facility/migrations/{0424_auto_20240220_1233.py => 0429_auto_20240220_1233.py} (95%) diff --git a/care/facility/migrations/0424_auto_20240220_1233.py b/care/facility/migrations/0429_auto_20240220_1233.py similarity index 95% rename from care/facility/migrations/0424_auto_20240220_1233.py rename to care/facility/migrations/0429_auto_20240220_1233.py index 03247707b8..7a9185468d 100644 --- a/care/facility/migrations/0424_auto_20240220_1233.py +++ b/care/facility/migrations/0429_auto_20240220_1233.py @@ -37,7 +37,7 @@ def reverse_transfer(apps, schema_editor): class Migration(migrations.Migration): dependencies = [ - ("facility", "0423_patientconsultation_consent_records_and_more"), + ("facility", "0428_alter_patientmetainfo_occupation"), ] operations = [