Factorizer/Cluster ERP WikiCluster ERP Türkçe
Home / Desktop client

Explanation

Metadata store and foreign-key cache

How the desktop MetadataStore loads and prunes server metadata, hides modules by app scope, registers labels, and how FkCache loads and caches option lists.

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.load reads GET /_meta and 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 /_meta leaves the metadata as shipped and hides nothing (src/cluster_erp/desktop/core/metadata_store.py:39-82).
  • register_labels copies every metadata label into the translator under meta.<module>... keys (src/cluster_erp/desktop/core/metadata_store.py:119-192).
  • FkCache pages 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:

  1. GET /_meta fills the raw document. A failure returns False (src/cluster_erp/desktop/core/metadata_store.py:20-27).
  2. _apply_switches reads GET /_switches and calls prune_switched_off. A field or column whose switch is 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).
  3. prune_gated_fields removes every field that names a permission the user does not hold, in form groups and in action forms (src/cluster_erp/desktop/core/metadata_store.py:231-265).
  4. _apply_field_overlay reads GET /_overlay and calls apply_field_overlay (src/cluster_erp/desktop/core/metadata_store.py:39-46).
  5. _load_app_scope reads GET /app_store/registry and 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_fields that 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 storage set to extra. 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-234 states 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-51 states 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-66 states 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-220 states 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) imports prune_switched_off from 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.preload through GenericForm._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 of load( on the store.
  • FkCache._load runs 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, and DOCUMENT_VERSION have no user in src/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

Verified against commit 92f8051 on 2026-11-13.

Verified against commit 92f8051 · 2026-11-13