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
| Model | Purpose |
|---|---|
OrganizationCommonBase | Abstract base holding the tree structure, caching, and uniqueness logic shared by both organization types |
Organization | Instance-wide organization (permission/governance tree, not tied to a facility) |
FacilityOrganization | Facility-scoped organization (departments/teams within one Facility) |
OrganizationUser | Membership row linking a User to an Organization with a RoleModel |
FacilityOrganizationUser | Membership 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
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
name | CharField(255) | yes | — | Display name. Feeds the sibling uniqueness check (validate_uniqueness) |
org_type | CharField(255) | yes | — | Free text in the DB, but writes bind to the OrganizationTypeChoices enum. See Organization type values |
description | TextField | no | null | Nullable and blank-able in the DB; the write/read specs default it to "" |
active | BooleanField | no | True | |
system_generated | BooleanField | no | False | System-generated orgs can't be edited or deleted. Read-only on the API (only OrganizationReadSpec surfaces it) |
metadata | JSONField | no | {} (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
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
parent | FK → self | no | null | CASCADE, 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_org | FK → self | no | null | CASCADE, related_name="root". Top of the tree, derived on save by set_organization_cache() |
has_children | BooleanField | no | False | Flipped 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.
| Field | Type | Default | Rebuilt by | Shape |
|---|---|---|---|---|
level_cache | IntegerField | 0 | set_organization_cache() | Depth in the tree (parent.level_cache + 1) |
parent_cache | ArrayField[int] | [] (list) | set_organization_cache() | Full ancestor internal id chain (parent.parent_cache + [parent.id]) |
cached_parent_json | JSONField | {} (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).
Related models
Organization
Instance-wide organization. Adds one field on top of OrganizationCommonBase:
| Field | Type | Default | Notes |
|---|---|---|---|
managing_organizations | ArrayField[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).
| Value | Meaning |
|---|---|
team | A working / department-style grouping of people (the default) |
govt | A 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 |
role | A 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_supplier | Supplier 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.
| Value | Meaning |
|---|---|
root | System-generated top node, one per facility (cannot be edited or deleted) |
dept | A department within the facility |
team | A team, often nested under a department |
role | A facility-scoped user group |
other | Any 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)
| Spec | Role | Fields exposed | Notes |
|---|---|---|---|
OrganizationBaseSpec | shared | id, active, org_type, name, description, metadata | __model__ = Organization, __exclude__ = ["parent"]. org_type bound to OrganizationTypeChoices; description defaults ""; metadata defaults {} |
OrganizationUpdateSpec | write · update | (inherits OrganizationBaseSpec) | No extra fields — parent can't change on update |
OrganizationWriteSpec | write · create | base + parent: UUID4 | null | Validates parent exists (validate_parent_organization); on create, perform_extra_deserialization resolves parent from external_id (or sets None) |
OrganizationReadSpec | read · list | base + level_cache, system_generated, has_children, parent: dict | perform_extra_serialization sets id = external_id and parent = obj.get_parent_json() — see parent read shape |
OrganizationRetrieveSpec | read · detail | OrganizationReadSpec + 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)
| Spec | Role | Fields exposed | Notes |
|---|---|---|---|
OrganizationUserBaseSpec | shared | (none) | __model__ = OrganizationUser, __exclude__ = ["user", "role"] |
OrganizationUserUpdateSpec | write · update | role: UUID4 | validate_role requires the RoleModel to exist; perform_extra_deserialization resolves role from external_id. Only the role can change on update |
OrganizationUserWriteSpec | write · create | role: UUID4 + user: UUID4 | Extends the update spec; validate_user requires the User to exist. On create, resolves both user and role from external_id |
OrganizationUserReadSpec | read | id: UUID4, user: dict, role: dict | user = cached UserSpec JSON (model_from_cache); role = RoleReadSpec JSON (full permissions) |
OrganizationUserExtendedReadSpec | read | id: UUID4, role: dict, organization: dict | Used 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:
- Computes
parent_cache=parent.parent_cache + [parent.id]andlevel_cache=parent.level_cache + 1. - Derives
root_orgfrom the parent (parent.root_org, or the parent itself if the parent is a root). - If the parent had no children before, flips
parent.has_children = Trueand 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
Organizationresource. Tree position (level_cache,parent_cache,root_org),has_children, andcached_parent_jsonare platform-maintained — don't set them from clients. - Write path:
OrganizationWriteSpec(create) /OrganizationUpdateSpec(update).org_typeis constrained toOrganizationTypeChoices;parentis supplied as the parent'sexternal_id(UUID) and validated to exist;validate_uniquenessenforces sibling-name uniqueness.parentis immutable after create (the update spec omits it). - Read path:
OrganizationReadSpec(list) returnsparentas nested JSON (get_parent_json()) pluslevel_cache/system_generated/has_children;OrganizationRetrieveSpec(detail) adds the caller'spermissions, expandedmanaging_organizations, and audit users. - Membership is managed through
OrganizationUser/FacilityOrganizationUser(specs above);roleis required and bounded by the assigning user's own role in that org. Adding a member to arole-typed org invalidates that user'scached_role_orgs. metadatais the supported place for deployment-specific key-value data without schema migrations; the specs pass it through verbatim asdict.- Soft-delete applies (via
EMRBaseModel); an organization can't be deleted while it still has children, and system-generated orgs can't be deleted.
Related
- Reference: Facility
- Reference: Base models & conventions
- Reference: User · Role · Permission
- Source — models: organization.py
- Source — specs: organization/spec.py · organization/organization_user_spec.py · resources/base.py (
EMRResource) - Source — related: role/spec.py · user/spec.py · security/models/role.py · users/models.py