Skip to content
Closed
Show file tree
Hide file tree
Changes from 21 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
13 changes: 7 additions & 6 deletions care/emr/api/viewsets/encounter.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
Patient,
)
from care.emr.models.patient import PatientIdentifier, PatientIdentifierConfig
from care.emr.resources.base import model_from_cache
from care.emr.resources.encounter.constants import COMPLETED_CHOICES, StatusChoices
from care.emr.resources.encounter.spec import (
EncounterCareTeamMemberWriteSpec,
Expand Down Expand Up @@ -268,12 +269,10 @@ def organizations(self, request, *args, **kwargs):
self.authorize_retrieve(instance)
encounter_organizations = EncounterOrganization.objects.filter(
encounter=instance
).select_related("organization")
).values_list("organization_id", flat=True)
data = [
FacilityOrganizationReadSpec.serialize(
encounter_organization.organization
).to_json()
for encounter_organization in encounter_organizations
model_from_cache(FacilityOrganizationReadSpec, id=org_id)
for org_id in encounter_organizations
]
Comment thread
coderabbitai[bot] marked this conversation as resolved.
return Response({"results": data})

Expand Down Expand Up @@ -302,7 +301,9 @@ def organizations_add(self, request, *args, **kwargs):
EncounterOrganization.objects.create(
encounter=instance, organization=organization
)
return Response(FacilityOrganizationReadSpec.serialize(organization).to_json())
return Response(
model_from_cache(FacilityOrganizationReadSpec, id=organization.id)
)

@extend_schema(
request=EncounterOrganizationManageSpec,
Expand Down
2 changes: 1 addition & 1 deletion care/emr/api/viewsets/facility.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,4 +217,4 @@ 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)
15 changes: 8 additions & 7 deletions care/emr/api/viewsets/location.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
FacilityLocationOrganization,
)
from care.emr.models.organization import FacilityOrganization, FacilityOrganizationUser
from care.emr.resources.base import model_from_cache
from care.emr.resources.encounter.constants import COMPLETED_CHOICES
from care.emr.resources.facility_organization.spec import FacilityOrganizationReadSpec
from care.emr.resources.location.spec import (
Expand Down Expand Up @@ -176,14 +177,12 @@ 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")
).values_list("organization_id", flat=True)
data = [
FacilityOrganizationReadSpec.serialize(
encounter_organization.organization
).to_json()
for encounter_organization in encounter_organizations
model_from_cache(FacilityOrganizationReadSpec, id=org_id)
for org_id in location_organizations
]
return Response({"results": data})

Expand Down Expand Up @@ -219,7 +218,9 @@ def organizations_add(self, request, *args, **kwargs):
FacilityLocationOrganization.objects.create(
location=instance, organization=organization
)
return Response(FacilityOrganizationReadSpec.serialize(organization).to_json())
return Response(
model_from_cache(FacilityOrganizationReadSpec, id=organization.id)
)

@extend_schema(
request=FacilityLocationOrganizationManageSpec, responses={200: None}
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")
11 changes: 4 additions & 7 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()
if self.action != "list":
patient = get_object_or_404(
Patient, external_id=self.kwargs.get("external_id")
Expand Down Expand Up @@ -285,7 +282,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 +302,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
13 changes: 7 additions & 6 deletions care/emr/api/viewsets/questionnaire.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
QuestionnaireTag,
)
from care.emr.models.questionnaire import FormSubmission, QuestionnaireResponse
from care.emr.resources.base import model_from_cache
from care.emr.resources.favorites.filters import FavoritesFilter
from care.emr.resources.favorites.spec import FavoriteResourceChoices
from care.emr.resources.form_submission.spec import FormSubmissionStatusChoices
Expand Down Expand Up @@ -193,10 +194,10 @@ def get_organizations(self, request, *args, **kwargs):
questionnaire = self.get_object()
questionnaire_organizations = QuestionnaireOrganization.objects.filter(
questionnaire=questionnaire
).select_related("organization")
).values_list("organization_id", flat=True)
organizations_serialized = [
OrganizationReadSpec.serialize(obj.organization).to_json()
for obj in questionnaire_organizations
model_from_cache(OrganizationReadSpec, id=org_id)
for org_id in questionnaire_organizations
]
return Response(
{
Expand Down Expand Up @@ -254,10 +255,10 @@ def set_organizations(self, request, *args, **kwargs):
questionnaire=questionnaire, organization=organization
)
organizations_serialized = [
OrganizationReadSpec.serialize(obj.organization).to_json()
for obj in QuestionnaireOrganization.objects.filter(
model_from_cache(OrganizationReadSpec, id=org_id)
for org_id in QuestionnaireOrganization.objects.filter(
questionnaire=questionnaire
).select_related("organization")
).values_list("organization_id", flat=True)
]
return Response(
{
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
10 changes: 5 additions & 5 deletions care/emr/resources/device/spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from care.emr.models import Device, DeviceEncounterHistory, DeviceLocationHistory
from care.emr.registries.device_type.device_registry import DeviceTypeRegistry
from care.emr.resources.base import EMRResource
from care.emr.resources.base import EMRResource, model_from_cache
from care.emr.resources.common.contact_point import ContactPoint
from care.emr.resources.encounter.spec import EncounterListSpec
from care.emr.resources.facility_organization.spec import FacilityOrganizationReadSpec
Expand Down Expand Up @@ -109,10 +109,10 @@ def perform_extra_serialization(cls, mapping, obj):
care_device_class = DeviceTypeRegistry.get_care_device_class(obj.care_type)
mapping["care_metadata"] = care_device_class().retrieve(obj)

if obj.managing_organization:
mapping["managing_organization"] = FacilityOrganizationReadSpec.serialize(
obj.managing_organization
).to_json()
if obj.managing_organization_id:
mapping["managing_organization"] = model_from_cache(
FacilityOrganizationReadSpec, id=obj.managing_organization_id
)


class DeviceLocationHistoryListSpec(EMRResource):
Expand Down
8 changes: 5 additions & 3 deletions care/emr/resources/encounter/spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,10 +161,12 @@ def perform_extra_serialization(cls, mapping, obj):
mapping["appointment"] = TokenBookingReadSpec.serialize(
obj.appointment
).to_json()
organizations = EncounterOrganization.objects.filter(encounter=obj)
organization_ids = EncounterOrganization.objects.filter(
encounter=obj
).values_list("organization_id", flat=True)
mapping["organizations"] = [
FacilityOrganizationReadSpec.serialize(encounter_org.organization).to_json()
for encounter_org in organizations
model_from_cache(FacilityOrganizationReadSpec, id=org_id)
for org_id in organization_ids
]
mapping["location_history"] = [
FacilityLocationEncounterListSpecWithLocation.serialize(i)
Expand Down
21 changes: 9 additions & 12 deletions care/emr/resources/facility/spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,17 +105,14 @@ 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(
obj.geo_organization
).to_json()
if obj.geo_organization_id:
mapping["geo_organization"] = model_from_cache(
OrganizationReadSpec, id=obj.geo_organization_id
)


class FacilityRetrieveSpec(FacilityReadSpec, FacilityPermissionsMixin):
Expand Down Expand Up @@ -214,7 +211,7 @@ def perform_extra_serialization(cls, mapping, obj):
mapping["id"] = obj.external_id
mapping["read_cover_image_url"] = obj.read_cover_image_url()
mapping["facility_type"] = REVERSE_FACILITY_TYPES[obj.facility_type]
if obj.geo_organization:
mapping["geo_organization"] = OrganizationReadSpec.serialize(
obj.geo_organization
).to_json()
if obj.geo_organization_id:
mapping["geo_organization"] = model_from_cache(
OrganizationReadSpec, id=obj.geo_organization_id
)
9 changes: 3 additions & 6 deletions care/emr/resources/facility_organization/spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from pydantic import UUID4, field_validator, model_validator

from care.emr.models.organization import FacilityOrganization
from care.emr.resources.base import EMRResource
from care.emr.resources.base import EMRResource, cacheable
from care.emr.resources.user.spec import UserSpec
from care.facility.models import Facility
from care.security.authorization import AuthorizationController
Expand Down Expand Up @@ -71,6 +71,7 @@ def perform_extra_deserialization(self, is_update, obj):
obj.parent = None


@cacheable
class FacilityOrganizationReadSpec(FacilityOrganizationBaseSpec):
org_type: FacilityOrganizationTypeChoices
parent: UUID4 | None = None
Expand All @@ -85,11 +86,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
Loading