Toggle menu
Toggle preferences menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.

References/Base models & conventions

From OHC Network Wiki
referencefoundationCARE 3.0+

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/models and care/utils/models. Several of their columns are opaque JSONFields 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 in care/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.



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_amount is only allowed when monetary_component_type == base.
  • A base component must have no conditions and must set amount.
  • amount and factor are mutually exclusive (not both).
  • Either amount or factor must be present — unless global_component is set together with a code.

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.

  • ValueSetname: str (required), status: str | None, compose: ValueSetCompose (required).
  • ValueSetComposeid, include: list[ValueSetInclude] (required), exclude: list[ValueSetInclude] | None, property: list[str] | None.
  • ValueSetIncludeid, system, version, concept: list[ValueSetConcept] | None, filter: list[ValueSetFilter] | None. concept and filter are mutually exclusive.
  • ValueSetConceptid, code, display (all optional).
  • ValueSetFilterid, property, op, value. op must 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: sets deleted=True and saves only that field, never a hard DELETE.
  • BaseFlag.save() validates flag against FlagRegistry, 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_flags populate those caches on read with a 1-day TTL (FLAGS_CACHE_TTL = 60 * 60 * 24).
  • SlugBaseModel exposes slug encode/parse helpers but does not override save(); subclasses decide when to call calculate_slug().
  • EMRResource.de_serialize is where write-time side effects happen — subclasses override perform_extra_deserialization to append to status histories, resolve FKs from external_id, and validate coded fields against bound value sets.
  • cacheable(...) (in base.py) is a decorator that marks a spec cacheable and wires a post_save signal to invalidate the per-instance serializer cache; model_from_cache then serves serialized specs from cache by pk / 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.serialize surfaces it as id, and de_serialize refuses to write id / external_id from request bodies.
  • Deletes are soft — a resource removed through the API still exists in the database with deleted=True, hidden by the default manager.
  • history is 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.
  • meta is the supported way to attach system metadata without a schema migration; specs with __store_metadata__ = True round-trip extra fields through it. created_by / updated_by are 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-side Period type does not enforce this.
  • Feature-flag state (BaseFlag subclasses) is read through the cached classmethods, not queried row-by-row.

Pages that link here: