The desktop client draws its screens from the metadata that the server publishes. This page explains how core/metadata_store.py loads and adjusts that metadata, how core/fk_cache.py loads and caches the option lists of foreign-key fields, and what core/entity_types.py declares, all under src/cluster_erp/desktop/. The server-side contract is described on metadata model. How the web client does the same is on metadata rendering.
In short¶
MetadataStore.loadreadsGET /_metaand then applies display switches, permission-gated fields, the tenant field overlay, and the app scope, in that order (src/cluster_erp/desktop/core/metadata_store.py:19-32).- A failure of any step after
/_metaleaves the metadata as shipped and hides nothing (src/cluster_erp/desktop/core/metadata_store.py:39-82). register_labelscopies every metadata label into the translator undermeta.<module>...keys (src/cluster_erp/desktop/core/metadata_store.py:119-192).FkCachepages through an option list 200 rows at a time, keeps it for 300 seconds, keeps at most 50 lists, and drops a module's lists on any change signal of that module (src/cluster_erp/desktop/core/fk_cache.py:13-14,src/cluster_erp/desktop/core/fk_cache.py:51-77,src/cluster_erp/desktop/core/fk_cache.py:164-214).
How it works¶
Loading metadata¶
load() runs once at start-up, before the main window exists. main() retries it up to three times (src/cluster_erp/desktop/main.py:194-222). The steps are:
GET /_metafills the raw document. A failure returnsFalse(src/cluster_erp/desktop/core/metadata_store.py:20-27)._apply_switchesreadsGET /_switchesand callsprune_switched_off. A field or column whoseswitchis off is removed from forms, list views, sub-tables, and action bodies. An unknown switch counts as on (src/cluster_erp/desktop/core/metadata_store.py:52-60,src/cluster_erp/desktop/core/metadata_store.py:443-473).prune_gated_fieldsremoves every field that names apermissionthe user does not hold, in form groups and in action forms (src/cluster_erp/desktop/core/metadata_store.py:231-265)._apply_field_overlayreadsGET /_overlayand callsapply_field_overlay(src/cluster_erp/desktop/core/metadata_store.py:39-46)._load_app_scopereadsGET /app_store/registryand computes the hidden modules (src/cluster_erp/desktop/core/metadata_store.py:67-82).
Tenant field overlay¶
apply_field_overlay changes the metadata in place for each entity in the overlay (src/cluster_erp/desktop/core/metadata_store.py:268-440):
- A shipped field that the tenant switched off leaves its group. It moves, read-only and not required, into one group with the key
closed_fieldsthat is shown only on existing records (src/cluster_erp/desktop/core/metadata_store.py:308-328,src/cluster_erp/desktop/core/metadata_store.py:429-440). - A shipped field with a required override becomes required (
src/cluster_erp/desktop/core/metadata_store.py:325-326). - A tenant-defined field joins its target group with
storageset toextra. A field with a condition gets its own conditional group right after the target group. A field gated by a permission is added only for holders (src/cluster_erp/desktop/core/metadata_store.py:329-394). - A tenant field marked for the list gets a column keyed
extra.<key>, inserted after the stretching column (src/cluster_erp/desktop/core/metadata_store.py:395-410). - Action forms that collect a field bucket follow the same switches and receive the tenant fields of that bucket, except derived fields (
src/cluster_erp/desktop/core/metadata_store.py:392-394,src/cluster_erp/desktop/core/metadata_store.py:411-428).
The server side of tenant fields is described on tenant fields.
App scope¶
hidden_modules(registry) returns the modules that are covered only by apps the user cannot use. An app is usable when its manifest is system or permanent, or when its state is installed, entitled, and seated. A module that no app covers is never hidden (src/cluster_erp/desktop/core/metadata_store.py:207-228). sorted_modules leaves hidden modules out and sorts the rest by order. module and entity still return a hidden module when asked by name (src/cluster_erp/desktop/core/metadata_store.py:93-110). Licensing is described on app store, licensing, and entitlements.
tests/test_desktop_app_scope.py pins this behaviour: the hidden set mirrors the server gate, the seat decides when present, direct lookup still works, switched-off fields are pruned, and a registry failure hides nothing (tests/test_desktop_app_scope.py:61-131).
Labels¶
Metadata labels are dictionaries keyed by language. resolve(label, lang) returns the text in the language, then English, then an empty string (src/cluster_erp/desktop/core/metadata_store.py:112-117). register_labels registers labels with the translator so that Translator.t can find them (src/cluster_erp/desktop/core/metadata_store.py:119-192, src/cluster_erp/desktop/i18n/translator.py:57-70):
| Key pattern | Label source | Source |
|---|---|---|
meta.<module>.group.<key> |
sidebar group | src/cluster_erp/desktop/core/metadata_store.py:131 |
meta.<module>.section.<key> |
sidebar section | src/cluster_erp/desktop/core/metadata_store.py:132-137 |
meta.<module>.<entity>.label and .label_plural |
entity | src/cluster_erp/desktop/core/metadata_store.py:140-142 |
meta.<module>.<entity>.col.<key> |
list view and sub-table columns | src/cluster_erp/desktop/core/metadata_store.py:144-149 |
meta.<module>.<entity>.filter.<key> and .filter.<key>.<value> |
list filters and their choices | src/cluster_erp/desktop/core/metadata_store.py:151-158 |
meta.<module>.<entity>.grp.<key>, .fld.<key>, .fld.<key>.<value> |
form groups, fields, and choices | src/cluster_erp/desktop/core/metadata_store.py:160-171 |
meta.<module>.<entity>.tab.<key> |
detail view tabs | src/cluster_erp/desktop/core/metadata_store.py:173-176 |
meta.<module>.<entity>.action.<key> |
actions | src/cluster_erp/desktop/core/metadata_store.py:178-179 |
meta.<module>.<entity>.scope |
scope selector | src/cluster_erp/desktop/core/metadata_store.py:181-183 |
Foreign-key cache¶
FkCache serves two maps per option list: id to display text, and id to the full row (src/cluster_erp/desktop/core/fk_cache.py:36-50). The cache key is module:endpoint:display_field, followed by the sorted query parameters when there are any, so that lists scoped by different parameters do not collide (src/cluster_erp/desktop/core/fk_cache.py:17-33).
| Method | Behaviour | Source |
|---|---|---|
preload |
Loads the list if it is missing or older than the lifetime, and returns a copy of the id-to-text map | src/cluster_erp/desktop/core/fk_cache.py:145-162 |
resolve |
Returns the display text for one id. An unknown id returns its first eight characters | src/cluster_erp/desktop/core/fk_cache.py:94-108 |
get_item |
Returns the full row for one id | src/cluster_erp/desktop/core/fk_cache.py:110-128 |
invalidate |
Drops every list of one module and endpoint | src/cluster_erp/desktop/core/fk_cache.py:130-137 |
invalidate_all |
Drops every list | src/cluster_erp/desktop/core/fk_cache.py:139-143 |
_load requests /<module><endpoint> with limit 200 and growing offset. It accepts a plain list or an object with items and total, and stops when a page is short or total is reached. The display text is the configured field, else name (src/cluster_erp/desktop/core/fk_cache.py:164-214). Before it adds a new key, it evicts the oldest entries until there is room (src/cluster_erp/desktop/core/fk_cache.py:85-92, src/cluster_erp/desktop/core/fk_cache.py:175-177). A failed load removes the key instead of caching an empty list (src/cluster_erp/desktop/core/fk_cache.py:215-223).
Entity descriptors¶
entity_types.py declares EntityDescriptor, a frozen dataclass with module and entity_type (src/cluster_erp/desktop/core/entity_types.py:6-9). It also declares five constants: NOTE and ATTACHMENT in the pseudo-module core, and DOCUMENT_TYPE, DOCUMENT, and DOCUMENT_VERSION in documents (src/cluster_erp/desktop/core/entity_types.py:15-29). Generic screens build their own descriptors from the metadata, for example src/cluster_erp/desktop/core/generic_api.py:22.
Why it is built this way¶
- Pruning gated fields: the docstring in
src/cluster_erp/desktop/core/metadata_store.py:232-234states that the API strips the value, so an empty box that cannot be written would otherwise appear. - Switches default to on: the comment in
src/cluster_erp/desktop/core/metadata_store.py:49-51states that nothing is ever blanked by accident. - The app scope mirrors the web shell: the comment in
src/cluster_erp/desktop/core/metadata_store.py:63-66states that the desktop shows the same modules as the web shell and that the API gate enforces the same rule. - No caching of failed loads: the comment in
src/cluster_erp/desktop/core/fk_cache.py:217-220states that an empty cached list could let a save silently clear unresolved foreign-key values.
Rules and boundaries¶
- The desktop and web clients must prune the same metadata shapes.
tests/test_order_shipment_chain.py:201(test_web_and_desktop_prune_the_same_shapes) importsprune_switched_offfrom the desktop store (tests/test_order_shipment_chain.py:16). - Option lists of a module are dropped on every change signal of that module, whatever the entity type (
src/cluster_erp/desktop/core/fk_cache.py:65-77).
Interaction with other parts¶
- The metadata comes from server routes described on metadata model and tenant fields.
Sidebar,MainWindow, and every generic widget read the store; see tabs, panes, and layout and generic widgets.- Foreign-key fields call
FkCache.preloadthroughGenericForm._fill_fk_combo(src/cluster_erp/desktop/widgets/generic/generic_form.py:841-900).
Limits and open points¶
- The metadata is loaded once per process. No code in
src/cluster_erp/desktop/reloads it after start-up, found by searching for calls ofload(on the store. FkCache._loadruns the HTTP requests while it holds the cache lock and on the calling thread (src/cluster_erp/desktop/core/fk_cache.py:102-104,src/cluster_erp/desktop/core/fk_cache.py:164-189). Forms call it from the GUI thread (src/cluster_erp/desktop/widgets/generic/generic_form_dialog.py:189-190).- The fallback label of the closed-fields group is a literal English text, used when the overlay sends none (
src/cluster_erp/desktop/core/metadata_store.py:275). - The constants
DOCUMENT_TYPE,DOCUMENT, andDOCUMENT_VERSIONhave no user insrc/cluster_erp/desktop/.
Terms introduced¶
| Term | Code identifier | Definition | Turkish term | Source |
|---|---|---|---|---|
| metadata store | MetadataStore |
The client-side copy of the server metadata that is loaded once, pruned, and served to the generic screens. | metadata deposu | src/cluster_erp/desktop/core/metadata_store.py:13 |
| foreign-key cache | FkCache |
The desktop cache of whole option lists for foreign-key fields and columns. | yabancı anahtar önbelleği | src/cluster_erp/desktop/core/fk_cache.py:36 |
| hidden modules | hidden_modules |
The modules covered only by apps the user cannot use, left out of the sidebar. | gizli modüller | src/cluster_erp/desktop/core/metadata_store.py:207 |
| closed fields group | closed_fields |
The read-only group that shows switched-off fields on existing records. | kapalı alanlar grubu | src/cluster_erp/desktop/core/metadata_store.py:434 |
Related pages¶
Verified against commit 92f8051 on 2026-11-13.