Facility Flag
FacilityFlag binds a registered flag name to a single Facility to gate server-side behaviour. It is not a FHIR resource and has no Pydantic spec — no endpoint, serializer, or ...CreateSpec/...ReadSpec — so everything below comes from the Django storage model. Clients never write flags; they read them as a derived array on the Facility resource (see Resource specs).
Source:
- Model:
care/facility/models/facility_flag.py - Base/flag machinery:
care/utils/models/base.py - Flag registry & types:
care/utils/registries/feature_flag.py - Surfaced via Facility spec:
care/emr/resources/facility/spec.py
Models
| Model | Purpose |
|---|---|
FacilityFlag | Associates a registered feature flag with a single Facility, gating behaviour per facility |
FacilityFlag extends BaseFlag, which extends BaseModel — the Care EMR base providing external_id (UUID), created_date/modified_date audit timestamps, and soft-delete via deleted. BaseModel is leaner than EMRBaseModel (see Base model): no created_by/updated_by, no history, no meta JSON. BaseFlag adds the flag value and the per-entity machinery: cache invalidation and validation against the flag registry.
FacilityFlag fields
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
flag | CharField(max_length=1024) | yes | — | Inherited from BaseFlag. The registered flag name, validated against the FACILITY flag registry on every save (see validation). Not an enum — the value space is whatever has been registered at runtime via FlagRegistry |
facility | FK → facility.Facility | yes | — | on_delete=CASCADE, null=False, blank=False. The facility the flag applies to. Backs the entity / entity_id properties via entity_field_name="facility" |
external_id | UUIDField | auto | uuid4 | Inherited from BaseModel; unique=True, db_index=True |
created_date | DateTimeField | auto | auto_now_add | Inherited; null=True, blank=True, db_index=True |
modified_date | DateTimeField | auto | auto_now | Inherited; null=True, blank=True, db_index=True |
deleted | BooleanField | no | False | Inherited; soft-delete flag, db_index=True. The default manager (BaseManager) filters deleted=False |
The model has no JSONFields, so there is no opaque JSON shape to document.
Flag type values
flag_type is fixed to FlagType.FACILITY. The full FlagType enum (from feature_flag.py):
FlagType member | Value | Used by |
|---|---|---|
USER | "USER" | UserFlag (User) |
FACILITY | "FACILITY" | FacilityFlag (this model) |
The flag values themselves are not enumerated in source. They are registered at runtime via FlagRegistry.register(FlagType.FACILITY, "<name>"), and a name absent from the FACILITY registry raises FlagNotFoundError on save.
Class-level configuration
These class attributes wire FacilityFlag into the generic BaseFlag caching and validation logic. They are not database fields.
| Attribute | Value | Notes |
|---|---|---|
cache_key_template | "facility_flag_cache:{entity_id}:{flag_name}" | Per-flag existence cache key |
all_flags_cache_key_template | "facility_all_flags_cache:{entity_id}" | Cache key for a facility's full flag list |
flag_type | FlagType.FACILITY | Registry namespace used for flag-name validation |
entity_field_name | "facility" | Tells BaseFlag which FK is the owning entity; backs the entity / entity_id properties |
Module-level constants mirror these templates: FACILITY_FLAG_CACHE_KEY ("facility_flag_cache:{facility_id}:{flag_name}"), FACILITY_ALL_FLAGS_CACHE_KEY ("facility_all_flags_cache:{facility_id}"), and FACILITY_FLAG_CACHE_TTL (60 * 60 * 24, 1 day).
Constraints
UniqueConstraint(
fields=["facility", "flag"],
condition=Q(deleted=False),
name="unique_facility_flag",
)
A facility holds a given flag at most once among non-deleted rows. The condition excludes soft-deleted rows, so a flag can be removed and re-added. Meta.verbose_name = "Facility Flag".
Methods & save behaviour
save() (inherited from BaseFlag)
Every save runs four steps in order:
validate_flag(self.flag)callsFlagRegistry.validate_flag_name(FlagType.FACILITY, flag), which validates the flag type, then checks that the name is registered. An unknown type or unregistered name raisesFlagNotFoundError(a subclass of DjangoValidationError) and the row is not written.- The per-flag cache key (
facility_flag_cache:{facility_id}:{flag}) is deleted. - The all-flags cache key (
facility_all_flags_cache:{facility_id}) is deleted. - The row is persisted via
super().save().
Deletes use the BaseModel soft-delete: delete() sets deleted = True and saves update_fields=["deleted"]. It does not purge cache entries, so they expire by TTL or are rebuilt on the next miss.
Lookups
| Method | Returns | Notes |
|---|---|---|
check_facility_has_flag(facility_id, flag_name) | bool | Delegates to BaseFlag.check_entity_has_flag: validates the flag name, then cache.get_or_set on the per-flag key with an .exists() query; cached for FLAGS_CACHE_TTL (1 day) |
get_all_flags(facility_id) | tuple[FlagName] | Delegates to BaseFlag.get_all_flags: cache.get_or_set on the all-flags key, materializing values_list("flag", flat=True) for the facility; cached for 1 day |
Both wrap the generic BaseFlag methods using entity_field_name="facility". Facility.get_facility_flags() is a thin wrapper over FacilityFlag.get_all_flags(self.id).
Properties (inherited)
entity→ the relatedFacilityinstance (getattr(self, "facility"))entity_id→facility_id(getattr(self, "facility_id"))
Resource specs (API schema)
FacilityFlag has no Pydantic resource spec and no REST endpoint of its own. There is no FacilityFlagCreateSpec / FacilityFlagUpdateSpec / FacilityFlagListSpec / FacilityFlagRetrieveSpec, no serializer, and no viewset — confirmed by their absence from care/emr/resources/ and care/facility/api/. Flags are administered through Django admin or registration/management code, not the public API.
Flags reach API clients in exactly one place: the Facility resource exposes them as a derived read-only field.
| Spec | Field | Role | Behaviour |
|---|---|---|---|
FacilityRetrieveSpec (facility/spec.py) | flags: list[str] | read · detail | Populated in perform_extra_serialization via mapping["flags"] = obj.get_facility_flags(). Server-maintained and read-only; clients cannot set or mutate flags through the Facility write specs |
The flags array is the materialized list of registered flag names attached to the facility, from FacilityFlag.get_all_flags (cache-backed, 1-day TTL). No value set is bound to it — entries are the runtime FACILITY registry names, not a coded concept. The User resource mirrors this, exposing flags via obj.get_all_flags() backed by UserFlag (see User).
Related models
FacilityFlag defines no secondary models. Its behaviour comes from two utilities in care/utils.
BaseFlag (abstract)
flag → CharField(max_length=1024)
(abstract base — no table of its own)
class attrs: cache_key_template, all_flags_cache_key_template, flag_type, entity_field_name
Provides save-time validation, cache invalidation, and the check_entity_has_flag / get_all_flags lookups. Subclasses set flag_type, entity_field_name, and the two cache-key templates. See care/utils/models/base.py.
FlagRegistry
An in-process, class-level registry (_flags: dict[FlagType, dict[FlagName, bool]]) of valid flag names per FlagType. Key methods:
| Method | Notes |
|---|---|
register(flag_type, flag_name) | Adds a name to a type's registry, creating the type bucket if needed |
register_wrapper(flag_type, flag_name) | Class-decorator form of register |
unregister(flag_type, flag_name) | Removes a name; logs a warning if absent |
validate_flag_type(flag_type) | Raises FlagNotFoundError ("Invalid Flag Type") if the type bucket is missing |
validate_flag_name(flag_type, flag_name) | Validates type, then raises FlagNotFoundError ("Flag not registered") if the name is absent |
get_all_flags(flag_type) | list[FlagName] of registered names |
get_all_flags_as_choices(flag_type) | (value, value) tuples for Django choices |
A flag must be registered before a FacilityFlag referencing it can be saved. See care/utils/registries/feature_flag.py.
API integration notes
- There is no CRUD endpoint. Manage flags via Django admin or registration/management code; clients only observe them through the read-only
flags: list[str]field onFacilityRetrieveSpec(see Resource specs). - In application code, read through
check_facility_has_flag/get_all_flags(orFacility.get_facility_flags()), which are cache-backed. Do not query the table directly for hot-path checks. - Flag names are limited to entries registered in the
FACILITYFlagRegistry. An unregistered name fails save validation withFlagNotFoundError, so clients cannot introduce arbitrary flag strings. - The
{facility, flag}uniqueness holds only over non-deleted rows, so soft-deleted history is preserved and a flag can be removed then re-added.
Related
- Reference: Facility
- Reference: Facility Config
- Reference: User — the
USERflag counterpart of the sameBaseFlagmachinery - Reference: Base model
- Source: facility_flag.py on GitHub