Skip to content
Closed
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
77ad843
added user model cache
praffq Jan 13, 2026
77015ed
added FacilityLocationListSpec model cache
praffq Jan 13, 2026
c54501c
added OrganizationReadSpec model cache
praffq Jan 13, 2026
4414f88
added FacilityOrganizationReadSpec model cache
praffq Jan 13, 2026
2e40804
Merge branch 'develop' into prafful/adding-model-caching-in-specs
praffq Jan 14, 2026
b571c98
coderabbit comments
praffq Jan 14, 2026
1e68dbe
coderabbit comments
praffq Jan 14, 2026
832f078
fix test cases
praffq Jan 14, 2026
414a2be
N+1 in /search and /search_retrieve endpoint
praffq Jan 21, 2026
ef9ce42
N+1 in /slots/get_slots_for_day/
praffq Jan 21, 2026
4098b87
N+1 in /getallfacilities/
praffq Jan 21, 2026
4dfb374
N+1 in /facility/
praffq Jan 21, 2026
48e6c47
N+1 in /available_users/
praffq Jan 21, 2026
5a0e5a2
N+1 in /api/v1/organization/{organization_external_id}/users/
praffq Jan 21, 2026
33853e9
Merge branch 'develop' into prafful/adding-model-caching-in-specs
praffq Jan 21, 2026
fbc57c5
minor fix from merge conflict
praffq Jan 21, 2026
afd3d75
remove FacilityLocationListSpec from cache
praffq Jan 23, 2026
f3b4eb7
Merge branch 'develop' into prafful/adding-model-caching-in-specs
praffq Jan 23, 2026
70b1bdc
Merge branch 'prafful/adding-model-caching-in-specs' into prafful/res…
praffq Jan 27, 2026
4369f60
fixes from discussion
praffq Jan 27, 2026
6bd9150
Merge branch 'develop' into prafful/resolve-n+1-issues
praffq Jan 27, 2026
1ae1c5e
comments
praffq Jan 27, 2026
9e2b2d8
Merge branch 'develop' into prafful/resolve-n+1-issues
praffq Jan 27, 2026
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
6 changes: 4 additions & 2 deletions care/emr/api/viewsets/facility.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ class FacilityViewSet(EMRModelViewSet):
filter_backends = [DjangoFilterBackend]

def get_queryset(self):
qs = super().get_queryset()
qs = super().get_queryset().select_related("geo_organization")
if self.request.user.is_superuser:
return qs
organization_ids = list(
Expand Down Expand Up @@ -217,4 +217,6 @@ class AllFacilityViewSet(EMRModelReadOnlyViewSet):
search_fields = ["name"]

def get_queryset(self):
return Facility.objects.filter(is_public=True).select_related()
return Facility.objects.filter(is_public=True).select_related(
"geo_organization"
)
6 changes: 3 additions & 3 deletions care/emr/api/viewsets/location.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,14 +176,14 @@ def get_queryset(self):
def organizations(self, request, *args, **kwargs):
# AuthZ is controlled from the get_queryset method, no need to repeat
instance = self.get_object()
encounter_organizations = FacilityLocationOrganization.objects.filter(
location_organizations = FacilityLocationOrganization.objects.filter(
location=instance
).select_related("organization")
data = [
FacilityOrganizationReadSpec.serialize(
encounter_organization.organization
location_organization.organization
).to_json()
for encounter_organization in encounter_organizations
for location_organization in location_organizations
]
return Response({"results": data})

Expand Down
4 changes: 3 additions & 1 deletion care/emr/api/viewsets/organization.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,4 +325,6 @@ def get_queryset(self):
raise PermissionDenied(
"User does not have the required permissions to list users"
)
return OrganizationUser.objects.filter(organization=organization)
return OrganizationUser.objects.filter(
organization=organization
).select_related("role")
21 changes: 11 additions & 10 deletions care/emr/api/viewsets/patient.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from care.emr.models import Organization, PatientUser, TokenBooking
from care.emr.models.patient import Patient, PatientIdentifier, PatientIdentifierConfig
from care.emr.models.scheduling.token import Token
from care.emr.resources.base import model_from_cache
from care.emr.resources.patient.spec import (
PatientCreateSpec,
PatientIdentifierConfigRequest,
Expand Down Expand Up @@ -90,11 +91,7 @@ def validate_data(self, instance, model_obj=None):
raise ValidationError("Year of birth cannot be after the year of death")

def get_queryset(self):
qs = (
super()
.get_queryset()
.select_related("created_by", "updated_by", "geo_organization")
)
qs = super().get_queryset().select_related("geo_organization")
if self.action != "list":
patient = get_object_or_404(
Patient, external_id=self.kwargs.get("external_id")
Expand Down Expand Up @@ -200,7 +197,9 @@ def search(self, request, *args, **kwargs):
if not request_data.phone_number and not request_data.config:
raise ValidationError("Either phone number or config is required")
if request_data.phone_number:
queryset = Patient.objects.filter(phone_number=request_data.phone_number)
queryset = Patient.objects.filter(
phone_number=request_data.phone_number
).select_related("geo_organization")
partial = True
else:
config_queryset = PatientIdentifierConfig.objects.filter(
Expand Down Expand Up @@ -243,7 +242,7 @@ def search(self, request, *args, **kwargs):

queryset = Patient.objects.filter(
id__in=identifier_queryset.values("patient_id")
)
).select_related("geo_organization")

if config.config.get("retrieve_config", {}).get(
"retrieve_with_year_of_birth"
Expand All @@ -268,7 +267,9 @@ class SearchRetrieveRequestSpec(BaseModel):
@action(detail=False, methods=["POST"])
def search_retrieve(self, request, *args, **kwargs):
request_data = self.SearchRetrieveRequestSpec(**request.data)
queryset = Patient.objects.filter(phone_number=request_data.phone_number)
queryset = Patient.objects.filter(
phone_number=request_data.phone_number
).select_related("geo_organization")
queryset = queryset.filter(year_of_birth=request_data.year_of_birth)
for patient in queryset:
if str(patient.external_id)[:5] == request_data.partial_id:
Expand All @@ -285,7 +286,7 @@ def get_users(self, request, *args, **kwargs):
patient = self.get_object()
patient_users = PatientUser.objects.filter(patient=patient)
data = [
UserSpec.serialize(patient_user.user).to_json()
model_from_cache(UserSpec, id=patient_user.user_id)
for patient_user in patient_users
]
return Response({"results": data})
Expand All @@ -305,7 +306,7 @@ def add_user(self, request, *args, **kwargs):
if PatientUser.objects.filter(user=user, patient=patient).exists():
raise ValidationError("User already exists")
PatientUser.objects.create(user=user, patient=patient, role=role)
return Response(UserSpec.serialize(user).to_json())
return Response(model_from_cache(UserSpec, id=user.id))

class PatientUserDeleteSpec(BaseModel):
user: UUID4
Expand Down
2 changes: 1 addition & 1 deletion care/emr/api/viewsets/scheduling/availability.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ def get_slots_for_day_handler(
start_datetime__date=request_data.day,
end_datetime__date=request_data.day,
resource=resource,
)
).select_related("availability")
if is_public is True:
created_slots = created_slots.filter(availability__schedule__is_public=True)
for slot in created_slots:
Expand Down
3 changes: 2 additions & 1 deletion care/emr/api/viewsets/scheduling/booking.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
)
from care.emr.models.scheduling import SchedulableResource, TokenBooking
from care.emr.models.scheduling.token import Token, TokenCategory, TokenQueue
from care.emr.resources.base import model_from_cache
from care.emr.resources.charge_item.handle_charge_item_cancel import (
handle_charge_item_cancel,
)
Expand Down Expand Up @@ -277,7 +278,7 @@ def available_users(self, request, *args, **kwargs):
return Response(
{
"users": [
UserSpec.serialize(user_resource.user).to_json()
model_from_cache(UserSpec, id=user_resource.user_id)
for user_resource in user_resources
]
}
Expand Down
6 changes: 1 addition & 5 deletions care/emr/resources/allergy_intolerance/spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
from care.emr.resources.allergy_intolerance.valueset import CARE_ALLERGY_CODE_VALUESET
from care.emr.resources.base import EMRResource
from care.emr.resources.common.coding import Coding
from care.emr.resources.user.spec import UserSpec
from care.emr.utils.valueset_coding_type import ValueSetBoundCoding


Expand Down Expand Up @@ -134,9 +133,6 @@ class AllergyIntoleranceReadSpec(BaseAllergyIntoleranceSpec):
@classmethod
def perform_extra_serialization(cls, mapping, obj):
mapping["id"] = obj.external_id
if obj.created_by:
mapping["created_by"] = UserSpec.serialize(obj.created_by)
if obj.updated_by:
mapping["updated_by"] = UserSpec.serialize(obj.updated_by)
cls.serialize_audit_users(mapping, obj)
if obj.encounter:
mapping["encounter"] = obj.encounter.external_id
6 changes: 3 additions & 3 deletions care/emr/resources/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,9 +162,9 @@ def to_json(self):
def serialize_audit_users(cls, mapping, obj):
from care.emr.resources.user.spec import UserSpec

if obj.created_by_id:
if hasattr(obj, "created_by_id") and obj.created_by_id:
mapping["created_by"] = model_from_cache(UserSpec, id=obj.created_by_id)
if obj.updated_by_id:
if hasattr(obj, "updated_by_id") and obj.updated_by_id:
mapping["updated_by"] = model_from_cache(UserSpec, id=obj.updated_by_id)


Expand Down Expand Up @@ -272,7 +272,7 @@ def model_from_cache(model: EMRResource, quiet=True, **kwargs) -> dict[str, Any]
data = model.serialize(obj)
cache.set(model_cache_key(model_string(db_model), model.__name__, pk), data)

return dict(data)
return data.model_dump(mode="json")


# TODO: add param for manually adding dependencies for cache invalidation
Expand Down
5 changes: 1 addition & 4 deletions care/emr/resources/condition/spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,10 +131,7 @@ def perform_extra_serialization(cls, mapping, obj):
if obj.encounter:
mapping["encounter"] = obj.encounter.external_id

if obj.created_by:
mapping["created_by"] = UserSpec.serialize(obj.created_by)
if obj.updated_by:
mapping["updated_by"] = UserSpec.serialize(obj.updated_by)
cls.serialize_audit_users(mapping, obj)


class ConditionUpdateSpec(BaseConditionSpec):
Expand Down
8 changes: 4 additions & 4 deletions care/emr/resources/consent/spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from care.emr.models import Encounter, FileUpload
from care.emr.models.consent import Consent
from care.emr.resources.base import EMRResource, PeriodSpec
from care.emr.resources.base import EMRResource, PeriodSpec, model_from_cache
from care.emr.resources.file_upload.spec import (
FileCategoryChoices,
FileTypeChoices,
Expand Down Expand Up @@ -117,9 +117,9 @@ def perform_extra_serialization(cls, mapping, obj):
mapping["encounter"] = obj.encounter.external_id

for verification in obj.verification_details:
verification["verified_by"] = UserSpec.serialize(
User.objects.get(external_id=verification["verified_by"])
).to_json()
verification["verified_by"] = model_from_cache(
UserSpec, external_id=verification["verified_by"]
)

mapping["verification_details"] = obj.verification_details
Comment thread
praffq marked this conversation as resolved.

Expand Down
10 changes: 4 additions & 6 deletions care/emr/resources/device/history_spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@
from pydantic import UUID4

from care.emr.models import DeviceServiceHistory
from care.emr.resources.base import EMRResource
from care.emr.resources.base import EMRResource, model_from_cache
from care.emr.resources.user.spec import UserSpec
from care.users.models import User


class DeviceServiceHistorySpecBase(EMRResource):
Expand Down Expand Up @@ -41,10 +40,9 @@ def perform_extra_serialization(cls, mapping, obj):
edit_history = []
for history in obj.edit_history:
user = history.get("updated_by")
user_obj = User.objects.filter(id=user).first()
if user_obj:
history["updated_by"] = UserSpec.serialize(user_obj).to_json()
if user:
history["updated_by"] = model_from_cache(UserSpec, id=user)
else:
history["updated_by"] = {} # Edge Case
history["updated_by"] = {}
edit_history.append(history)
Comment thread
praffq marked this conversation as resolved.
mapping["edit_history"] = edit_history
4 changes: 3 additions & 1 deletion care/emr/resources/encounter/spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,9 @@ def perform_extra_serialization(cls, mapping, obj):
mapping["appointment"] = TokenBookingReadSpec.serialize(
obj.appointment
).to_json()
organizations = EncounterOrganization.objects.filter(encounter=obj)
organizations = EncounterOrganization.objects.filter(
encounter=obj
).select_related("organization")
mapping["organizations"] = [
FacilityOrganizationReadSpec.serialize(encounter_org.organization).to_json()
for encounter_org in organizations
Expand Down
7 changes: 2 additions & 5 deletions care/emr/resources/facility/spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from care.emr.models import Organization
from care.emr.models.patient import PatientIdentifierConfigCache
from care.emr.resources.base import EMRResource, cacheable, model_from_cache
from care.emr.resources.base import EMRResource, cacheable
from care.emr.resources.common.coding import Coding
from care.emr.resources.common.monetary_component import MonetaryComponentDefinition
from care.emr.resources.invoice.default_expression_evaluator import (
Expand Down Expand Up @@ -105,12 +105,9 @@ class FacilityReadSpec(FacilityBaseSpec):

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

mapping["id"] = obj.external_id
mapping["read_cover_image_url"] = obj.read_cover_image_url()
if obj.created_by:
mapping["created_by"] = model_from_cache(UserSpec, id=obj.created_by_id)
cls.serialize_audit_users(mapping, obj)
mapping["facility_type"] = REVERSE_FACILITY_TYPES[obj.facility_type]
if obj.geo_organization:
mapping["geo_organization"] = OrganizationReadSpec.serialize(
Expand Down
6 changes: 1 addition & 5 deletions care/emr/resources/facility_organization/spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,7 @@ class FacilityOrganizationReadSpec(FacilityOrganizationBaseSpec):
def perform_extra_serialization(cls, mapping, obj):
mapping["id"] = obj.external_id
mapping["parent"] = obj.get_parent_json()

if obj.created_by:
mapping["created_by"] = UserSpec.serialize(obj.created_by)
if obj.updated_by:
mapping["updated_by"] = UserSpec.serialize(obj.updated_by)
cls.serialize_audit_users(mapping, obj)


class FacilityOrganizationRetrieveSpec(FacilityOrganizationReadSpec):
Expand Down
14 changes: 7 additions & 7 deletions care/emr/resources/file_upload/spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from pydantic import UUID4, field_validator

from care.emr.models import FileUpload
from care.emr.resources.base import EMRResource
from care.emr.resources.base import EMRResource, model_from_cache
from care.emr.resources.user.spec import UserSpec
from care.utils.models.validators import file_name_validator

Expand Down Expand Up @@ -92,10 +92,10 @@ def perform_extra_serialization(cls, mapping, obj):
mapping["id"] = obj.external_id
mapping["extension"] = obj.get_extension()
mapping["mime_type"] = obj.meta.get("mime_type")
if obj.created_by:
mapping["uploaded_by"] = UserSpec.serialize(obj.created_by)
if obj.archived_by:
mapping["archived_by"] = UserSpec.serialize(obj.archived_by)
if obj.created_by_id:
mapping["uploaded_by"] = model_from_cache(UserSpec, id=obj.created_by_id)
if obj.archived_by_id:
mapping["archived_by"] = model_from_cache(UserSpec, id=obj.archived_by_id)


class FileUploadRetrieveSpec(FileUploadListSpec):
Expand All @@ -112,8 +112,8 @@ def perform_extra_serialization(cls, mapping, obj):
else:
mapping["read_signed_url"] = obj.files_manager.read_signed_url(obj)

if obj.updated_by:
mapping["updated_by"] = UserSpec.serialize(obj.updated_by)
if obj.updated_by_id:
mapping["updated_by"] = model_from_cache(UserSpec, id=obj.updated_by_id)


class ConsentFileUploadCreateSpec(FileUploadBaseSpec):
Expand Down
18 changes: 6 additions & 12 deletions care/emr/resources/healthcare_service/spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from pydantic import UUID4

from care.emr.models.healthcare_service import HealthcareService
from care.emr.models.location import FacilityLocation
from care.emr.models.organization import FacilityOrganization
from care.emr.resources.base import EMRResource
from care.emr.resources.facility_organization.spec import FacilityOrganizationReadSpec
Expand Down Expand Up @@ -69,18 +68,13 @@ class HealthcareServiceRetrieveSpec(HealthcareServiceReadSpec):

@classmethod
def perform_extra_serialization(cls, mapping, obj):
from care.emr.models.location import FacilityLocation

super().perform_extra_serialization(mapping, obj)
locations = []
for location in obj.locations:
try:
locations.append(
FacilityLocationListSpec.serialize(
FacilityLocation.objects.get(id=location)
).to_json()
)
except Exception: # noqa S110
pass
mapping["locations"] = locations
mapping["locations"] = [
FacilityLocationListSpec.serialize(location).to_json()
for location in FacilityLocation.objects.filter(id__in=obj.locations)
]
if obj.managing_organization:
mapping["managing_organization"] = FacilityOrganizationReadSpec.serialize(
obj.managing_organization
Expand Down
6 changes: 1 addition & 5 deletions care/emr/resources/location/spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from care.emr.models.location import FacilityLocation
from care.emr.resources.base import EMRResource
from care.emr.resources.common import Coding
from care.emr.resources.user.spec import UserSpec


class LocationEncounterAvailabilityStatusChoices(str, Enum):
Expand Down Expand Up @@ -151,10 +150,7 @@ class FacilityLocationRetrieveSpec(FacilityLocationListSpec):
@classmethod
def perform_extra_serialization(cls, mapping, obj):
super().perform_extra_serialization(mapping, obj)
if obj.created_by:
mapping["created_by"] = UserSpec.serialize(obj.created_by)
if obj.updated_by:
mapping["updated_by"] = UserSpec.serialize(obj.updated_by)
cls.serialize_audit_users(mapping, obj)


class FacilityLocationEncounterBaseSpec(EMRResource):
Expand Down
3 changes: 1 addition & 2 deletions care/emr/resources/medication/administration/spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,5 +191,4 @@ def perform_extra_serialization(cls, mapping, obj):
obj.administered_product
)

if obj.created_by:
mapping["created_by"] = UserSpec.serialize(obj.created_by)
cls.serialize_audit_users(mapping, obj)
6 changes: 4 additions & 2 deletions care/emr/resources/medication/dispense/dispense_order.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ class MedicationDispenseOrderReadSpec(BaseMedicationDispenseOrderSpec):
@classmethod
def perform_extra_serialization(cls, mapping, obj):
mapping["id"] = obj.external_id
mapping["location"] = FacilityLocationListSpec.serialize(obj.location).to_json()
location = FacilityLocation.objects.get(id=obj.location_id)
mapping["location"] = FacilityLocationListSpec.serialize(location).to_json()
Comment thread
praffq marked this conversation as resolved.
mapping["patient"] = PatientListSpec.serialize(obj.patient).to_json()


Expand All @@ -74,5 +75,6 @@ class MedicationDispenseOrderRetrieveSpec(MedicationDispenseOrderReadSpec):
def perform_extra_serialization(cls, mapping, obj):
cls.serialize_audit_users(mapping, obj)
mapping["id"] = obj.external_id
mapping["location"] = FacilityLocationListSpec.serialize(obj.location).to_json()
location = FacilityLocation.objects.get(id=obj.location_id)
mapping["location"] = FacilityLocationListSpec.serialize(location).to_json()
Comment thread
praffq marked this conversation as resolved.
mapping["patient"] = PatientRetrieveSpec.serialize(obj.patient).to_json()
Loading