References/Organization
More languages
More actions
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: [https://github.com/ohcnetwork/care/blob/develop/care/emr/models/organization.py care/emr/models/organization.py] · [https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/organization/spec.py resources/organization/spec.py] · [https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/organization/organization_user_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 | ||
org_type |
CharField(255) |
yes | ||
description |
TextField |
no | ||
active |
BooleanField |
no | ||
system_generated |
BooleanField |
no | ||
metadata |
JSONField |
no |
Tree structure
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
parent |
FK → self |
no | ||
root_org |
FK → self |
no | ||
has_children |
BooleanField |
no |
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)
|
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 References/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 [[#organizationusersave
|
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 | 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
Pages that link here:
- Reference: References/Permission
Wiki: ohcnwiki.tellmey.fyi