Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions care/emr/api/otp_viewsets/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import json
from enum import Enum
from os import getenv

from django.conf import settings
from django.core.exceptions import FieldError
from django_filters.constants import EMPTY_VALUES
from rest_framework.exceptions import ValidationError

from care.emr.api.viewsets.base import EMRBaseViewSet
from config.patient_otp_authentication import (
JWTTokenPatientAuthentication,
OTPAuthenticatedPermission,
)


class OTPResourceType(str, Enum):
diagnostic_report = "diagnostic_report"
Comment thread
nandkishorr marked this conversation as resolved.
medication_request_prescription = "medication_request_prescription"


class OTPBaseViewset(EMRBaseViewSet):
"""
Base viewset for OTP authenticated endpoints.
"""

authentication_classes = [JWTTokenPatientAuthentication]
permission_classes = [OTPAuthenticatedPermission]


class QuerysetEnablerMixin:
"""
Mixin to enable queryset filtering based on the presence of a filterset_class attribute.
"""

resource_type = OTPResourceType

def config_key(self):
return f"OTP_{self.resource_type.value.upper()}_FILTERS"

Comment thread
nandkishorr marked this conversation as resolved.
def get_env_value(self, key):
config = getenv(key)
if not config:
return {}
try:
return json.loads(config)
except json.JSONDecodeError as e:
raise ValidationError(
{key: "Invalid JSON in default filter configuration."}
) from e

def get_read_filters(self):
return self.get_env_value(self.config_key())

def apply_default_filters(self, queryset):
read_filters = self.get_read_filters()
if not read_filters:
return queryset.none()
query_params = self.request.query_params
allowed_filters = set(getattr(self.filterset_class, "base_filters", {}))
for filter_config in read_filters:
filter_name = filter_config.get("name")
properties = filter_config.get("properties", {})
if not filter_name or filter_name in query_params:
continue
if filter_name not in allowed_filters:
raise ValidationError({filter_name: "Invalid filter"})
value = properties.get("value")
if value in EMPTY_VALUES:
continue

field_name = properties.get("field_name", filter_name)
lookup_expr = properties.get("lookup_expr", "exact")
try:
queryset = queryset.filter(**{f"{field_name}__{lookup_expr}": value})
except FieldError as e:
raise ValidationError(
{filter_name: "Invalid default filter configuration"}
) from e
return queryset

def get_queryset(self):
if not settings.OTP_QUERYSET_ENABLED:
return self.database_model.objects.none()
queryset = super().get_queryset()
if self.action == "list":
return self.apply_default_filters(queryset)
return queryset
40 changes: 40 additions & 0 deletions care/emr/api/otp_viewsets/diagnostic_report.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from django_filters import rest_framework as filters

from care.emr.api.otp_viewsets.base import (
OTPBaseViewset,
OTPResourceType,
QuerysetEnablerMixin,
)
from care.emr.api.viewsets.base import EMRListMixin, EMRRetrieveMixin
from care.emr.models.diagnostic_report import DiagnosticReport
from care.emr.resources.diagnostic_report.spec import (
DiagnosticReportListSpec,
DiagnosticReportRetrieveSpec,
)
from care.utils.filters.multiselect import MultiSelectFilter


class OTPDiagnosticReportFilters(filters.FilterSet):
facility = filters.UUIDFilter(field_name="facility__external_id")
status = MultiSelectFilter(field_name="status")
encounter = filters.UUIDFilter(field_name="encounter__external_id")
patient = filters.UUIDFilter(field_name="patient__external_id")
created_date = filters.DateTimeFromToRangeFilter(field_name="created_date")


class OTPDiagnosticReportViewSet(
QuerysetEnablerMixin, EMRRetrieveMixin, OTPBaseViewset, EMRListMixin
):
database_model = DiagnosticReport
pydantic_read_model = DiagnosticReportListSpec
pydantic_retrieve_model = DiagnosticReportRetrieveSpec
filterset_class = OTPDiagnosticReportFilters
filter_backends = [filters.DjangoFilterBackend]
resource_type = OTPResourceType.diagnostic_report

def get_queryset(self):
return (
super()
.get_queryset()
.filter(patient__phone_number=self.request.user.phone_number)
)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Comment thread
nandkishorr marked this conversation as resolved.
43 changes: 43 additions & 0 deletions care/emr/api/otp_viewsets/medication_request_prescription.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from django_filters import rest_framework as filters

from care.emr.api.otp_viewsets.base import (
OTPBaseViewset,
OTPResourceType,
QuerysetEnablerMixin,
)
from care.emr.api.viewsets.base import EMRListMixin, EMRRetrieveMixin
from care.emr.models.medication_request import MedicationRequestPrescription
from care.emr.resources.medication.request_prescription.spec import (
MedicationRequestPrescriptionReadSpec,
MedicationRequestPrescriptionRetrieveMedicationsSpec,
)
from care.utils.filters.multiselect import MultiSelectFilter


class OTPMedicationRequestPrescriptionFilters(filters.FilterSet):
facility = filters.UUIDFilter(field_name="encounter__facility__external_id")
status = MultiSelectFilter(field_name="status")
encounter = filters.UUIDFilter(field_name="encounter__external_id")
patient = filters.UUIDFilter(field_name="patient__external_id")
created_date = filters.DateTimeFromToRangeFilter(field_name="created_date")


class OTPMedicationRequestPrescriptionViewSet(
QuerysetEnablerMixin,
EMRRetrieveMixin,
OTPBaseViewset,
EMRListMixin,
):
database_model = MedicationRequestPrescription
pydantic_read_model = MedicationRequestPrescriptionReadSpec
pydantic_retrieve_model = MedicationRequestPrescriptionRetrieveMedicationsSpec
filterset_class = OTPMedicationRequestPrescriptionFilters
filter_backends = [filters.DjangoFilterBackend]
resource_type = OTPResourceType.medication_request_prescription

def get_queryset(self):
return (
super()
.get_queryset()
.filter(patient__phone_number=self.request.user.phone_number)
)
Comment thread
nandkishorr marked this conversation as resolved.
11 changes: 3 additions & 8 deletions care/emr/api/otp_viewsets/patient.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
from care.emr.api.viewsets.base import EMRBaseViewSet, EMRCreateMixin, EMRListMixin
from care.emr.api.otp_viewsets.base import OTPBaseViewset
from care.emr.api.viewsets.base import EMRCreateMixin, EMRListMixin
from care.emr.models.patient import Patient
from care.emr.resources.patient.otp_based_flow import (
PatientOTPReadSpec,
PatientOTPWriteSpec,
)
from config.patient_otp_authentication import (
JWTTokenPatientAuthentication,
OTPAuthenticatedPermission,
)


class PatientOTPView(EMRCreateMixin, EMRListMixin, EMRBaseViewSet):
authentication_classes = [JWTTokenPatientAuthentication]
permission_classes = [OTPAuthenticatedPermission]
class PatientOTPView(EMRCreateMixin, EMRListMixin, OTPBaseViewset):
pydantic_model = PatientOTPWriteSpec
pydantic_read_model = PatientOTPReadSpec

Expand Down
17 changes: 17 additions & 0 deletions care/emr/resources/encounter/spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,23 @@ def perform_extra_serialization(cls, mapping, obj):
cls.serialize_audit_users(mapping, obj)


class OTPEncounterBaseSpec(BaseModel):
__model__ = Encounter
__exclude__ = [
"patient",
"organizations",
"facility",
"appointment",
"current_location",
"care_team",
]
id: UUID4 = None
status: StatusChoices
period: PeriodSpec = {}
priority: EncounterPriorityChoices
external_identifier: str | None = None


class EncounterCareTeamMemberSpec(BaseModel):
user_id: UUID4
role: ValueSetBoundCoding[PRACTITIONER_ROLE_VALUESET.slug]
Expand Down
7 changes: 7 additions & 0 deletions care/emr/resources/scheduling/slot/spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,11 +144,18 @@ def perform_extra_serialization(cls, mapping, obj):

class TokenBookingOTPReadSpec(TokenBookingBaseReadSpec):
patient: PatientOTPReadSpec
associated_encounter: dict = {}
Comment thread
nandkishorr marked this conversation as resolved.

@classmethod
def perform_extra_serialization(cls, mapping, obj):
from care.emr.resources.encounter.spec import OTPEncounterBaseSpec

super().perform_extra_serialization(mapping, obj)
mapping["patient"] = PatientOTPReadSpec.serialize(obj.patient).to_json()
if obj.associated_encounter_id:
mapping["associated_encounter"] = OTPEncounterBaseSpec.serialize(
obj.associated_encounter
).to_json()


class TokenBookingReadSpec(TokenBookingBaseReadSpec):
Expand Down
Loading
Loading