Skip to main content
Version: 3.1

Organization

Organizations are Care's FHIR-aligned grouping primitive: a nested tree that groups permissions and resources. A node might represent all doctors, a Cardiology sub-team, or a governance unit, and permissions attached to a parent flow implicitly to every descendant. You touch them whenever you model who can do what, and where.

Source: care/emr/models/organization.py · resources/organization/spec.py · resources/organization/organization_user_spec.py

The Django model is only the storage layer. Its opaque columns — JSONFields (metadata, cached_parent_json) and arrays (parent_cache, managing_organizations) — get their structure from the resource specs under care/emr/resources/organization/, which also define the org_type enum, validation, the nested JSON returned for parent, and the read/write API schemas. See Resource specs (API schema).

Models

ModelPurpose
OrganizationCommonBaseAbstract base holding the tree structure, caching, and uniqueness logic shared by both organization types
OrganizationInstance-wide organization (permission/governance tree, not tied to a facility)
FacilityOrganizationFacility-scoped organization (departments/teams within one Facility)
OrganizationUserMembership row linking a User to an Organization with a RoleModel
FacilityOrganizationUserMembership row linking a User to a FacilityOrganization with a RoleModel

Organization and FacilityOrganization extend OrganizationCommonBase, which extends EMRBaseModel — the shared Care EMR base providing external_id, audit fields, soft-delete, and history/meta JSON. OrganizationUser and FacilityOrganizationUser extend EMRBaseModel directly. OrganizationCommonBase is abstract = True and has no table of its own.

OrganizationCommonBase fields

These columns land on both the Organization and FacilityOrganization tables.

Identity & classification

FieldTypeRequiredDefaultNotes
nameCharField(255)yesDisplay name. Feeds the sibling uniqueness check (validate_uniqueness)
org_typeCharField(255)yesFree text in the DB, but writes bind to the OrganizationTypeChoices enum. See Organization type values
descriptionTextFieldnonullNullable and blank-able in the DB; the write/read specs default it to ""
activeBooleanFieldnoTrue
system_generatedBooleanFieldnoFalseSystem-generated orgs can't be edited or deleted. Read-only on the API (only OrganizationReadSpec surfaces it)
metadataJSONFieldno{} (dict)Open key-value bag for deployment-specific data — no fixed schema, no migration needed. Specs pass it through verbatim as metadata: dict

Tree structure

FieldTypeRequiredDefaultNotes
parentFK → selfnonullCASCADE, related_name="children". A null parent is a root org. Written as the parent's external_id (UUID); read back as a nested JSON object — see parent read shape
root_orgFK → selfnonullCASCADE, related_name="root". Top of the tree, derived on save by set_organization_cache()
has_childrenBooleanFieldnoFalseFlipped to True on the parent when its first child is created. Read-only on the API

Denormalized caches

Care rebuilds these on insert so reading the tree doesn't need recursive joins. Do not set them from clients.

FieldTypeDefaultRebuilt byShape
level_cacheIntegerField0set_organization_cache()Depth in the tree (parent.level_cache + 1)
parent_cacheArrayField[int][] (list)set_organization_cache()Full ancestor internal id chain (parent.parent_cache + [parent.id])
cached_parent_jsonJSONField{} (dict)get_parent_json()Materialized parent record (shape below). Rebuilt after cache_expiry_days (15)

cached_parent_json is built in get_parent_json() and is {} for root orgs:

{
id: str (parent.external_id, UUID as string)
name: str
description: str | null
org_type: str (OrganizationTypeChoices value)
metadata: dict
parent: dict (recursively, the parent's own cached_parent_json)
level_cache: int
cache_expiry: str (ISO datetime; cache is reused while now < cache_expiry)
}

This is the same structure OrganizationReadSpec.parent returns on read (see parent read shape).

Organization

Instance-wide organization. Adds one field on top of OrganizationCommonBase:

FieldTypeDefaultNotes
managing_organizationsArrayField[int][] (list)Internal ids of the orgs that manage this one — the management hierarchy deciding who may administer the org and its users (a user is editable only from a managing org above them). Expanded to nested org JSON (OrganizationReadSpec) only by OrganizationRetrieveSpec

FacilityOrganization

Facility-scoped organization — departments, teams, and groups within one facility. Adds one field on top of OrganizationCommonBase:

facility → FK facility.Facility (CASCADE)

Its org_type binds to FacilityOrganizationTypeChoices rather than OrganizationTypeChoices (see Facility organization type values), and its specs live in resources/facility_organization/. FacilityOrganizationWriteSpec requires facility + org_type + optional parent (validated to be in the same facility); FacilityOrganizationReadSpec / FacilityOrganizationRetrieveSpec mirror the instance read specs, resolving the caller's permissions through get_permission_on_facility_organization. See Facility.

OrganizationUser

Grants a user access to an organization's resources through a role — only the resources assigned to the org, not everything it owns.

organization → FK Organization (CASCADE)
user → FK users.User (CASCADE)
role → FK security.RoleModel (CASCADE)

FacilityOrganizationUser

The facility-scoped equivalent of OrganizationUser.

organization → FK FacilityOrganization (CASCADE)
user → FK users.User (CASCADE)
role → FK security.RoleModel (CASCADE)

Enum & coded values

Organization type values

Organization.org_type is a CharField in the DB, but every write binds to OrganizationTypeChoices (resources/organization/spec.py).

ValueMeaning
teamA working / department-style grouping of people (the default)
govtA governance unit mirroring a real administrative hierarchy (State → District → …). Any user can view these to support cross-governance coordination; create/edit is superadmin-only; facilities and patients reference these nodes geographically
roleA user group — a flat set of users, e.g. Volunteer, ASHA Worker. Adding an OrganizationUser to a role-typed org invalidates that user's cached_role_orgs (see OrganizationUser.save()); managed by superadmins
product_supplierSupplier organization (links to the supply chain)

Facility organization type values

FacilityOrganization.org_type binds to a separate enum — FacilityOrganizationTypeChoices (resources/facility_organization/spec.py), not OrganizationTypeChoices.

ValueMeaning
rootSystem-generated top node, one per facility (cannot be edited or deleted)
deptA department within the facility
teamA team, often nested under a department
roleA facility-scoped user group
otherAny grouping outside the categories above

Root, govt, and role (instance) organizations, plus any system-generated organization, are restricted to superadmin management.

Resource specs (API schema)

Resource specs (EMRResource subclasses) are the API layer. serialize builds a read object from a DB row, running the perform_extra_serialization / perform_extra_user_serialization hooks; de_serialize builds a DB row from a write payload, running perform_extra_deserialization. __exclude__ lists the model fields the base serializer skips so a hook can handle them instead.

Organization specs (resources/organization/spec.py)

SpecRoleFields exposedNotes
OrganizationBaseSpecsharedid, active, org_type, name, description, metadata__model__ = Organization, __exclude__ = ["parent"]. org_type bound to OrganizationTypeChoices; description defaults ""; metadata defaults {}
OrganizationUpdateSpecwrite · update(inherits OrganizationBaseSpec)No extra fields — parent can't change on update
OrganizationWriteSpecwrite · createbase + parent: UUID4 | nullValidates parent exists (validate_parent_organization); on create, perform_extra_deserialization resolves parent from external_id (or sets None)
OrganizationReadSpecread · listbase + level_cache, system_generated, has_children, parent: dictperform_extra_serialization sets id = external_id and parent = obj.get_parent_json() — see parent read shape
OrganizationRetrieveSpecread · detailOrganizationReadSpec + permissions: list[str], managing_organizations: list[dict]perform_extra_user_serialization fills permissions (via AuthorizationController.get_permission_on_organization for the requesting user), expands managing_organizations to nested OrganizationReadSpec JSON, and adds audit users (serialize_audit_users)

parent read shape

On read, OrganizationReadSpec.parent (and OrganizationRetrieveSpec.parent) is not a UUID — it's the nested object from get_parent_json(), sharing the shape of cached_parent_json: { id, name, description, org_type, metadata, parent (recursive), level_cache, cache_expiry }, or {} for root orgs. On write, parent is instead a plain UUID4 (the parent's external_id).

Membership specs (resources/organization/organization_user_spec.py)

SpecRoleFields exposedNotes
OrganizationUserBaseSpecshared(none)__model__ = OrganizationUser, __exclude__ = ["user", "role"]
OrganizationUserUpdateSpecwrite · updaterole: UUID4validate_role requires the RoleModel to exist; perform_extra_deserialization resolves role from external_id. Only the role can change on update
OrganizationUserWriteSpecwrite · createrole: UUID4 + user: UUID4Extends the update spec; validate_user requires the User to exist. On create, resolves both user and role from external_id
OrganizationUserReadSpecreadid: UUID4, user: dict, role: dictuser = cached UserSpec JSON (model_from_cache); role = RoleReadSpec JSON (full permissions)
OrganizationUserExtendedReadSpecreadid: UUID4, role: dict, organization: dictUsed by OrganizationUser.get_cached_role_orgs(). organization = OrganizationReadSpec JSON; role = RoleReadMinimalSpec JSON (no permission list)

Nested membership JSON shapes (from resources/role/spec.py, resources/user/spec.py):

role (RoleReadSpec): { id: UUID, name, description, is_system: bool,
is_archived: bool, contexts: [RoleContext],
permissions: [{ name, description, slug, context }] }
role (RoleReadMinimalSpec): same as above without `permissions`
user (UserSpec): cached user summary JSON

Methods & save behaviour

OrganizationCommonBase.save()

On insert (no id yet), the base saves the row, then calls set_organization_cache() to populate the tree caches. Updates save normally and don't recompute caches.

set_organization_cache() side effects when a parent is set:

  1. Computes parent_cache = parent.parent_cache + [parent.id] and level_cache = parent.level_cache + 1.
  2. Derives root_org from the parent (parent.root_org, or the parent itself if the parent is a root).
  3. If the parent had no children before, flips parent.has_children = True and persists only that field.

get_parent_json()

Returns the cached parent JSON while cached_parent_json is present and cache_expiry is still in the future. Otherwise it recursively rebuilds the nested parent record, stamps a new expiry (cache_expiry_days = 15), and persists cached_parent_json. Returns {} for root orgs. This is what OrganizationReadSpec.parent exposes on every read.

validate_uniqueness(queryset, pydantic_instance, model_instance)

Classmethod enforcing name uniqueness among siblings — those sharing the same root_org and level_cache. For a new org it derives level_cache/root_org from the supplied parent (external_id); a null parent means level_cache = 0 and a null root_org. The resource layer calls it before create/update.

OrganizationUser.save()

After saving, if the linked organization's org_type is role (OrganizationTypeChoices.role), it clears user.cached_role_orgs (sets it to None and persists that field) so the user's role-org cache rebuilds on the next read.

OrganizationUser.get_cached_role_orgs(user_id) is a classmethod that serializes all of a user's role-typed org memberships via OrganizationUserExtendedReadSpec.

API integration notes

  • Organizations are exposed through Care's REST API and align with the FHIR Organization resource. Tree position (level_cache, parent_cache, root_org), has_children, and cached_parent_json are platform-maintained — don't set them from clients.
  • Write path: OrganizationWriteSpec (create) / OrganizationUpdateSpec (update). org_type is constrained to OrganizationTypeChoices; parent is supplied as the parent's external_id (UUID) and validated to exist; validate_uniqueness enforces sibling-name uniqueness. parent is immutable after create (the update spec omits it).
  • Read path: OrganizationReadSpec (list) returns parent as nested JSON (get_parent_json()) plus level_cache/system_generated/has_children; OrganizationRetrieveSpec (detail) adds the caller's permissions, expanded managing_organizations, and audit users.
  • Membership is managed through OrganizationUser / FacilityOrganizationUser (specs above); role is required and bounded by the assigning user's own role in that org. Adding a member to a role-typed org invalidates that user's cached_role_orgs.
  • metadata is the supported place for deployment-specific key-value data without schema migrations; the specs pass it through verbatim as dict.
  • Soft-delete applies (via EMRBaseModel); an organization can't be deleted while it still has children, and system-generated orgs can't be deleted.