← Knowledge library

Facility administration

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…

3.1 · versioned documentation · 6 min read

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

Model Purpose
FacilityFlag Associates a registered feature flag with a single Facility, gating behaviour per facility

FacilityFlag extends BaseFlag, which extends BaseModel — the MANSOQ 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:

  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

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)

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

Related

Source evidence

References are pinned to the private MANSOQ source repository. Repository access is required to open these links.