Skip to content

Commit

Permalink
Merge branch 'vigneshhari/health-details' into medication_administration
Browse files Browse the repository at this point in the history
  • Loading branch information
khavinshankar authored Dec 30, 2024
2 parents 9b6c03c + 66b0a74 commit 6078b73
Show file tree
Hide file tree
Showing 56 changed files with 1,788 additions and 289 deletions.
2 changes: 1 addition & 1 deletion care/emr/api/otp_viewsets/login.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ def login(self, request):
if not otp_object:
raise ValidationError({"otp": "Invalid OTP"})

# otp_object.is_used = True # TODO UNCOMMENT THIS !!
otp_object.is_used = True
otp_object.save()

token = PatientToken()
Expand Down
22 changes: 15 additions & 7 deletions care/emr/api/viewsets/allergy_intolerance.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from drf_spectacular.utils import extend_schema, extend_schema_view
from rest_framework.exceptions import PermissionDenied

from care.emr.api.viewsets.authz_base import EncounterBasedAuthorizationBase
from care.emr.api.viewsets.base import EMRModelViewSet, EMRQuestionnaireResponseMixin
from care.emr.models import Encounter
from care.emr.models.allergy_intolerance import AllergyIntolerance
Expand All @@ -15,6 +16,7 @@
AllergyIntrolanceSpecRead,
)
from care.emr.resources.questionnaire.spec import SubjectType
from care.security.authorization import AuthorizationController


class AllergyIntoleranceFilters(FilterSet):
Expand All @@ -24,7 +26,9 @@ class AllergyIntoleranceFilters(FilterSet):
@extend_schema_view(
create=extend_schema(request=AllergyIntoleranceSpec),
)
class AllergyIntoleranceViewSet(EMRQuestionnaireResponseMixin, EMRModelViewSet):
class AllergyIntoleranceViewSet(
EncounterBasedAuthorizationBase, EMRQuestionnaireResponseMixin, EMRModelViewSet
):
database_model = AllergyIntolerance
pydantic_model = AllergyIntoleranceSpec
pydantic_read_model = AllergyIntrolanceSpecRead
Expand All @@ -36,14 +40,18 @@ class AllergyIntoleranceViewSet(EMRQuestionnaireResponseMixin, EMRModelViewSet):
filterset_class = AllergyIntoleranceFilters
filter_backends = [DjangoFilterBackend]

def authorize_create(self, instance: AllergyIntoleranceSpec):
encounter = Encounter.objects.get(external_id=instance.encounter)
if str(encounter.patient.external_id) != self.kwargs["patient_external_id"]:
err = "Malformed request"
raise PermissionDenied(err)
# Check if the user has access to the patient and write access to the encounter
def validate_data(self, instance: AllergyIntoleranceSpec, model_instance=None):
if not model_instance:
encounter = Encounter.objects.get(external_id=instance.encounter)
if str(encounter.patient.external_id) != self.kwargs["patient_external_id"]:
err = "Malformed request"
raise PermissionDenied(err)

def get_queryset(self):
if not AuthorizationController.call(
"can_view_clinical_data", self.request.user, self.get_patient_obj()
):
raise PermissionDenied("Permission denied to user")
return (
super()
.get_queryset()
Expand Down
31 changes: 31 additions & 0 deletions care/emr/api/viewsets/authz_base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from rest_framework.exceptions import PermissionDenied
from rest_framework.generics import get_object_or_404

from care.emr.models import Encounter, Patient
from care.security.authorization import AuthorizationController


class EncounterBasedAuthorizationBase:
def get_patient_obj(self):
return get_object_or_404(
Patient, external_id=self.kwargs["patient_external_id"]
)

def authorize_update(self, request_obj, model_instance):
if not AuthorizationController.call(
"can_update_encounter_obj", self.request.user, model_instance.encounter
):
raise PermissionDenied("You do not have permission to update encounter")

def authorize_create(self, instance):
encounter = get_object_or_404(Encounter, external_id=instance.encounter)
if not AuthorizationController.call(
"can_update_encounter_obj", self.request.user, encounter
):
raise PermissionDenied("You do not have permission to update encounter")

def authorize_delete(self, instance):
if not AuthorizationController.call(
"can_update_encounter_obj", self.request.user, instance.encounter
):
raise PermissionDenied("You do not have permission to update encounter")
3 changes: 2 additions & 1 deletion care/emr/api/viewsets/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def json_schema_spec(self, *args, **kwargs):
class EMRRetrieveMixin:
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
data = self.get_retrieve_pydantic_model().serialize(instance)
data = self.get_retrieve_pydantic_model().serialize(instance, request.user)
return Response(data.to_json())


Expand Down Expand Up @@ -168,6 +168,7 @@ def handle_update(self, instance, request_data):
serializer_obj = pydantic_model.model_validate(
clean_data, context={"is_update": True, "object": instance}
)
self.validate_data(serializer_obj, instance)
self.authorize_update(serializer_obj, instance)
model_instance = serializer_obj.de_serialize(obj=instance)
self.perform_update(model_instance)
Expand Down
18 changes: 16 additions & 2 deletions care/emr/api/viewsets/condition.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.exceptions import PermissionDenied

from care.emr.api.viewsets.authz_base import EncounterBasedAuthorizationBase
from care.emr.api.viewsets.base import EMRModelViewSet, EMRQuestionnaireResponseMixin
from care.emr.models.condition import Condition
from care.emr.models.encounter import Encounter
Expand All @@ -14,6 +15,7 @@
ConditionSpecRead,
)
from care.emr.resources.questionnaire.spec import SubjectType
from care.security.authorization import AuthorizationController


class ConditionFilters(FilterSet):
Expand All @@ -25,7 +27,9 @@ class ConditionFilters(FilterSet):
severity = CharFilter(field_name="severity", lookup_expr="iexact")


class SymptomViewSet(EMRQuestionnaireResponseMixin, EMRModelViewSet):
class SymptomViewSet(
EncounterBasedAuthorizationBase, EMRQuestionnaireResponseMixin, EMRModelViewSet
):
database_model = Condition
pydantic_model = ConditionSpec
pydantic_read_model = ConditionSpecRead
Expand All @@ -51,6 +55,10 @@ def authorize_create(self, instance: ConditionSpec):

def get_queryset(self):
# Check if the user has read access to the patient and their EMR Data
if not AuthorizationController.call(
"can_view_clinical_data", self.request.user, self.get_patient_obj()
):
raise PermissionDenied("Permission denied to user")
return (
super()
.get_queryset()
Expand All @@ -65,7 +73,9 @@ def get_queryset(self):
InternalQuestionnaireRegistry.register(SymptomViewSet)


class DiagnosisViewSet(EMRQuestionnaireResponseMixin, EMRModelViewSet):
class DiagnosisViewSet(
EncounterBasedAuthorizationBase, EMRQuestionnaireResponseMixin, EMRModelViewSet
):
database_model = Condition
pydantic_model = ConditionSpec
pydantic_read_model = ConditionSpecRead
Expand All @@ -91,6 +101,10 @@ def authorize_create(self, instance: ConditionSpec):

def get_queryset(self):
# Check if the user has read access to the patient and their EMR Data
if not AuthorizationController.call(
"can_view_clinical_data", self.request.user, self.get_patient_obj()
):
raise PermissionDenied("Permission denied to user")
return (
super()
.get_queryset()
Expand Down
58 changes: 46 additions & 12 deletions care/emr/api/viewsets/encounter.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@
EMRRetrieveMixin,
EMRUpdateMixin,
)
from care.emr.models import Encounter, EncounterOrganization, FacilityOrganization
from care.emr.models import (
Encounter,
EncounterOrganization,
FacilityOrganization,
Patient,
)
from care.emr.resources.encounter.constants import COMPLETED_CHOICES
from care.emr.resources.encounter.spec import (
EncounterCreateSpec,
Expand All @@ -39,7 +44,6 @@ def filter(self, qs, value):


class EncounterFilters(filters.FilterSet):
patient = filters.UUIDFilter(field_name="patient__external_id")
facility = filters.UUIDFilter(field_name="facility__external_id")
status = filters.CharFilter(field_name="status", lookup_expr="iexact")
encounter_class = filters.CharFilter(
Expand Down Expand Up @@ -67,30 +71,27 @@ class EncounterViewSet(
filterset_class = EncounterFilters
filter_backends = [filters.DjangoFilterBackend]

def get_facility_obj(self):
return get_object_or_404(
Facility, external_id=self.kwargs["facility_external_id"]
)

def perform_create(self, instance):
with transaction.atomic():
organizations = getattr(instance, "_organizations", [])
super().perform_create(instance)
for organization in organizations:
EncounterOrganization.objects.create(
encounter=instance,
organization=FacilityOrganization.objects.get(
external_id=organization, facility=instance.facility
organization=get_object_or_404(
FacilityOrganization,
external_id=organization,
facility=instance.facility,
),
)
if not organizations:
instance.sync_organization_cache()

def authorize_update(self, request_obj, model_instance):
if not AuthorizationController.call(
"can_create_encounter_obj", self.request.user, model_instance.facility
"can_update_encounter_obj", self.request.user, model_instance
):
raise PermissionDenied("You do not have permission to create encounter")
raise PermissionDenied("You do not have permission to update encounter")

def authorize_create(self, instance):
# Check if encounter create permission exists on Facility Organization
Expand All @@ -101,16 +102,47 @@ def authorize_create(self, instance):
raise PermissionDenied("You do not have permission to create encounter")

def get_queryset(self):
return (
qs = (
super()
.get_queryset()
.select_related("patient", "facility", "appointment")
.order_by("-created_date")
)
if (
self.action in ["list", "retrieve"]
and "patient" in self.request.GET
and self.request.GET["patient"]
):
# If the user has view access to the patient, then encounter view is also granted for that patient
patient = get_object_or_404(
Patient, external_id=self.request.GET["patient"]
)
if AuthorizationController.call(
"can_view_patient_obj", self.request.user, patient
):
return qs.filter(patient=patient)
raise PermissionDenied("User Cannot access patient")

if (
self.action in ["list", "retrieve"]
and "facility" in self.request.GET
and self.request.GET["facility"]
):
# If the user has view access to the patient, then encounter view is also granted for that patient
facility = get_object_or_404(
Facility, external_id=self.request.GET["facility"]
)
return AuthorizationController.call(
"get_filtered_encounters", qs, self.request.user, facility
)
if self.action in ["list", "retrieve"]:
raise PermissionDenied("Cannot access encounters")
return qs # Authz Exists separately for update and deletes

@action(detail=True, methods=["GET"])
def organizations(self, request, *args, **kwargs):
instance = self.get_object()
self.authorize_update({}, instance)
encounter_organizations = EncounterOrganization.objects.filter(
encounter=instance
).select_related("organization")
Expand All @@ -128,6 +160,7 @@ class EncounterOrganizationManageSpec(BaseModel):
@action(detail=True, methods=["POST"])
def organizations_add(self, request, *args, **kwargs):
instance = self.get_object()
self.authorize_update({}, instance)
request_data = self.EncounterOrganizationManageSpec(**request.data)
organization = get_object_or_404(
FacilityOrganization, external_id=request_data.organization
Expand All @@ -147,6 +180,7 @@ def organizations_add(self, request, *args, **kwargs):
@action(detail=True, methods=["DELETE"])
def organizations_remove(self, request, *args, **kwargs):
instance = self.get_object()
self.authorize_update({}, instance)
request_data = self.EncounterOrganizationManageSpec(**request.data)
organization = get_object_or_404(
FacilityOrganization, external_id=request_data.organization
Expand Down
55 changes: 55 additions & 0 deletions care/emr/api/viewsets/facility.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
from django.db.models import Q
from django.utils.decorators import method_decorator
from rest_framework.decorators import action, parser_classes
from rest_framework.parsers import MultiPartParser
from rest_framework.response import Response

from care.emr.api.viewsets.base import EMRModelViewSet
from care.emr.models.organziation import FacilityOrganizationUser, OrganizationUser
from care.emr.resources.facility.spec import FacilityCreateSpec, FacilityReadSpec
from care.facility.api.serializers.facility import FacilityImageUploadSerializer
from care.facility.models import Facility
from care.utils.file_uploads.cover_image import delete_cover_image


class FacilityViewSet(EMRModelViewSet):
database_model = Facility
pydantic_model = FacilityCreateSpec
pydantic_read_model = FacilityReadSpec

def get_queryset(self):
# TODO Add Permission checks
organization_ids = list(
OrganizationUser.objects.filter(user=self.request.user).values_list(
"organization_id", flat=True
)
)
return (
super()
.get_queryset()
.filter(
Q(
id__in=FacilityOrganizationUser.objects.filter(
user=self.request.user
).values_list("organization__facility_id")
)
| Q(geo_organization_cache__overlap=organization_ids)
)
)

@method_decorator(parser_classes([MultiPartParser]))
@action(methods=["POST"], detail=True)
def cover_image(self, request, external_id):
facility = self.get_object()
serializer = FacilityImageUploadSerializer(facility, data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data)

@cover_image.mapping.delete
def cover_image_delete(self, *args, **kwargs):
facility = self.get_object()
delete_cover_image(facility.cover_image_url, "cover_images")
facility.cover_image_url = None
facility.save()
return Response(status=204)
Loading

0 comments on commit 6078b73

Please sign in to comment.