ECM Model
The ECM model is the central concept of ecmind-blue-client. It provides a typed, ORM-style interface for ECM objects — folders, registers, and documents.
Every object returned by ecm.dms.select(), ecm.dms.get(), ecm.dms.insert_and_get(), or ecm.dms.update_and_get() is an instance of a model class. Model instances carry both index field values (your custom fields) and system metadata (always via obj.system).
1. Model classes
Define a model class by subclassing one of the three base classes and declaring typed fields as class annotations:
from ecmind_blue_client.ecm.model import ECMFolderModel, ECMRegisterModel, ECMDocumentModel, ECMField, ECMTableField, ECMTableRowModel
class InvoiceRow(ECMTableRowModel):
Amount: ECMField[float]
Description: ECMField[str]
class InvoiceFolder(ECMFolderModel):
_internal_name_ = "InvoiceFolder" # server-side internal name of the object type
Title: ECMField[str]
Year: ECMField[int]
Positions: ECMTableField[InvoiceRow]
class InvoiceRegister(ECMRegisterModel):
_internal_name_ = "InvoiceRegister"
Name: ECMField[str]
class InvoiceDocument(ECMDocumentModel):
_internal_name_ = "InvoiceDocument"
Subject: ECMField[str]
Amount: ECMField[float]
| Base class | Use for |
|---|---|
|
Folder object types |
|
Register (sub-folder) object types |
|
Document object types |
1.1. Dynamic models
When the object type is only known at runtime, use the factory functions instead of a class statement:
from ecmind_blue_client.ecm.model import make_folder_model, make_register_model, make_document_model
InvoiceFolder = make_folder_model("InvoiceFolder")
Dynamic models support the same query API. Undeclared fields are accessed via obj["internalName"].
2. ECMField
ECMField[T] is the descriptor for typed index fields. The type parameter T determines the Python type of the field value (str, int, float, datetime, date, time, bool).
At class level, ECMField acts as a condition builder:
InvoiceFolder.Year == 2024 # ECMCondition
InvoiceFolder.Year >= 2020 # ECMCondition
InvoiceFolder.Title.in_("A", "B") # ECMCondition
InvoiceFolder.Year.DESC # ECMSortOrder for order_by()
At instance level, it returns the stored value:
folder = ecm.dms.get(InvoiceFolder, 12345)
print(folder.Year) # int | None
3. ECMTableField
ECMTableField[RowT] holds multi-row table fields. The row class must subclass ECMTableRowModel and declare its columns as ECMField annotations.
for row in folder.Positions:
print(row.Amount, row.Description)
Each row exposes:
| Attribute | Description |
|---|---|
|
Internal row identifier assigned by the server. |
|
|
|
Returns |
4. system properties
Every model instance exposes a system attribute that holds all server-populated metadata. Index fields (your ECMField declarations) and system properties are kept strictly separate.
4.1. Always available
The following properties are always populated, regardless of which flags were passed to the query or get call:
| Property | Type | Description |
|---|---|---|
|
|
Numeric ID of the object on the server. |
|
|
Display name of the object as stored on the server. Usually the value of the key field. |
|
|
|
|
|
|
|
|
Set of internal field names that have been modified since loading. |
|
|
Set of table field names that have been modified (row added, removed, or changed) since loading. |
|
|
Returns |
4.2. Available per object type
Some system properties are only present on specific model types:
| Property | Type | Available on | Description |
|---|---|---|---|
|
|
|
ID of the parent folder. Only populated when |
|
|
|
ID of the directly enclosing register, if the register is nested inside another register. |
|
|
|
ID of the parent register. Only populated when |
|
|
|
Type ID of the parent register. |
4.3. Optional — loaded on request
The following properties are None by default and must be explicitly requested via flags on select(), get(), insert_and_get(), or update_and_get().
4.3.1. system.rights
| Attribute | Type | Description |
|---|---|---|
|
|
May create child objects (registers or documents in a folder; documents in a register). |
|
|
May modify the index fields of the object. |
|
|
May read the file of the document. |
|
|
May modify the file of the document. |
|
|
May delete the object. |
folder = ecm.dms.get(InvoiceFolder, 12345, rights=True)
if folder.system.rights.edit_metadata:
print("editing allowed")
4.3.2. system.base_params
Populated when base_params=True (get()) or .base_params() (select()) is passed. Type: ECMModelBaseParams.
| Attribute | Type | Description |
|---|---|---|
|
|
Username of the user who created the object. |
|
|
Timestamp of object creation. |
|
|
Current owner of the object. |
|
|
Username of the user who last modified the object. |
|
|
Timestamp of the last modification. |
|
|
Number of links to this object. |
|
|
Number of text notices (remarks) attached to the object. |
folder = ecm.dms.get(InvoiceFolder, 12345, base_params=True)
print(f"Created by {folder.system.base_params.creator} on {folder.system.base_params.creation_date}")
4.3.3. system.file_properties
Populated when file_properties=True (get()) or .file_properties() (select()) is passed. Only meaningful for ECMDocumentModel. Type: ECMModelFileProperties.
| Attribute | Type | Description |
|---|---|---|
|
|
Number of files (primary and secondary files). |
|
|
Total file size in bytes. |
|
|
File extension (e.g. |
|
|
MIME type (e.g. |
|
|
MIME group (e.g. |
|
|
ID of the file type icon. |
|
|
Number of pages, if known. |
doc = ecm.dms.get(InvoiceDocument, 42, file_properties=True)
fp = doc.system.file_properties
print(f"{fp.extension}, {fp.size} bytes, {fp.documentpagecount} pages")
4.3.4. system.variants
Populated when variants=True (get()) or .variants() (select()) is passed. Only meaningful for ECMDocumentModel with the W-module enabled. Type: list[ECMModelDocumentVariant].
Each entry in the list has:
| Attribute | Type | Description |
|---|---|---|
|
|
Document ID of this variant. |
|
|
Version label (e.g. |
|
|
|
|
|
ID of the parent variant, or |
|
|
IDs of child variants branched from this one. |
doc = ecm.dms.get(InvoiceDocument, 42, variants=True)
for v in doc.system.variants:
print(v.doc_ver, "✓" if v.is_active else "")
5. Change tracking
Model instances track field changes automatically. Assigning a new value to an ECMField marks that field as modified:
folder = ecm.dms.get(InvoiceFolder, 12345)
folder.Title = "Updated Title"
print(folder.system.is_modified) # True
print(folder.system.modified_fields) # {"Title"}
print(folder.system.is_field_modified("Year")) # False
update() reads system.is_modified to decide whether to skip the server call. When no fields have changed and no files are provided, the call is omitted silently (unless force=True).
Removing a row from a ECMTableField sets system.has_removed_table_rows = True, which causes update() to add REPLACETABLEFIELDS=1 to the request.
6. See also
-
select() — query builder with
.rights(),.base_params(),.file_properties(),.variants(),.remarks() -
get() — load a single object by ID with optional flag parameters
-
insert() / insert_and_get() — create new objects
-
update() / update_and_get() — write changes back to the server