References/Facility Flag: Difference between revisions
More languages
More actions
OHC identity seed |
Automated edit (via update-page on MediaWiki MCP Server) |
||
| Line 217: | Line 217: | ||
* Flag names are limited to entries registered in the <code>FACILITY</code> <code>FlagRegistry</code>. An unregistered name fails save validation with <code>FlagNotFoundError</code>, so clients cannot introduce arbitrary flag strings. |
* Flag names are limited to entries registered in the <code>FACILITY</code> <code>FlagRegistry</code>. An unregistered name fails save validation with <code>FlagNotFoundError</code>, so clients cannot introduce arbitrary flag strings. |
||
* The <code>{facility, flag}</code> uniqueness holds only over non-deleted rows, so soft-deleted history is preserved and a flag can be removed then re-added. |
* The <code>{facility, flag}</code> uniqueness holds only over non-deleted rows, so soft-deleted history is preserved and a flag can be removed then re-added. |
||
{{Navbox facility}} |
|||
{{Related}} |
{{Related}} |
||
Latest revision as of 09:32, 6 July 2026
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: [https://github.com/ohcnetwork/care/blob/develop/care/facility/models/facility_flag.py
care/facility/models/facility_flag.py] - Base/flag machinery: [https://github.com/ohcnetwork/care/blob/develop/care/utils/models/base.py
care/utils/models/base.py] - Flag registry & types: [https://github.com/ohcnetwork/care/blob/develop/care/utils/registries/feature_flag.py
care/utils/registries/feature_flag.py] - Surfaced via Facility spec: [https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/facility/spec.py
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 | ||
facility |
FK → facility.Facility |
yes | ||
external_id |
UUIDField |
auto | ||
created_date |
DateTimeField |
auto | ||
modified_date |
DateTimeField |
auto | ||
deleted |
BooleanField |
no |
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 [https://github.com/ohcnetwork/care/blob/develop/care/utils/registries/feature_flag.py feature_flag.py]):
FlagType member
|
Value | Used by |
|---|---|---|
USER
|
"USER"
|
UserFlag ([[References/User & Skills
|
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 ([https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/facility/spec.py 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 [https://github.com/ohcnetwork/care/blob/develop/care/utils/models/base.py 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 [https://github.com/ohcnetwork/care/blob/develop/care/utils/registries/feature_flag.py 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: References/Base models & conventions