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

References/User & Skills

From OHC Network Wiki
referenceaccess-governanceCARE 3.0+

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:

  • User extends Django's AbstractUser, adding its own external_id and a soft-delete deleted flag. It is not an EMRBaseModel.
  • Skill, UserSkill, and MobileOTP extend BaseModel (external_id, created_date, modified_date, soft-delete via deleted).
  • UserFlag extends BaseFlag, a BaseModel subclass that adds a flag field plus a cache-invalidating save().
  • PlugConfig extends plain django.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 GenderChoices and require it; read specs return it as a plain string. See [[#genderchoices-values}}

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 0168. 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_orgs on create — UserCreateSpec.perform_extra_deserialization copies role_orgs onto obj._role_orgs so 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 via obj.set_password(self.password), or sets an unusable password when it is None.
  • Cache — UserSpec is @cacheable(use_base_manager=True). Saving a User invalidates the cache via post_save, and created_by is read through model_from_cache(UserSpec, ...). use_base_manager=True lets cached lookups resolve soft-deleted users.
  • Computed read fields — id (from external_id), profile_picture_url (resolved), mfa_enabled, role_orgs, flags, geo_organization, created_by, and the current-user organizations/facilities/permissions are all populated in perform_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.

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() — returns cached_role_orgs if set; otherwise loads from OrganizationUser.get_cached_role_orgs(self.id) and persists it via save(update_fields=["cached_role_orgs"]). Read specs surface the result as role_orgs.
  • read_profile_picture_url() — resolves profile_picture_url against FACILITY_CDN or the S3 bucket endpoint; returns None when unset. Backs every read spec's profile_picture_url.
  • is_mfa_enabled()True when mfa_settings["totp"]["enabled"] is set; surfaced as mfa_enabled.
  • full_name (property) — joins prefix, get_full_name(), and suffix.
  • check_username_exists(username) (static) — checks across the entire (including deleted) queryset; used by UserCreateSpec.validate_username.
  • get_all_flags() — delegates to UserFlag.get_all_flags(self.id); surfaced as flags in UserRetrieveSpec.

CustomUserManager adds:

  • get_queryset() filters deleted=False; get_entire_queryset() returns all rows.
  • create_superuser() forces phone_number="+919696969696", gender="non_binary", and user_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 as id. Never key on the integer PK or username alone.
  • CustomUserManager honours deleted: soft-deleted users drop out of normal queries, but cached UserSpec lookups use the base manager (use_base_manager=True) and can still resolve them — for example as a created_by reference.
  • Authorization is not stored on User. Access resolves through the roles/permissions/organization system, and cached_role_orgs is a platform-maintained cache — clients must not write it. New users supply role_orgs (organization + role UUIDs) at create time.
  • mfa_settings, preferences, and PlugConfig.meta are open JSON bags for evolving config without migrations. mfa_settings is mutated by the MFA flow (mfa/spec.py), not by user create/update.
  • is_service_account distinguishes machine/integration accounts from human users.
  • Write through UserCreateSpec / UserUpdateSpec; read through UserSpec (list), UserRetrieveSpec (detail), CurrentUserRetrieveSpec (self), and PublicUserReadSpec (public). Picture URLs, MFA status, roles, flags, and nested org/creator objects are all computed server-side.

Pages that link here: