References/User & Skills
More languages
More actions
A User is a Care account — authentication, profile, clinician credentials, and notification/MFA data. The Django model is the storage layer. The Pydantic resource specs in care/emr/resources/user/ and care/emr/resources/mfa/ are the API layer: they define enums, validation, the shape of the opaque JSONFields, and the separate request/response schemas you write to and read from.
Source: [https://github.com/ohcnetwork/care/blob/develop/care/users/models.py care/users/models.py], [https://github.com/ohcnetwork/care/blob/develop/care/facility/models/patient.py care/facility/models/patient.py], [https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/user/spec.py care/emr/resources/user/spec.py], [https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/mfa/spec.py care/emr/resources/mfa/spec.py]
Models
| Model | Purpose |
|---|---|
User
|
Care account: authentication, profile, clinician credentials, and notification/MFA data |
Skill
|
Instance-wide skill/competency definition |
UserSkill
|
Through model linking a User to a Skill
|
UserFlag
|
Feature flag scoped to a single User
|
PlugConfig
|
Slug-keyed JSON configuration for installed plugs |
MobileOTP
|
One-time password issued against a phone number |
The base class determines which audit fields each model carries:
Userextends Django'sAbstractUser, adding its ownexternal_idand a soft-deletedeletedflag. It is not anEMRBaseModel.Skill,UserSkill, andMobileOTPextendBaseModel(external_id,created_date,modified_date, soft-delete viadeleted).UserFlagextendsBaseFlag, aBaseModelsubclass that adds aflagfield plus a cache-invalidatingsave().PlugConfigextends plaindjango.db.models.Model— no audit or soft-delete fields.
User fields
User inherits password, email, first_name, last_name, is_staff, is_active, is_superuser, last_login, and date_joined from AbstractUser. The fields below are Care additions or overrides. Queries run through CustomUserManager, which drops deleted=True rows by default; get_entire_queryset() bypasses that filter.
Identity & profile
| Field | Type | Notes |
|---|---|---|
external_id |
UUIDField |
Unique, indexed; stable public identifier. Surfaced as id in every read spec
|
username |
CharField(150) |
Unique; model-validated by UsernameValidator. On create, the API additionally enforces ^[a-zA-Z0-9_-]{3,}$ and global uniqueness, including deleted users
|
user_type |
CharField(100) |
Nullable; free-text role label (e.g. administrator). No user spec exposes it — set internally or by create_superuser
|
prefix |
CharField(10) |
Name prefix (e.g. Dr.); spec caps length at 10. Optional in specs |
suffix |
CharField(50) |
Name suffix; spec caps length at 50. Optional in specs
{{Field|model=User&Skills|section=Identity & profile|name=gender|type=CharField(100)|notes=Nullable in DB. Write specs constrain it to |
old_gender |
IntegerField |
Legacy GENDER_CHOICES (1 Male, 2 Female, 3 Non-binary); nullable. No user spec exposes it
|
date_of_birth |
DateField |
Nullable. Read-only (nullable string) in CurrentUserRetrieveSpec
|
profile_picture_url |
CharField(500) |
Storage key, not a URL. Read specs return the resolved URL via read_profile_picture_url(), not the raw key
|
created_by |
FK → User (self, SET_NULL) |
Account creator; related_name="users_created". Serialized in UserRetrieveSpec as a cached nested UserSpec dict (nullable)
|
deleted |
BooleanField |
Soft-delete flag (default False); read-only in UserSpec
|
verified |
BooleanField |
Default False; read-only in CurrentUserRetrieveSpec
|
is_service_account |
BooleanField |
Default False; marks machine/integration accounts. Settable on create, read in UserRetrieveSpec
|
Contact
| Field | Type | Notes |
|---|---|---|
phone_number |
CharField(14) |
Model: mobile_or_landline_number_validator. Required in specs, capped at 14 chars; must be globally unique on create
|
alt_phone_number |
CharField(14) |
Nullable; model mobile_validator. Read-only string in CurrentUserRetrieveSpec
|
video_connect_link |
URLField |
Nullable; tele-consult link. No user spec exposes it |
Clinician credentials
| Field | Type | Notes |
|---|---|---|
qualification |
TextField |
Nullable. Read-only (nullable string) in CurrentUserRetrieveSpec
|
doctor_experience_commenced_on |
DateField |
Nullable; experience is derived from this. Read-only (nullable string) in CurrentUserRetrieveSpec
|
doctor_medical_council_registration |
CharField(255) |
Nullable; council registration number. Read-only (nullable string) in CurrentUserRetrieveSpec
|
weekly_working_hours |
IntegerField |
Nullable; model-validated 0–168. Read-only (nullable string) in CurrentUserRetrieveSpec
|
Organization & facility
| Field | Type | Notes |
|---|---|---|
geo_organization |
FK → emr.Organization (SET_NULL) |
Geographic/administrative org. Write specs accept a UUID4 and resolve it to an Organization with org_type="govt" (404 otherwise). Read in UserRetrieveSpec as a nested OrganizationReadSpec dict. Listed in UserBaseSpec.__exclude__, so it never round-trips through the generic field copy
|
home_facility |
FK → facility.Facility (PROTECT) |
Primary facility. No user spec exposes it |
skills |
ManyToManyField → Skill |
Through UserSkill
|
cached_role_orgs |
JSONField |
Nullable; cached role/organization map. Lazily populated by get_cached_role_orgs() and surfaced as role_orgs in read specs. Platform-maintained — do not write directly
|
Notifications, MFA & preferences
| Field | Type | Notes |
|---|---|---|
pf_endpoint |
TextField |
Web-push endpoint; nullable. Read-only in CurrentUserRetrieveSpec
|
pf_p256dh |
TextField |
Web-push key; nullable. Read-only in CurrentUserRetrieveSpec
|
pf_auth |
TextField |
Web-push auth secret; nullable. Read-only in CurrentUserRetrieveSpec
|
totp_secret |
TextField |
Nullable; TOTP seed. Never serialized; written only via the MFA setup/verify flow |
mfa_settings |
JSONField |
Default {}. Shape: { "totp": { "enabled": bool, ... } }. is_mfa_enabled() reads mfa_settings["totp"]["enabled"]; surfaced as the boolean mfa_enabled in UserSpec. Clients never write it directly
|
preferences |
JSONField |
Default {}; open per-user UI/app preferences bag. Read-only dict in CurrentUserRetrieveSpec
|
REQUIRED_FIELDS = ["email"], and the model is managed by CustomUserManager.
Enum values
GenderChoices values
Bound to gender on every write spec (UserUpdateSpec, UserCreateSpec). Defined in [https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/patient/spec.py care/emr/resources/patient/spec.py] and reused here; read specs return gender as a free string.
| Value |
|---|
male
|
female
|
non_binary
|
transgender
|
create_superuser() sets gender="non_binary", which is a member of this enum. The model's legacy integer GENDER_CHOICES (1 Male, 2 Female, 3 Non-binary) is separate and unused by the API.
LoginMethod values
Used by MFALoginRequest.method in [https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/mfa/spec.py care/emr/resources/mfa/spec.py].
| Value | Meaning |
|---|---|
totp
|
Authenticator app one-time code |
backup
|
Single-use backup recovery code |
Resource specs (API schema)
Every user spec builds on EMRResource ([https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py care/emr/resources/base.py]), which provides serialize (DB → pydantic) and de_serialize (pydantic → DB) plus the perform_extra_serialization / perform_extra_deserialization hooks. UserBaseSpec sets __model__ = User and __exclude__ = ["geo_organization"].
| Spec class | Role | Fields / behaviour |
|---|---|---|
UserBaseSpec
|
shared base | id, first_name, last_name, phone_number (max 14), prefix (max 10, optional), suffix (max 50, optional)
|
UserUpdateSpec
|
write · update | Base + gender (GenderChoices, required), phone_number (max 14), geo_organization (UUID4, optional). De-serialize resolves geo_organization to an Organization with org_type="govt" (404 if missing)
|
UserCreateSpec
|
write · create | Extends UserUpdateSpec + password (optional), username, email, is_service_account (default False), role_orgs: list[UserRoleOrgCreateSpec]. Validates username pattern/uniqueness, phone uniqueness, email format/uniqueness, and password strength. De-serialize stashes role_orgs on the instance and calls set_password()
|
UserRoleOrgCreateSpec
|
write · nested | organization: UUID4, role: UUID4 — one role-in-organization assignment supplied at user creation
|
UserSpec
|
read · list/summary | Base + last_login, profile_picture_url, gender (string), username, mfa_enabled (default False), phone_number, deleted (default False), role_orgs: dict. @cacheable(use_base_manager=True) — cached, and resolves deleted users. Sets id from external_id, resolves the picture URL, computes mfa_enabled, loads role_orgs
|
UserRetrieveSpec
|
read · detail | None, email, flags: list[str], is_service_account. Serializes created_by (cached UserSpec), geo_organization (OrganizationReadSpec), and flags (get_all_flags())
|
CurrentUserRetrieveSpec
|
read · self (/users/getcurrentuser-style)
|
Extends UserRetrieveSpec + is_superuser, qualification, doctor_experience_commenced_on, doctor_medical_council_registration, weekly_working_hours, alt_phone_number, date_of_birth (all nullable strings), verified, pf_endpoint/pf_p256dh/pf_auth, organizations: list[dict], facilities: list[dict], permissions: list[str], preferences: dict. Computes the caller's organizations, facilities (excluding deleted), and resolved permission slugs
|
PublicUserReadSpec
|
read · public | Base + last_login, profile_picture_url, gender, username, role_orgs: list[dict]. Minimal unauthenticated projection
|
The password-reset flow uses plain-BaseModel request/response DTOs (not EMRResource, no model binding), all in user/spec.py:
| DTO | Shape |
|---|---|
ResetPasswordCheckRequest
|
{ token: str }
|
ResetPasswordConfirmRequest
|
{ token: str, password: str }
|
ResetPasswordResponse
|
{ detail: str }
|
ResetPasswordRequestTokenRequest
|
{ username: str }
|
MFA specs
Plain-BaseModel DTOs in [https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/mfa/spec.py care/emr/resources/mfa/spec.py]. They carry no model binding and drive the TOTP/backup-code flow that writes totp_secret and mfa_settings.
| DTO | Direction | Shape |
|---|---|---|
TOTPSetupResponse
|
response | { uri: str, secret_key: str }
|
TOTPVerifyRequest
|
request | { code: str }
|
TOTPVerifyResponse
|
response | { backup_codes: list[str] }
|
PasswordVerifyRequest
|
request | { password: str }
|
MFALoginRequest
|
request | { method: LoginMethod, code: str, temp_token: str }
|
MFALoginResponse
|
response | { access: str, refresh: str } (JWT pair)
|
Validation rules (write specs)
| Field | Rule | Source |
|---|---|---|
username
|
Matches ^[a-zA-Z0-9_-]{3,}$; must not already exist (checked against the entire queryset, including deleted)
|
UserCreateSpec.validate_username
|
phone_number
|
Must not already exist on any user | UserCreateSpec.validate_phone_number
|
email
|
Must not already exist; must pass Django validate_email
|
UserCreateSpec.validate_user_email
|
password
|
If provided, must pass Django validate_password (else "Password is too weak"); None is allowed
|
UserCreateSpec.validate_password
|
gender
|
Must be a GenderChoices member
|
type annotation |
geo_organization
|
Must reference an existing Organization with org_type="govt"
|
UserUpdateSpec.perform_extra_deserialization
|
Server-maintained behaviour
role_orgson create —UserCreateSpec.perform_extra_deserializationcopiesrole_orgsontoobj._role_orgsso the view can apply role/organization assignments after the user is saved. It is not a DB column.set_password— create hashes the supplied password viaobj.set_password(self.password), or sets an unusable password when it isNone.- Cache —
UserSpecis@cacheable(use_base_manager=True). Saving aUserinvalidates the cache viapost_save, andcreated_byis read throughmodel_from_cache(UserSpec, ...).use_base_manager=Truelets cached lookups resolve soft-deleted users. - Computed read fields —
id(fromexternal_id),profile_picture_url(resolved),mfa_enabled,role_orgs,flags,geo_organization,created_by, and the current-userorganizations/facilities/permissionsare all populated inperform_extra_serialization, never copied straight from columns.
Shared spec types
Defined in care/emr/resources/base.py and care/emr/resources/common/. No user spec binds these period and contact types; they belong to the shared resource vocabulary documented here for cross-reference.
| Type | Shape | Source |
|---|---|---|
PeriodSpec
|
None, end: datetime | None } — both must be timezone-aware; start ≤ end enforced
|
[https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py base.py]
|
Period
|
{ id: str?, start: datetime?, end: datetime? }, extra="forbid"
|
[https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/common/period.py common/period.py]
|
ContactPoint
|
{ system: ContactPointSystemChoices, value: str, use: ContactPointUseChoices }
|
[https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/common/contact_point.py common/contact_point.py]
|
PhoneNumber
|
E.164 string (PhoneNumberValidator)
|
[https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py base.py]
|
ContactPointSystemChoices: phone, fax, email, pager, url, sms, other. ContactPointUseChoices: home, work, temp, old, mobile.
Related models
Skill
Instance-wide competency definition. Extends BaseModel.
name → CharField(255), unique
description → TextField (nullable, default "")
UserSkill
Through model joining User and Skill. Extends BaseModel.
user → FK User (CASCADE, nullable)
skill → FK Skill (CASCADE, nullable)
The unique_user_skill constraint blocks duplicate (skill, user) pairs among non-deleted rows.
UserFlag
Feature flag attached to a single user. Extends BaseFlag (a BaseModel subclass with a flag field).
user → FK User (CASCADE)
flag → CharField(1024) (inherited from BaseFlag)
flag_type = FlagType.USER. The unique_user_flag constraint blocks duplicate (user, flag) pairs among non-deleted rows. Flags are read through UserFlag.check_user_has_flag(user_id, flag_name) and get_all_flags(user_id), both cache-backed (TTL 1 day). UserRetrieveSpec.flags surfaces these names as a list[str].
PlugConfig
Slug-keyed configuration for installed plugs. Extends plain models.Model (no audit/soft-delete fields).
slug → CharField(255), unique
meta → JSONField (default {})
MobileOTP
Defined in care/facility/models/patient.py. One-time password issued against a phone number for verification flows. Extends BaseModel.
is_used → BooleanField (default False)
phone_number → CharField(14) (mobile_or_landline_number_validator)
otp → CharField(10)
Methods & save behaviour
User overrides neither save() nor delete(). Its helpers back the computed read fields:
get_cached_role_orgs()— returnscached_role_orgsif set; otherwise loads fromOrganizationUser.get_cached_role_orgs(self.id)and persists it viasave(update_fields=["cached_role_orgs"]). Read specs surface the result asrole_orgs.read_profile_picture_url()— resolvesprofile_picture_urlagainstFACILITY_CDNor the S3 bucket endpoint; returnsNonewhen unset. Backs every read spec'sprofile_picture_url.is_mfa_enabled()—Truewhenmfa_settings["totp"]["enabled"]is set; surfaced asmfa_enabled.full_name(property) — joinsprefix,get_full_name(), andsuffix.check_username_exists(username)(static) — checks across the entire (including deleted) queryset; used byUserCreateSpec.validate_username.get_all_flags()— delegates toUserFlag.get_all_flags(self.id); surfaced asflagsinUserRetrieveSpec.
CustomUserManager adds:
get_queryset()filtersdeleted=False;get_entire_queryset()returns all rows.create_superuser()forcesphone_number="+919696969696",gender="non_binary", anduser_type="administrator".make_random_password()generates a secure password guaranteeing lower/upper/digit composition.
UserFlag.save() (via BaseFlag) validates the flag name against the registry and invalidates the per-flag and all-flags cache keys on every write.
API integration notes
- The public identifier is
external_id, returned asid. Never key on the integer PK orusernamealone. CustomUserManagerhonoursdeleted: soft-deleted users drop out of normal queries, but cachedUserSpeclookups use the base manager (use_base_manager=True) and can still resolve them — for example as acreated_byreference.- Authorization is not stored on
User. Access resolves through the roles/permissions/organization system, andcached_role_orgsis a platform-maintained cache — clients must not write it. New users supplyrole_orgs(organization + role UUIDs) at create time. mfa_settings,preferences, andPlugConfig.metaare open JSON bags for evolving config without migrations.mfa_settingsis mutated by the MFA flow (mfa/spec.py), not by user create/update.is_service_accountdistinguishes machine/integration accounts from human users.- Write through
UserCreateSpec/UserUpdateSpec; read throughUserSpec(list),UserRetrieveSpec(detail),CurrentUserRetrieveSpec(self), andPublicUserReadSpec(public). Picture URLs, MFA status, roles, flags, and nested org/creator objects are all computed server-side.
Related
Pages that link here:
- Concepts/User
- References/Consent
- References/Encounter
- References/File Upload
- References/Schedule & Availability
- Concept: Concepts/Patient
- Reference: References/Base models & conventions
Wiki: ohcnwiki.tellmey.fyi