References/Base models & conventions
More languages
More actions
Every Care EMR resource inherits a shared base that supplies opaque IDs, audit fields, soft-delete, history, slugs, and feature flags. A resource author writes domain fields and validation; the base handles the rest. It spans two layers:
- Storage layer — abstract Django models in
care/emr/modelsandcare/utils/models. Several of their columns are opaqueJSONFields whose structure lives elsewhere; the model alone won't tell you what goes inside them. - API layer — Pydantic resource specs built on
EMRResource(care/emr/resources/base.py). These define enums, field validation, the read/write schemas, and the structured shapes that fill those JSON columns via nested specs. The structured types incare/emr/resources/common/are reused across nearly every resource.
Come here for the rules a resource gets for free: the soft-delete contract, what serialize/de_serialize do, how write and read schemas diverge, and the common spec types behind the JSON fields. Concrete resources such as References/Patient inherit a storage base and define their own specs on top of EMRResource.
Source: [https://github.com/ohcnetwork/care/blob/develop/care/emr/models/base.py care/emr/models/base.py], [https://github.com/ohcnetwork/care/blob/develop/care/utils/models/base.py care/utils/models/base.py], [https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py care/emr/resources/base.py], [https://github.com/ohcnetwork/care/tree/develop/care/emr/resources/common care/emr/resources/common/]
Models
| Model | Layer | Purpose |
|---|---|---|
BaseModel
|
storage | Lowest-level abstract base — opaque external_id, timestamps, and soft-delete
|
BaseManager
|
storage | Default manager that hides soft-deleted rows |
BaseFlag
|
storage | Abstract base for feature-flag tables with cache-backed lookups |
EMRBaseModel
|
storage | Standard base for EMR resources — adds audit created_by/updated_by, history, and meta
|
SlugBaseModel
|
storage | EMR base that adds facility-scoped or instance-scoped slug helpers |
EMRResource
|
API | Pydantic base for all resource specs — serialize / de_serialize between DB objects and API schemas
|
All five storage models are abstract = True — none owns a database table. A concrete resource model inherits one and contributes its own columns.
The storage models inherit in one chain:
models.Model
└─ BaseModel (care/utils/models/base.py)
├─ BaseFlag (care/utils/models/base.py)
└─ EMRBaseModel (care/emr/models/base.py)
└─ SlugBaseModel
The API specs form a parallel hierarchy under pydantic.BaseModel:
pydantic.BaseModel
└─ EMRResource (care/emr/resources/base.py)
└─ <Resource>SpecBase / <Resource>CreateSpec / ...ReadSpec / ...RetrieveSpec
Storage layer
BaseModel fields
The root abstract model. Every other base — and so every persisted Care resource — carries these four columns.
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
external_id |
UUIDField |
yes | ||
created_date |
DateTimeField |
no | ||
modified_date |
DateTimeField |
no | ||
deleted |
BooleanField |
yes |
objects is overridden to a BaseManager, so the default queryset returns only live rows.
Soft delete
BaseModel.delete() issues no SQL DELETE. It flips the flag and persists only that column:
def delete(self, *args):
self.deleted = True
self.save(update_fields=["deleted"])
Rows stay put for audit and referential integrity. To reach deleted rows, bypass the default manager — for example via Model._base_manager or an unfiltered queryset.
EMRBaseModel fields
The standard base for EMR resource models. On top of the BaseModel columns it adds audit, history, and metadata:
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
history |
JSONField |
no | ||
meta |
JSONField |
no | ||
created_by |
FK → users.User |
no | ||
updated_by |
FK → users.User |
no |
The %(app_label)s_%(class)s_… related_name template lets every concrete subclass reuse the same FK definitions without reverse-accessor collisions.
SlugBaseModel helpers
Extends EMRBaseModel for resources that expose a human-readable, scoped slug — value sets and definitions, for instance. It adds no columns of its own; concrete subclasses supply their own slug and, when facility-scoped, facility fields. It sets the class flag FACILITY_SCOPED = True.
| Method | Behaviour |
|---|---|
calculate_slug_from_facility(facility_external_id, slug)
|
Class helper → f-<facility_external_id>-<slug>
|
calculate_slug_from_instance(slug)
|
Class helper → i-<slug>
|
calculate_slug()
|
Instance helper — facility-scoped form when FACILITY_SCOPED and facility are set, otherwise instance-scoped
|
parse_slug(slug)
|
Reverses the encoding; returns {facility, slug_value} for f- slugs or {slug_value} for i- slugs; raises ValueError on invalid input
|
Slug encoding:
facility-scoped: f-<facility external_id (36-char UUID)>-<slug>
instance-scoped: i-<slug>
parse_slug reads the facility segment as slug[2:38] (36 chars), validates it as a UUID, takes slug[39:] as the slug value, and rejects any slug of length ≤ 2.
Related models
BaseManager
The default manager set as objects on BaseModel. Every query through Model.objects is implicitly scoped to deleted=False:
def get_queryset(self):
return super().get_queryset().filter(deleted=False)
BaseFlag
Abstract base for per-entity feature-flag tables (facility or organization flags, for example). It extends BaseModel, stores one validated flag name, and serves all lookups from cache.
| Field | Type | Notes |
|---|---|---|
flag |
CharField(max_length=1024) |
Flag name, validated against FlagRegistry for the subclass's flag_type
|
Subclasses configure these class attributes:
| Attribute | Purpose |
|---|---|
cache_key_template
|
Cache key for a single (entity_id, flag_name) pair
|
all_flags_cache_key_template
|
Cache key for all flags of one entity |
flag_type
|
Flag category validated against FlagRegistry
|
entity_field_name
|
Name of the FK field pointing at the owning entity |
Helper properties and classmethods:
| Member | Behaviour |
|---|---|
entity
|
Resolves the related entity via entity_field_name
|
entity_id
|
Resolves <entity_field_name>_id without a join
|
validate_flag(flag_name)
|
Validates the name against FlagRegistry for flag_type
|
check_entity_has_flag(entity_id, flag_name)
|
Cached exists() lookup (TTL 1 day)
|
get_all_flags(entity_id)
|
Cached tuple of all flag names for an entity (TTL 1 day) |
API layer
EMRResource
The Pydantic base (care/emr/resources/base.py) that every resource spec extends. It moves data both ways between Django model instances and the API schema, and holds the serialization rules in one place.
| Class attribute | Default | Role |
|---|---|---|
__model__
|
None
|
The Django model the spec serializes to/from |
__exclude__
|
[]
|
Field names skipped in both serialize and de_serialize
|
__store_metadata__
|
False
|
When True, spec fields that are not DB columns are read from / written to the model's meta JSON bag
|
__version__
|
0.1
|
Stamped onto every serialized object as version
|
meta
|
{}
|
Open metadata dict carried on the spec itself |
| Method | Direction | Behaviour |
|---|---|---|
serialize(obj, user=None)
|
DB → API | Builds a spec via model_construct from the DB object's columns; copies meta fields back out when __store_metadata__; calls perform_extra_serialization (always sets mapping["id"] = obj.external_id) and, when a user is passed, perform_extra_user_serialization; stamps version
|
de_serialize(obj=None, partial=False)
|
API → DB | Dumps the spec (exclude_defaults=True), writes mapped fields onto a new or existing model instance (skipping __exclude__, id, external_id), routes non-column fields into meta when __store_metadata__, then calls perform_extra_deserialization(is_update, obj). is_update is True when an existing obj is supplied
|
perform_extra_serialization(mapping, obj)
|
DB → API | Hook for resolving derived/nested read fields; base sets id
|
perform_extra_deserialization(is_update, obj)
|
API → DB | Hook for server-side side effects on write (e.g. appending to status_history, resolving FKs from external_id, validating coded values against a value set)
|
serialize_audit_users(mapping, obj)
|
DB → API | Helper that fills created_by / updated_by from a cached UserSpec
|
to_json()
|
— | model_dump(mode="json", exclude=["meta"])
|
get_database_mapping()
|
— | Lists the model's non-FK column names, used to decide which spec fields map to columns |
Each concrete resource exposes a small set of specs built on EMRResource, named by convention:
| Spec class | Kind | Role |
|---|---|---|
<X>SpecBase
|
shared | Common fields shared by the write/read specs |
<X>CreateSpec
|
write · create | Request body for POST; de_serialize builds a new model instance
|
<X>UpdateSpec
|
write · update | Request body for PUT/PATCH; de_serialize updates an existing instance (is_update=True)
|
<X>ListSpec
|
read · list | Lightweight response for list endpoints |
<X>ReadSpec / <X>RetrieveSpec
|
read · detail | Full response for the detail endpoint (often resolving nested/coded fields in perform_extra_serialization)
|
Coded fields commonly bind to a value set — a system/code allow-list. The resource spec declares the binding (e.g. via json_schema_extra={"slug": "<valueset-slug>"} on the field) and validates the submitted Coding against it in perform_extra_deserialization. Status fields commonly maintain a server-side status_history appended on create/update. Each resource page documents its exact bindings and side effects.
PeriodSpec
Defined alongside EMRResource in base.py. The validated period type used by write specs — distinct from the looser Period read type in common/.
| Field | Type | Required | Default | Validation |
|---|---|---|---|---|
start
|
datetime
|
no | None
|
must be timezone-aware |
end
|
datetime
|
no | None
|
must be timezone-aware |
When both are set, start must be ≤ end. Naive datetimes are rejected with "Start/End Date must be timezone aware".
PhoneNumber
An Annotated type exported from base.py: a phone string validated by PhoneNumberValidator with number_format="E164", no default region, and no region restriction. Use it wherever a spec field accepts a phone number.
Shared common specs
care/emr/resources/common/ holds the structured types reused across resources — the actual shapes behind many of the opaque JSONFields in the storage layer.
Coding
A single code from a code system. model_config = extra="forbid".
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
system |
str |
no | ||
version |
str |
no | ||
code |
str |
yes | ||
display |
str |
no |
CodeableConcept
A concept expressed as one or more codings plus free text. extra="forbid".
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
id |
str |
no | ||
coding |
list[Coding] |
no | ||
text |
str |
yes (field present) |
Period
The read/storage period shape — looser than PeriodSpec, with no timezone or ordering validation. extra="forbid".
| Field | Type | Required | Default |
|---|---|---|---|
id
|
str
|
no | None
|
start
|
datetime
|
no | None
|
end
|
datetime
|
no | None
|
Quantity
A measured amount, optionally with coded units. extra="forbid".
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
value |
Decimal |
no | ||
unit |
Coding |
no | ||
meta |
dict |
no | ||
code |
Coding |
no |
Ratio (same file) wraps two required Quantity values: numerator and denominator.
ContactPoint
A means of contact (phone, email, etc.). All three fields are required.
| Field | Type | Required | Notes |
|---|---|---|---|
system |
ContactPointSystemChoices |
yes | |
value |
str |
yes | |
use |
ContactPointUseChoices |
yes |
ContactPointSystemChoices values
| Value |
|---|
phone
|
fax
|
email
|
pager
|
url
|
sms
|
other
|
ContactPointUseChoices values
| Value |
|---|
home
|
work
|
temp
|
old
|
mobile
|
MonetaryComponent and pricing types
Pricing line components used by billing resources (see References/Charge Item Definition, References/Charge Item).
MonetaryComponent:
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
monetary_component_type |
MonetaryComponentType |
yes | ||
code |
Coding |
no | ||
factor |
Decimal |
no | ||
amount |
Decimal |
no | ||
tax_included_amount |
Decimal |
no | ||
global_component |
bool |
no | ||
conditions |
list[EvaluatorConditionSpec] |
no |
Model validators enforce:
tax_included_amountis only allowed whenmonetary_component_type == base.- A
basecomponent must have noconditionsand must setamount. amountandfactorare mutually exclusive (not both).- Either
amountorfactormust be present — unlessglobal_componentis set together with acode.
MonetaryComponentType values
| Value |
|---|
base
|
surcharge
|
discount
|
tax
|
informational
|
MonetaryComponentsWithoutBase (a RootModel over list[MonetaryComponent]) adds two list-level rules: no duplicate code.code values across the list; and, when a base component declares tax_included_amount, the sum of tax-component amounts (or base.amount * factor) plus tax_included_amount must equal the base amount. MonetaryComponents extends it with one more: at most one base component.
MonetaryComponentDefinition extends MonetaryComponent for definition-time use: it adds a required title: str, disables the duplicate-code / amount-or-factor checks, and forbids a base component entirely.
DiscountConfiguration:
| Field | Type | Notes |
|---|---|---|
max_applicable |
int |
ge=0
|
applicability_order |
DiscountApplicability |
enum below |
DiscountApplicability values
| Value |
|---|
total_asc
|
total_desc
|
EvaluatorConditionSpec
A single condition evaluated against a registered metric — used inside MonetaryComponent.conditions.
| Field | Type | Required | Notes |
|---|---|---|---|
metric |
str |
yes | |
operation |
str |
yes | |
value |
dict |
yes |
ValueSet definition types
The shapes used to define a value set (the FHIR compose structure), used by ValueSet.
ValueSet—name: str(required),status: str | None,compose: ValueSetCompose(required).ValueSetCompose—id,include: list[ValueSetInclude](required),exclude: list[ValueSetInclude] | None,property: list[str] | None.ValueSetInclude—id,system,version,concept: list[ValueSetConcept] | None,filter: list[ValueSetFilter] | None.conceptandfilterare mutually exclusive.ValueSetConcept—id,code,display(all optional).ValueSetFilter—id,property,op,value.opmust be one of:=,is-a,descendent-of,is-not-a,regex,in,not-in,generalizes,child-of,descendent-leaf,exists.
All ValueSet types use extra="forbid".
MailTypeChoices
Defined in common/mail_type.py.
| Name | Value |
|---|---|
create
|
create_password
|
reset
|
reset_password
|
Methods & save behaviour
BaseModel.delete()soft-deletes: setsdeleted=Trueand saves only that field, never a hardDELETE.BaseFlag.save()validatesflagagainstFlagRegistry, then evicts the single-flag and all-flags cache entries for the entity before persisting, so cached lookups stay consistent.BaseFlag.check_entity_has_flag/get_all_flagspopulate those caches on read with a 1-day TTL (FLAGS_CACHE_TTL = 60 * 60 * 24).SlugBaseModelexposes slug encode/parse helpers but does not overridesave(); subclasses decide when to callcalculate_slug().EMRResource.de_serializeis where write-time side effects happen — subclasses overrideperform_extra_deserializationto append to status histories, resolve FKs fromexternal_id, and validate coded fields against bound value sets.cacheable(...)(inbase.py) is a decorator that marks a spec cacheable and wires apost_savesignal to invalidate the per-instance serializer cache;model_from_cachethen serves serialized specs from cache bypk/id/external_id.
API integration notes
external_id(UUID) is the identifier used across Care's REST API and FHIR resources; the integer primary key is internal and never exposed.EMRResource.serializesurfaces it asid, andde_serializerefuses to writeid/external_idfrom request bodies.- Deletes are soft — a resource removed through the API still exists in the database with
deleted=True, hidden by the default manager. historyis not returned by standard endpoints; every version (including migration-time changes) is stored flattened with its performer and served through a separate, on-request audit API.metais the supported way to attach system metadata without a schema migration; specs with__store_metadata__ = Trueround-trip extra fields through it.created_by/updated_byare platform-maintained audit fields — clients must not set them directly.- Periods sent on write (
PeriodSpec) must be timezone-aware and ordered (start ≤ end); the read-sidePeriodtype does not enforce this. - Feature-flag state (
BaseFlagsubclasses) is read through the cached classmethods, not queried row-by-row.
Related
Pages that link here:
- References/Account
- References/Activity Definition
- References/Allergy Intolerance
- References/Charge Item
- References/Charge Item Definition
- References/Condition
- References/Diagnostic Report
- References/Facility Config
- References/Facility Flag
- References/Healthcare Service
- References/Inventory Item
- References/Invoice
- References/Location
- References/Medication Administration
- References/Medication Dispense
- References/Medication Request
- References/Medication Statement
- References/Notes
- References/Observation
- References/Observation Definition
- References/Payment Reconciliation
- References/Permission
- References/Permission Association
- References/Product
- References/Product Knowledge
- References/Report & Templates
- References/Resource Category
- References/Resource Request
- References/Role
- References/Specimen
- References/Supply Request
- References/Tagging
- References/User & Skills
- References/Value Set
- Reference: References/Charge Item Definition