From 0724813bcdcec8ab4fc30b9bfc0e0d24e9e430e4 Mon Sep 17 00:00:00 2001 From: Shivank Kacker Date: Fri, 31 May 2024 00:35:59 +0530 Subject: [PATCH] complete? --- care/facility/api/serializers/file_upload.py | 12 ++- .../api/serializers/patient_consultation.py | 39 ++++++--- care/facility/api/viewsets/file_upload.py | 16 ++-- .../api/viewsets/patient_consultation.py | 11 +++ ...ntconsultation_consent_records_and_more.py | 47 +++++++---- care/facility/models/file_upload.py | 2 +- care/facility/models/patient_consultation.py | 82 ++++++++++++++++--- config/api_router.py | 6 +- 8 files changed, 164 insertions(+), 51 deletions(-) diff --git a/care/facility/api/serializers/file_upload.py b/care/facility/api/serializers/file_upload.py index e991cf045a..399d7862ce 100644 --- a/care/facility/api/serializers/file_upload.py +++ b/care/facility/api/serializers/file_upload.py @@ -7,7 +7,10 @@ from care.facility.models.facility import Facility from care.facility.models.file_upload import FileUpload from care.facility.models.patient import PatientRegistration -from care.facility.models.patient_consultation import PatientConsultation +from care.facility.models.patient_consultation import ( + PatientConsent, + PatientConsultation, +) from care.facility.models.patient_sample import PatientSample from care.users.api.serializers.user import UserBaseMinimumSerializer from care.users.models import User @@ -53,9 +56,9 @@ def check_permissions(file_type, associating_id, user, action="create"): raise Exception("No Permission") return consultation.id elif file_type == FileUpload.FileType.CONSENT_RECORD.value: - consultation = PatientConsultation.objects.get( - consent_records__contains=[{"id": associating_id}] - ) + consultation = PatientConsent.objects.get( + external_id=associating_id + ).consultation if consultation.discharge_date and not action == "read": raise serializers.ValidationError( { @@ -173,6 +176,7 @@ class Meta: fields = ( "id", "name", + "associating_id", "uploaded_by", "archived_by", "archived_datetime", diff --git a/care/facility/api/serializers/patient_consultation.py b/care/facility/api/serializers/patient_consultation.py index bc977667c7..216e22c8a4 100644 --- a/care/facility/api/serializers/patient_consultation.py +++ b/care/facility/api/serializers/patient_consultation.py @@ -873,6 +873,7 @@ class Meta: class PatientConsentSerializer(serializers.ModelSerializer): id = serializers.CharField(source="external_id", read_only=True) created_by = UserBaseMinimumSerializer(read_only=True) + archived_by = UserBaseMinimumSerializer(read_only=True) class Meta: model = PatientConsent @@ -882,6 +883,8 @@ class Meta: "type", "patient_code_status", "archived", + "archived_by", + "archived_date", "created_by", "created_date", ) @@ -890,19 +893,31 @@ class Meta: "id", "created_by", "created_date", + "archived", + "archived_by", + "archived_date", ) - def update(self, instance, validated_data): - # check if the consent has been archived, if so, cascade archive all files - if instance.archived: - for file in instance.files.all(): - file.is_archived = True - file.archive_reason = instance.archived_reason - file.archive_datetime = timezone.now() - file.save() + def clear_existing_records(self, consultation, type, self_id=None): + consents = PatientConsent.objects.filter( + consultation=consultation, type=type + ).exclude(id=self_id) + + # looping because .update does not call model save method + for consent in consents: + consent.archived = True + consent.archived_by = self.context["request"].user + consent.archived_date = timezone.now() + consent.save() - return super().update(instance, validated_data) + def create(self, validated_data): + self.clear_existing_records( + consultation=validated_data["consultation"], type=validated_data["type"] + ) + return super().create(validated_data) - def create(self, validated_data): - validated_data["created_by"] = self.context["request"].user - return super().create(validated_data) + def update(self, instance, validated_data): + self.clear_existing_records( + consultation=instance.consultation, type=instance.type, self_id=instance.id + ) + return super().update(instance, validated_data) diff --git a/care/facility/api/viewsets/file_upload.py b/care/facility/api/viewsets/file_upload.py index 666eac03c4..2f9ad882c5 100644 --- a/care/facility/api/viewsets/file_upload.py +++ b/care/facility/api/viewsets/file_upload.py @@ -83,13 +83,19 @@ def get_queryset(self): {"associating_id": "associating_id missing in request params"} ) file_type = self.request.GET["file_type"] - associating_id = self.request.GET["associating_id"] + associating_ids = self.request.GET["associating_id"].split(",") if file_type not in FileUpload.FileType.__members__: raise ValidationError({"file_type": "invalid file type"}) file_type = FileUpload.FileType[file_type].value - associating_internal_id = check_permissions( - file_type, associating_id, self.request.user, "read" - ) + + associating_internal_ids = [] + + for associating_id in associating_ids: + associating_internal_id = check_permissions( + file_type, associating_id, self.request.user, "read" + ) + associating_internal_ids.append(associating_internal_id) + return self.queryset.filter( - file_type=file_type, associating_id=associating_internal_id + file_type=file_type, associating_id__in=associating_internal_ids ) diff --git a/care/facility/api/viewsets/patient_consultation.py b/care/facility/api/viewsets/patient_consultation.py index 463dd240bd..6f7fdc61b1 100644 --- a/care/facility/api/viewsets/patient_consultation.py +++ b/care/facility/api/viewsets/patient_consultation.py @@ -309,3 +309,14 @@ class PatientConsentViewSet( ) queryset = PatientConsent.objects.all().select_related("consultation") filter_backends = (filters.DjangoFilterBackend,) + + filterset_fields = ("archived",) + + def get_queryset(self): + consultation_id = self.kwargs.get("consultation_external_id", None) + return self.queryset.filter(consultation__external_id=consultation_id) + + def perform_create(self, serializer): + consultation_id = self.kwargs.get("consultation_external_id", None) + consultation = PatientConsultation.objects.get(external_id=consultation_id) + serializer.save(consultation=consultation, created_by=self.request.user) diff --git a/care/facility/migrations/0441_remove_patientconsultation_consent_records_and_more.py b/care/facility/migrations/0441_remove_patientconsultation_consent_records_and_more.py index 6cca0a333e..bdaddb3b51 100644 --- a/care/facility/migrations/0441_remove_patientconsultation_consent_records_and_more.py +++ b/care/facility/migrations/0441_remove_patientconsultation_consent_records_and_more.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.10 on 2024-05-30 06:33 +# Generated by Django 4.2.10 on 2024-05-30 16:35 import uuid @@ -7,6 +7,7 @@ from django.db import migrations, models from django.utils import timezone +import care.facility.models.mixins.permissions.patient from care.facility.models.file_upload import FileUpload as FileUploadModel from care.facility.models.patient_consultation import ( PatientConsent as PatientConsentModel, @@ -60,8 +61,6 @@ def migrate_consents(apps, schema_editor): files.update(**kwargs) - new_consent.files.add(*files) - def reverse_migrate(apps, schema_editor): PatientConsent: PatientConsentModel = apps.get_model( "facility", "PatientConsent" @@ -121,9 +120,9 @@ def reverse_migrate(apps, schema_editor): "type", models.IntegerField( choices=[ - (1, "Consent For Admission"), + (1, "Consent for Admission"), (2, "Patient Code Status"), - (3, "Consent For Procedure"), + (3, "Consent for Procedure"), (4, "High Risk Consent"), (5, "Others"), ] @@ -134,15 +133,26 @@ def reverse_migrate(apps, schema_editor): models.IntegerField( blank=True, choices=[ - (1, "Dnh"), - (2, "Dnr"), - (3, "Comfort Care"), + (1, "Do Not Hospitalize"), + (2, "Do Not Resuscitate"), + (3, "Comfort Care Only"), (4, "Active Treatment"), ], null=True, ), ), ("archived", models.BooleanField(default=False)), + ("archived_date", models.DateTimeField(blank=True, null=True)), + ( + "archived_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="archived_consents", + to=settings.AUTH_USER_MODEL, + ), + ), ( "consultation", models.ForeignKey( @@ -158,16 +168,19 @@ def reverse_migrate(apps, schema_editor): to=settings.AUTH_USER_MODEL, ), ), - ( - "files", - models.ManyToManyField( - related_name="consents", to="facility.fileupload" - ), - ), ], - options={ - "abstract": False, - }, + bases=( + models.Model, + care.facility.models.mixins.permissions.patient.ConsultationRelatedPermissionMixin, + ), + ), + migrations.AddConstraint( + model_name="patientconsent", + constraint=models.UniqueConstraint( + condition=models.Q(("archived", False)), + fields=("consultation", "type"), + name="unique_consultation_consent", + ), ), migrations.RunPython(migrate_consents, reverse_code=reverse_migrate), migrations.RemoveField( diff --git a/care/facility/models/file_upload.py b/care/facility/models/file_upload.py index c09f3814c3..5ac205f82c 100644 --- a/care/facility/models/file_upload.py +++ b/care/facility/models/file_upload.py @@ -164,4 +164,4 @@ class FileType(models.IntegerChoices): FileCategoryChoices = [(x.value, x.name) for x in BaseFileUpload.FileCategory] def __str__(self): - return f"{self.FileTypeChoices[self.file_type][1]} - {self.name}" + return f"{self.FileTypeChoices[self.file_type][1]} - {self.name}{' (Archived)' if self.is_archived else ''}" diff --git a/care/facility/models/patient_consultation.py b/care/facility/models/patient_consultation.py index bd6e86de52..c5398e0a8f 100644 --- a/care/facility/models/patient_consultation.py +++ b/care/facility/models/patient_consultation.py @@ -390,30 +390,90 @@ class ConsultationClinician(models.Model): ) -class PatientConsent(BaseModel): +class PatientConsent(BaseModel, ConsultationRelatedPermissionMixin): consultation = models.ForeignKey(PatientConsultation, on_delete=models.CASCADE) type = models.IntegerField(choices=ConsentType.choices) patient_code_status = models.IntegerField( choices=PatientCodeStatusType.choices, null=True, blank=True ) archived = models.BooleanField(default=False) - files = models.ManyToManyField(FileUpload, related_name="consents") + archived_by = models.ForeignKey( + User, + on_delete=models.PROTECT, + null=True, + blank=True, + related_name="archived_consents", + ) + archived_date = models.DateTimeField(null=True, blank=True) created_by = models.ForeignKey( User, on_delete=models.PROTECT, related_name="created_consents" ) + class Meta: + constraints = [ + models.UniqueConstraint( + fields=["consultation", "type"], + name="unique_consultation_consent", + condition=models.Q(archived=False), + ) + ] + def __str__(self) -> str: - return f"{self.consultation.patient.name} - {ConsentType(self.type).label}" + return f"{self.consultation.patient.name} - {ConsentType(self.type).label}{' (Archived)' if self.archived else ''}" def save(self, *args, **kwargs): - # archive existing patient code status consent - if self.pk is None and self.type == ConsentType.PATIENT_CODE_STATUS: - existing_pcs = PatientConsent.objects.filter( - consultation=self.consultation, - type=ConsentType.PATIENT_CODE_STATUS, - archived=False, + if self.archived: + files = FileUpload.objects.filter( + associating_id=self.external_id, + is_archived=False, + ) + files.update( + is_archived=True, + archived_datetime=timezone.now(), + archive_reason="Consent Archived", + archived_by=self.archived_by, ) - if existing_pcs.exists(): - existing_pcs.update(archived=True) super().save(*args, **kwargs) + + @staticmethod + def has_write_permission(request): + return request.user.is_superuser or ( + request.user.verified + and ConsultationRelatedPermissionMixin.has_write_permission(request) + ) + + def has_object_read_permission(self, request): + if not super().has_object_read_permission(request): + return False + return ( + request.user.is_superuser + or ( + self.patient.facility + and request.user in self.consultation.patient.facility.users.all() + ) + or ( + self.consultation.assigned_to == request.user + or request.user == self.consultation.patient.assigned_to + ) + or ( + request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"] + and ( + self.consultation.patient.facility + and request.user.district + == self.consultation.patient.facility.district + ) + ) + or ( + request.user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"] + and ( + self.consultation.patient.facility + and request.user.state == self.consultation.patient.facility.state + ) + ) + ) + + def has_object_update_permission(self, request): + return super().has_object_update_permission( + request + ) and self.has_object_read_permission(request) diff --git a/config/api_router.py b/config/api_router.py index ab8787fcc2..746c7cd4ac 100644 --- a/config/api_router.py +++ b/config/api_router.py @@ -59,7 +59,10 @@ PatientSearchViewSet, PatientViewSet, ) -from care.facility.api.viewsets.patient_consultation import PatientConsultationViewSet +from care.facility.api.viewsets.patient_consultation import ( + PatientConsentViewSet, + PatientConsultationViewSet, +) from care.facility.api.viewsets.patient_external_test import PatientExternalTestViewSet from care.facility.api.viewsets.patient_investigation import ( InvestigationGroupViewset, @@ -234,6 +237,7 @@ consultation_nested_router.register( r"prescription_administration", MedicineAdministrationViewSet ) +consultation_nested_router.register(r"consents", PatientConsentViewSet) consultation_nested_router.register(r"events", PatientConsultationEventViewSet) router.register("event_types", EventTypeViewSet)