Skip to main content
Version: 3.1

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:

Models

ModelPurpose
FacilityFlagAssociates 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

FieldTypeRequiredDefaultNotes
flagCharField(max_length=1024)yesInherited 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
facilityFK → facility.Facilityyeson_delete=CASCADE, null=False, blank=False. The facility the flag applies to. Backs the entity / entity_id properties via entity_field_name="facility"
external_idUUIDFieldautouuid4Inherited from BaseModel; unique=True, db_index=True
created_dateDateTimeFieldautoauto_now_addInherited; null=True, blank=True, db_index=True
modified_dateDateTimeFieldautoauto_nowInherited; null=True, blank=True, db_index=True
deletedBooleanFieldnoFalseInherited; 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 memberValueUsed 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.

AttributeValueNotes
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_typeFlagType.FACILITYRegistry 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:

  1. validate_flag(self.flag) calls FlagRegistry.validate_flag_name(FlagType.FACILITY, flag), which validates the flag type, then checks that the name is registered. An unknown type or unregistered name raises FlagNotFoundError (a subclass of Django ValidationError) and the row is not written.
  2. The per-flag cache key (facility_flag_cache:{facility_id}:{flag}) is deleted.
  3. The all-flags cache key (facility_all_flags_cache:{facility_id}) is deleted.
  4. 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

MethodReturnsNotes
check_facility_has_flag(facility_id, flag_name)boolDelegates 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 related Facility instance (getattr(self, "facility"))
  • entity_idfacility_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.

SpecFieldRoleBehaviour
FacilityRetrieveSpec (facility/spec.py)flags: list[str]read · detailPopulated 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).

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:

MethodNotes
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 on FacilityRetrieveSpec (see Resource specs).
  • In application code, read through check_facility_has_flag / get_all_flags (or Facility.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 FACILITY FlagRegistry. An unregistered name fails save validation with FlagNotFoundError, 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.