ECM-Modell

Das ECM-Modell ist das zentrale Konzept von ecmind-blue-client. Es bietet eine typisierte, ORM-artige Schnittstelle für ECM-Objekte — Ordner, Register und Dokumente.

Jedes Objekt, das von ecm.dms.select(), ecm.dms.get(), ecm.dms.insert_and_get() oder ecm.dms.update_and_get() zurückgegeben wird, ist eine Instanz einer Modellklasse. Modellinstanzen enthalten sowohl Indexfelddaten (benutzerdefinierte Felder) als auch Systemmetadaten (immer über obj.system).

1. Modellklassen

Eine Modellklasse wird durch Ableitung von einer der drei Basisklassen definiert. Die Felder werden als typisierte Klassenannotationen deklariert:

from ecmind_blue_client.ecm.model import ECMFolderModel, ECMRegisterModel, ECMDocumentModel, ECMField, ECMTableField, ECMTableRowModel

class RechnungsZeile(ECMTableRowModel):
    Betrag: ECMField[float]
    Beschreibung: ECMField[str]

class RechnungsOrdner(ECMFolderModel):
    _internal_name_ = "InvoiceFolder"   # interner Name des Objekttyps auf dem Server
    Titel: ECMField[str]
    Jahr: ECMField[int]
    Positionen: ECMTableField[RechnungsZeile]

class RechnungsRegister(ECMRegisterModel):
    _internal_name_ = "InvoiceRegister"
    Name: ECMField[str]

class RechnungsDokument(ECMDocumentModel):
    _internal_name_ = "InvoiceDocument"
    Betreff: ECMField[str]
    Betrag: ECMField[float]
Basisklasse Verwendung

ECMFolderModel

Ordner-Objekttypen

ECMRegisterModel

Register-Objekttypen (Unterordner)

ECMDocumentModel

Dokument-Objekttypen

1.1. Dynamische Modelle

Wenn der Objekttyp erst zur Laufzeit bekannt ist, können statt einer Klassendefinition Factory-Funktionen verwendet werden:

from ecmind_blue_client.ecm.model import make_folder_model, make_register_model, make_document_model

RechnungsOrdner = make_folder_model("InvoiceFolder")

Dynamische Modelle unterstützen dieselbe Query-API. Nicht deklarierte Felder werden über obj["internalName"] abgerufen.

2. ECMField

ECMField[T] ist der Descriptor für typisierte Indexfelder. Der Typparameter T legt den Python-Typ des Feldwertes fest (str, int, float, datetime, date, time, bool).

Auf Klassenebene wirkt ECMField als Condition-Builder:

RechnungsOrdner.Jahr == 2024           # ECMCondition
RechnungsOrdner.Jahr >= 2020           # ECMCondition
RechnungsOrdner.Titel.in_("A", "B")   # ECMCondition
RechnungsOrdner.Jahr.DESC              # ECMSortOrder für order_by()

Auf Instanzebene gibt er den gespeicherten Wert zurück:

ordner = ecm.dms.get(RechnungsOrdner, 12345)
print(ordner.Jahr)   # int | None

2.1. Pflichtfelder

Felder können als Pflichtfeld deklariert werden. insert() und update() prüfen diese standardmäßig clientseitig:

class RechnungsOrdner(ECMFolderModel):
    _internal_name_ = "InvoiceFolder"
    Titel: ECMField[str] = ECMField(mandatory=True)
    Jahr: ECMField[int]

3. ECMTableField

ECMTableField[RowT] enthält mehrreihige Tabellenfelder. Die Zeilenklasse muss ECMTableRowModel ableiten und ihre Spalten als ECMField-Annotationen deklarieren.

for zeile in ordner.Positionen:
    print(zeile.Betrag, zeile.Beschreibung)

Jede Zeile stellt folgende Attribute bereit:

Attribut Beschreibung

row_id

Interne Zeilenkennung, die vom Server vergeben wird.

is_modified

True, wenn seit dem Laden ein Feld dieser Zeile geändert wurde.

is_field_modified(field_name)

Gibt True zurück, wenn das genannte Feld geändert wurde.

4. system-Eigenschaften

Jede Modellinstanz besitzt ein system-Attribut, das alle serverseitig befüllten Metadaten enthält. Indexfelder (eigene ECMField-Deklarationen) und Systemeigenschaften sind strikt getrennt.

4.1. Immer verfügbar

Die folgenden Eigenschaften sind immer gefüllt, unabhängig von den Flags des Abfrage- oder Abrufaufrufs:

Eigenschaft Typ Beschreibung

system.id

int | None

Numerische ID des Objekts auf dem Server. None für Instanzen, die noch nicht gespeichert wurden (z. B. vor insert()).

system.name

str | None

Anzeigename des Objekts, wie er auf dem Server gespeichert ist. Entspricht in der Regel dem Wert des Schlüsselfeldes.

system.is_modified

bool

True, wenn seit dem Laden ein Indexfeld geändert wurde. Wird intern von update() verwendet, um unnötige Serveraufrufe zu überspringen.

system.has_removed_table_rows

bool

True, wenn seit dem Laden mindestens eine Tabellenzeile aus einem ECMTableField entfernt wurde. Löst REPLACETABLEFIELDS=1 in update() aus.

system.modified_fields

set[str]

Menge der internen Feldnamen, die seit dem Laden geändert wurden.

system.modified_table_fields

set[str]

Menge der Tabellenfeldnamen, bei denen Zeilen hinzugefügt, entfernt oder geändert wurden.

system.is_field_modified(field_name)

bool

Gibt True zurück, wenn das genannte Feld seit dem Laden geändert wurde.

4.2. Je nach Objekttyp verfügbar

Einige Systemeigenschaften sind nur bei bestimmten Modelltypen vorhanden:

Eigenschaft Typ Verfügbar bei Beschreibung

system.folder_id

int | None

ECMRegisterModel, ECMDocumentModel

ID des übergeordneten Ordners. Wird nur befüllt, wenn use_result_list=True an get() übergeben wird oder das Objekt über select() zurückgegeben wird.

system.parent_register_id

int | None

ECMRegisterModel

ID des direkt übergeordneten Registers, wenn das Register in einem anderen Register verschachtelt ist.

system.register_id

int | None

ECMDocumentModel

ID des übergeordneten Registers. Wird nur befüllt, wenn use_result_list=True an get() übergeben wird oder das Objekt über select() zurückgegeben wird.

system.register_type_id

int | None

ECMDocumentModel

Typ-ID des übergeordneten Registers.

4.3. Optional — auf Anfrage geladen

Die folgenden Eigenschaften sind standardmäßig None und müssen über Flags bei select(), get(), insert_and_get() oder update_and_get() explizit angefordert werden.

4.3.1. system.rights

Befüllt bei rights=True (get()) oder .rights() (select()). Typ: ECMModelRights.

Attribut Typ Beschreibung

insert

bool

Darf untergeordnete Objekte anlegen (Register oder Dokumente in einem Ordner; Dokumente in einem Register).

edit_metadata

bool

Darf die Indexfelder des Objekts ändern.

read_file

bool

Darf die Datei des Dokuments lesen.

edit_file

bool

Darf die Datei des Dokuments ändern.

delete

bool

Darf das Objekt löschen.

ordner = ecm.dms.get(RechnungsOrdner, 12345, rights=True)
if ordner.system.rights.edit_metadata:
    print("Bearbeitung erlaubt")

4.3.2. system.base_params

Befüllt bei base_params=True (get()) oder .base_params() (select()). Typ: ECMModelBaseParams.

Attribut Typ Beschreibung

creator

str | None

Benutzername des Erstellers.

creation_date

datetime | None

Erstellungszeitpunkt.

owner

str | None

Aktueller Eigentümer des Objekts.

modifier

str | None

Benutzername des letzten Bearbeiters.

modified_date

datetime | None

Zeitstempel der letzten Änderung.

links_count

int | None

Anzahl der Verknüpfungen auf dieses Objekt.

text_notice_count

int | None

Anzahl der Textnotizen (Bemerkungen) am Objekt.

ordner = ecm.dms.get(RechnungsOrdner, 12345, base_params=True)
print(f"Erstellt von {ordner.system.base_params.creator} am {ordner.system.base_params.creation_date}")

4.3.3. system.file_properties

Befüllt bei file_properties=True (get()) oder .file_properties() (select()). Nur für ECMDocumentModel relevant. Typ: ECMModelFileProperties.

Attribut Typ Beschreibung

count

int | None

Anzahl der Dateien (Primär- und Sekundärdateien).

size

int | None

Gesamtdateigröße in Bytes.

extension

str | None

Dateiendung (z. B. pdf, docx).

mimetype

str | None

MIME-Typ (z. B. application/pdf).

mimetypegroup

str | None

MIME-Gruppe (z. B. application).

iconid

int | None

ID des Dateitypicons.

documentpagecount

int | None

Seitenanzahl, sofern bekannt.

dok = ecm.dms.get(RechnungsDokument, 42, file_properties=True)
fp = dok.system.file_properties
print(f"{fp.extension}, {fp.size} Bytes, {fp.documentpagecount} Seiten")

4.3.4. system.variants

Befüllt bei variants=True (get()) oder .variants() (select()). Nur für ECMDocumentModel mit aktiviertem W-Modul relevant. Typ: list[ECMModelDocumentVariant].

Jeder Eintrag enthält:

Attribut Typ Beschreibung

doc_id

int

Dokument-ID dieser Variante.

doc_ver

str

Versionsbezeichnung (z. B. "1.0", "1.1").

is_active

bool

True für die aktuell aktive Variante.

doc_parent

int | None

ID der übergeordneten Variante, oder None für die Wurzel.

children

list[int]

IDs der aus dieser Variante abgezweigten Kindvarianten.

dok = ecm.dms.get(RechnungsDokument, 42, variants=True)
for v in dok.system.variants:
    print(v.doc_ver, "✓" if v.is_active else "")

4.3.5. system.remarks

Befüllt bei remarks=True (get()) oder .remarks() (select()). Typ: list[str]. Enthält die Textnotizen des Objekts.

for ordner in ecm.dms.select(RechnungsOrdner).remarks().stream():
    for bemerkung in ordner.system.remarks:
        print(bemerkung)

5. Änderungsverfolgung

Modellinstanzen verfolgen Feldänderungen automatisch. Das Zuweisen eines neuen Wertes an ein ECMField markiert das Feld als geändert:

ordner = ecm.dms.get(RechnungsOrdner, 12345)
ordner.Titel = "Aktualisierter Titel"

print(ordner.system.is_modified)               # True
print(ordner.system.modified_fields)           # {"Titel"}
print(ordner.system.is_field_modified("Jahr")) # False

update() liest system.is_modified, um zu entscheiden, ob der Serveraufruf übersprungen werden kann. Wurden keine Felder geändert und werden keine Dateien übergeben, wird der Aufruf stillschweigend ausgelassen (außer bei force=True).

Das Entfernen einer Zeile aus einem ECMTableField setzt system.has_removed_table_rows = True, wodurch update() REPLACETABLEFIELDS=1 zur Anfrage hinzufügt.

6. Siehe auch