References/Device
More languages
More actions
A Device tracks a physical or logical piece of equipment within a facility — from its lifecycle status to where it's placed and which encounter it's attached to.
The Django model is only the storage layer; it types fields loosely — CharField for status enums, JSONField for contact/metadata. The contract lives in the Pydantic resource specs under care/emr/resources/device/, which constrain those CharFields to enums, structure the JSON fields, and define validation, read/write schemas, and server-maintained side effects.
Source:
- Model: [https://github.com/ohcnetwork/care/blob/develop/care/emr/models/device.py
care/emr/models/device.py] - Spec: [https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/device/spec.py
care/emr/resources/device/spec.py] - History spec: [https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/device/history_spec.py
care/emr/resources/device/history_spec.py] - Viewset: [https://github.com/ohcnetwork/care/blob/develop/care/emr/api/viewsets/device.py
care/emr/api/viewsets/device.py] - Device-type registry: [https://github.com/ohcnetwork/care/blob/develop/care/emr/registries/device_type/device_registry.py
care/emr/registries/device_type/device_registry.py]
Models
| Model | Purpose |
|---|---|
Device
|
A physical or logical device tracked within a facility |
DeviceEncounterHistory
|
Records each period a device was associated with an Encounter
|
DeviceLocationHistory
|
Records each period a device was placed at a FacilityLocation
|
DeviceServiceHistory
|
Records servicing/maintenance events for a device |
All models extend EMRBaseModel (shared Care EMR base with external_id, audit fields, and soft-delete semantics).
Device fields
Device data
| Field | Type | Spec constraint | Notes |
|---|---|---|---|
identifier |
CharField(1024) |
str
| |
status |
CharField(16) |
DeviceStatusChoices (required)
| |
availability_status |
CharField(14) |
DeviceAvailabilityStatusChoices (required)
| |
manufacturer |
CharField(1024) |
str
| |
manufacture_date |
DateTimeField |
datetime
| |
expiration_date |
DateTimeField |
datetime
| |
lot_number |
CharField(1024) |
str
| |
serial_number |
CharField(1024) |
str
| |
registered_name |
CharField(1024) |
str (required)
| |
user_friendly_name |
CharField(1024) |
str
| |
model_number |
CharField(1024) |
str
| |
part_number |
CharField(1024) |
str
| |
contact |
JSONField (default {}) |
list[ContactPoint] (default [])
| |
care_type |
CharField(1024) (default None) |
str
| |
metadata |
JSONField (default {}) |
not exposed directly |
Virtual / spec-only field
| Field | Spec type | Notes |
|---|---|---|
care_metadata
|
dict (default {})
|
Not a DB column. On read it's populated by the care_type plugin — DeviceListSpec calls the plugin's list(obj), DeviceRetrieveSpec calls retrieve(obj). On write it carries plugin-specific data for handle_create/handle_update, which typically persist into the model's metadata column. Falls back to {} when there's no care_type or the plugin lookup fails
|
Relations
| Field | Type | Notes |
|---|---|---|
facility |
FK → Facility |
CASCADE; owning facility (required). Set server-side from the URL (facility_external_id), never from the request body
{{Field|model=Device|section=Relations|name=managing_organization|type=FK → FacilityOrganization|notes= |
current_location |
FK → FacilityLocation |
SET_NULL, nullable; where the device is currently placed. Excluded from the create/update body — managed via associate_location
|
current_encounter |
FK → Encounter |
SET_NULL, nullable; encounter the device is currently attached to. Excluded from the create/update body — managed via associate_encounter
|
Access cache
| Field | Type | Notes |
|---|---|---|
facility_organization_cache |
ArrayField[int] |
Denormalized cache of facility-organization IDs, rebuilt on every save(). Don't set it directly. get_queryset reads it to scope which devices a user may see
|
Enum values
DeviceStatusChoices values
status field. Defined in spec.py.
| Value | Meaning |
|---|---|
active
|
Device is in service |
inactive
|
Device is not currently in service |
entered_in_error
|
Record created in error |
DeviceAvailabilityStatusChoices values
availability_status field. Defined in spec.py.
| Value | Meaning |
|---|---|
lost
|
Device cannot be located |
damaged
|
Device is damaged |
destroyed
|
Device is destroyed |
available
|
Device is available for use |
ContactPoint shape
contact is list[ContactPoint]. Each item (from care/emr/resources/common/contact_point.py):
ContactPoint {
system: ContactPointSystemChoices # required
value: str # required
use: ContactPointUseChoices # required
}
system values
|
use values
|
|---|---|
phone, fax, email, pager, url, sms, other
|
home, work, temp, old, mobile
|
Related models
DeviceEncounterHistory
Audit trail of device-to-encounter associations. Attaching a device to an encounter creates a new row; detaching it stamps end. Read-only via the API (EMRModelReadOnlyViewSet), ordered by -end.
device → FK Device (CASCADE)
encounter → FK Encounter (CASCADE)
start → DateTimeField
end → DateTimeField (nullable)
DeviceLocationHistory
Audit trail of device placements. Moving a device to a location creates a new row; leaving it stamps end. Read-only via the API, ordered by -end.
device → FK Device (CASCADE)
location → FK FacilityLocation (CASCADE)
start → DateTimeField
end → DateTimeField (nullable)
DeviceServiceHistory
Servicing/maintenance log for a device. Writable via the API (create + update + list + retrieve).
device → FK Device (PROTECT, required)
serviced_on → DateTimeField (nullable, default None)
note → TextField (default "")
edit_history → JSONField (default [])
device uses PROTECT, so a Device with service-history rows can't be hard-deleted while those rows exist. edit_history is an append-only audit list the server maintains on every update (see Methods & save behaviour).
Resource specs (API schema)
The viewset wires these specs as create = DeviceCreateSpec, update = DeviceUpdateSpec, list = DeviceListSpec, retrieve = DeviceRetrieveSpec. All extend EMRResource (serialize / de_serialize, with perform_extra_serialization / perform_extra_deserialization hooks).
Device specs
| Spec class | Role | Adds / overrides | Excludes |
|---|---|---|---|
DeviceSpecBase
|
shared base | id, all device-data fields, status/availability_status enums, contact: list[ContactPoint], registered_name required
|
facility, managing_organization, current_location, current_encounter, care_metadata
|
DeviceCreateSpec
|
write · create | care_type (validated against DeviceTypeRegistry), care_metadata: dict
|
(inherits base excludes) |
DeviceUpdateSpec
|
write · update | care_metadata: dict — no care_type, which makes care_type immutable after create
|
(inherits base excludes) |
DeviceListSpec
|
read · list | id = external_id; care_metadata from plugin list(obj)
|
(inherits) |
DeviceRetrieveSpec
|
read · detail | full nested objects: current_location (FacilityLocationListSpec), current_encounter (EncounterListSpec), managing_organization (FacilityOrganizationReadSpec), created_by/updated_by (audit users); care_metadata from plugin retrieve(obj)
|
(inherits) |
Device history specs
| Spec class | Role | Fields | Excludes |
|---|---|---|---|
DeviceLocationHistoryListSpec
|
read · list | id, location (nested FacilityLocationListSpec), created_by, start, end?
|
device, location (raw FK)
|
DeviceEncounterHistoryListSpec
|
read · list | id, encounter (nested EncounterListSpec), created_by, start, end?
|
device, encounter (raw FK)
|
DeviceServiceHistorySpecBase
|
shared base | id
|
device, edit_history
|
DeviceServiceHistoryWriteSpec
|
write | serviced_on: datetime (required), note: str (required)
|
(inherits) |
DeviceServiceHistoryListSpec
|
read · list | adds created_date, modified_date; id = external_id
|
(inherits) |
DeviceServiceHistoryRetrieveSpec
|
read · detail | adds edit_history: list[dict] (each entry's updated_by hydrated to a UserSpec from cache), created_by, updated_by
|
(inherits) |
Validation rules
statusandavailability_statusmust each match one of their enum values, or the request is rejected.registered_nameis required on every write.care_type(create only) must be a registered device-type key ornull.DeviceCreateSpec.validate_care_typecallsDeviceTypeRegistry.get_care_device_class(value), which raises for unknown types.care_typecan't change on update —DeviceUpdateSpecdoesn't expose it.DeviceServiceHistory.serviced_onandnoteare required on write; updates are blocked onceedit_historyreaches 50 entries ("Cannot Edit instance anymore").ContactPoint.system,value, anduseare all required for each contact entry.
Methods & save behaviour
Device.save() — organization cache
Every save rebuilds facility_organization_cache:
- Look up the
FacilityOrganizationwithorg_type="root"for the device'sfacility; its ID seeds the cache. - If
managing_organizationis set, add itsidand itsparent_cache(the full ancestor chain). - Persist the resulting set as
facility_organization_cache.
This lets the list endpoint filter by organization hierarchy without traversing joins. Treat the cache as platform-maintained; managing_organization and facility are the writable inputs that drive it.
care_type plugin hooks
When care_type is set, the matching DeviceTypeBase subclass from DeviceTypeRegistry runs inside the create/update/delete transaction:
| Stage | Hook | Effect |
|---|---|---|
| create | handle_create(request_data, obj)
|
May mutate obj / obj.metadata and persist (e.g. the bundled camera plugin stores request_data["some_data"] into metadata)
|
| update | handle_update(request_data, obj)
|
Same, on update |
| destroy | handle_delete(obj)
|
Validation/cleanup before the device is soft-deleted (deleted=True)
|
| list | list(obj)
|
Supplies care_metadata for DeviceListSpec
|
| retrieve | retrieve(obj)
|
Supplies care_metadata for DeviceRetrieveSpec
|
DeviceServiceHistory edit history
On perform_update, the viewset snapshots the current DB row and appends {serviced_on, note, updated_by} to edit_history before writing the new values — a server-maintained, append-only trail of previous states. Updates are refused once it holds 50 entries.
API integration notes
facilitycomes from the URL (facility_external_id) on create, not from the body.current_location,current_encounter, andmanaging_organizationaren't writable through the device create/update body. Dedicated detail actions mutate them, and each one closes the open history row (stampsend) before opening a new one:POST associate_encounter—{ encounter: uuid | null }; closes the openDeviceEncounterHistory, setscurrent_encounter, and opens (and returns) a new history row.nulldetaches.POST associate_location—{ location: uuid | null }; same pattern againstDeviceLocationHistory.POST add_managing_organization—{ managing_organization: uuid }.POST remove_managing_organization— clearsmanaging_organization.
- When an encounter reaches a completed status,
disassociate_device_from_encounterclearscurrent_encounteron all attached devices and stampsendon their openDeviceEncounterHistoryrows. metadata(the model column) is plugin-owned; clients read deployment-specific data back through thecare_metadatavirtual field, notmetadata.facility_organization_cacheis platform-maintained — don't set it directly. The list endpoint scopes results to devices whose cache overlaps the requesting user's facility-organization memberships, or, when?location=is supplied, by location permission /parent_cachewith?include_children=.
Related
Pages that link here:
- Reference: References/Encounter