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

ECMFolderModel

Folder object types

ECMRegisterModel

Register (sub-folder) object types

ECMDocumentModel

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

2.1. Mandatory fields

Fields can be declared as mandatory. insert() and update() validate them client-side by default:

class InvoiceFolder(ECMFolderModel):
    _internal_name_ = "InvoiceFolder"
    Title: ECMField[str] = ECMField(mandatory=True)
    Year: ECMField[int]

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

row_id

Internal row identifier assigned by the server.

is_modified

True if any field in this row has been changed since loading.

is_field_modified(field_name)

Returns True if the named field has been changed.

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

system.id

int | None

Numeric ID of the object on the server. None for instances that have not yet been written to the server (e.g. before insert()).

system.name

str | None

Display name of the object as stored on the server. Usually the value of the key field.

system.is_modified

bool

True if any index field has been changed since the instance was loaded. Used internally by update() to skip unnecessary server calls.

system.has_removed_table_rows

bool

True if at least one table row has been removed from a ECMTableField since loading. Triggers REPLACETABLEFIELDS=1 in update().

system.modified_fields

set[str]

Set of internal field names that have been modified since loading.

system.modified_table_fields

set[str]

Set of table field names that have been modified (row added, removed, or changed) since loading.

system.is_field_modified(field_name)

bool

Returns True if the named field has been changed since loading.

4.2. Available per object type

Some system properties are only present on specific model types:

Property Type Available on Description

system.folder_id

int | None

ECMRegisterModel, ECMDocumentModel

ID of the parent folder. Only populated when use_result_list=True is passed to get(), or when returned by select().

system.parent_register_id

int | None

ECMRegisterModel

ID of the directly enclosing register, if the register is nested inside another register.

system.register_id

int | None

ECMDocumentModel

ID of the parent register. Only populated when use_result_list=True is passed to get(), or when returned by select().

system.register_type_id

int | None

ECMDocumentModel

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

Populated when rights=True (get()) or .rights() (select()) is passed. Type: ECMModelRights.

Attribute Type Description

insert

bool

May create child objects (registers or documents in a folder; documents in a register).

edit_metadata

bool

May modify the index fields of the object.

read_file

bool

May read the file of the document.

edit_file

bool

May modify the file of the document.

delete

bool

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

creator

str | None

Username of the user who created the object.

creation_date

datetime | None

Timestamp of object creation.

owner

str | None

Current owner of the object.

modifier

str | None

Username of the user who last modified the object.

modified_date

datetime | None

Timestamp of the last modification.

links_count

int | None

Number of links to this object.

text_notice_count

int | None

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

count

int | None

Number of files (primary and secondary files).

size

int | None

Total file size in bytes.

extension

str | None

File extension (e.g. pdf, docx).

mimetype

str | None

MIME type (e.g. application/pdf).

mimetypegroup

str | None

MIME group (e.g. application).

iconid

int | None

ID of the file type icon.

documentpagecount

int | None

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

doc_id

int

Document ID of this variant.

doc_ver

str

Version label (e.g. "1.0", "1.1").

is_active

bool

True for the currently active variant.

doc_parent

int | None

ID of the parent variant, or None for the root.

children

list[int]

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 "")

4.3.5. system.remarks

Populated when remarks=True (get()) or .remarks() (select()) is passed. Type: list[str]. Contains the text notices attached to the object.

for folder in ecm.dms.select(InvoiceFolder).remarks().stream():
    for remark in folder.system.remarks:
        print(remark)

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