Benutzerdefinierte Felder

Das Bearbeiten der Schemas für Benutzerdefinierte Felder und Serialisieren/Deserialisieren auf Inhalten verwendet die folgenden Begriffe:

  • Property Sheet: Ein Property Sheet definiert ein Schema. Dieses Schema
    wird zur Validierung der Custom Properties verwendet.
  • Assignment Slot: Ein Assignment Slot definiert einen Steckplatz dem
    maximal ein Property Sheet zugewiesen werden kann.
  • Assignment: Ein Assignment ist die Zuweisung eines Property Sheets an
    einen Assignment Slot. Ein Property Sheet kann mehrere Assignments haben.
  • Custom Properties: Custom Properties sind die konkreten Daten auf den
    Inhaltsobjekten. Custom Properties werden gegen ihr zugehöriges Property Sheet validiert.

Mittels Property Sheets ist es möglich benutzerdefinierte Schemata mit einem oder mehreren Feldern zu definieren damit zusätzliche Properties in GEVER strukturiert erfasst werden können.

Das Erstellen, Bearbeiten oder Löschen von Property Sheets über die API benötigt die Rolle PropertySheetsManager. Das Auflisten / Anzeigen (GET) ist Benutzern mit Leseberechtigung erlaubt.

Zur Verwaltung von Property Sheets steht der Service-Endpoint @propertysheets zur Verfügung. Folgende Aufrufe sind möglich:

Eine Liste aller bestehender Property Sheets:

GET /@propertysheets HTTP/1.1

Auslesen der Definition eines Property Sheets:

GET /@propertysheets/<property_sheet_name> HTTP/1.1

Hinzufügen eines neuen Property Sheets:

POST /@propertysheets/<property_sheet_name> HTTP/1.1

Mutieren eines bestehenden Property Sheets:

PATCH /@propertysheets/<property_sheet_name> HTTP/1.1

Löschen eines bestehenden Property Sheets:

DELETE /@propertysheets/<property_sheet_name> HTTP/1.1

Neue Property Sheets erstellen

Neue Property Sheets können mittels POST Request hinzugefügt werden. Ein Sheet kann immer nur als gesamte Einheit gespeichert werden. Existiert schon ein Sheet mit dem verwendeten Namen, so wird dieses überschrieben.

Zum Erstellen eines Property Sheets muss man die gewünschten Felder und Assignments angeben.

Der JSON-Attributname des einzelnen Feldes wird als Feld-identifier verwendet. Einzelne Felder werden in folgendem Format erwartet:

  • field_type: Der Feldtyp, folgende Typen sind unterstützt:
    • bool
    • choice
    • date
    • multiple_choice
    • int
    • text
    • textline
  • title: Der Titel des Feldes
  • description: Die Beschreibung des Feldes
  • required: Ob das Feld erforderlich ist
  • values: Auswahlmöglichkeiten für das Feld, nur für choice Feldtyp
  • default: Ein statischer Default-Wert

Es existieren noch weitere Möglichkeiten, Defaults anzugeben. Details dazu sind im Abschnitt Default-Werte beschrieben.

Die für das Assignment verwendeten Assignment-Slots müssen aus dem Vokabular opengever.propertysheets.PropertySheetAssignmentsVocabulary stammen. Ein Spezialfall ist dabei der Default-Slot IDocument.default bzw. IDossier.default, welcher unabhängig vom Dokument- oder Dossiertyp Feld immer dargestellt wird.

Zudem müssen Assignments eindeutig sein, mehrere Property Sheets dem gleichen Assignment-Slot zuzuweisen ist im Moment nicht unterstützt.

Beispiel-Request:

POST http://localhost:8080/fd/@propertysheets/question HTTP/1.1
Accept: application/json

{
  "fields": [
    {
      "name": "yesorno",
      "field_type": "bool",
      "title": "Y/N",
      "description": "yes or no",
      "required": true
    }
  ],
  "assignments": ["IDocumentMetadata.document_type.question"]
}

Beispiel-Response:

HTTP/1.1 201 Created
Content-Type: application/json+schema
Location: /@propertysheets/question

{
    "assignments": [
        "IDocumentMetadata.document_type.question"
    ],
    "fields": [
        {
            "description": "yes or no",
            "field_type": "bool",
            "name": "yesorno",
            "required": true,
            "title": "Y/N"
        }
    ],
    "id": "question"
}

Default-Werte

Feld-Definitionen für alle Typen unterstützen folgende Optionen, um Default-Werte bestimmen zu können. Diese Optionen schliessen sich gegenseitig aus, es kann immer nur eine dieser Optionen angegeben werden

  • default: Ein statischer Default-Wert
  • default_factory: Bestimmen des Defaults mittels einer default factory Funktion
  • default_expression: Bestimmen des Defaults mittels einer TALES expression
  • default_from_member: Bestimmen des Defaults mittels eines Properties auf dem Member / User

Optionen für dynamische Default-Werte (alle Optionen ausser default) können aus Sicherheitsgründen nur von Benutzern mit der Rolle Manager gesetzt werden - die Rolle PropertySheetsManager reicht nicht.

default

Diese Option erwartet einen statischen Wert, welcher als default für das Feld verwendet wird. Der Typ des Werts muss dem Feld-Typ entsprechen.

Beispiel:

{
  "name": "language",
  "title": "Language",
  "field_type": "text",
  "default": "en"
}

default_factory

Diese Option aktzeptiert einen String, der einen dottedname zu einer default factory enthält (eine Python Funktion, die dynamisch einen Default-Wert zurückgibt).

Beispiel:

{
  "name": "language",
  "title": "Language",
  "field_type": "text",
  "default_factory": "opengever.document.example.language_default_factory"
}

default_expression

Diese Option aktzeptiert einen String, der eine gültige TALES Expression enthält, welche dynamisch ausgewertet wird um einen Default-Wert zu bestimmen.

Der ExpressionContext in dem die Expression ausgewertet wird, enthält die üblichen Namen. Allerdings sind aufgrund einer Limitierung zur Zeit der aktuelle Kontext und der enthaltende Folder nicht verfügbar. here und object sind daher None, und der folder ist auf das Portal gesetzt.

Beispiel:

{
  "name": "userid",
  "title": "User ID",
  "field_type": "text",
  "default_expression": "member/getId"
}

default_from_member

Diese Option aktzeptiert ein JSON Objekt mit mindestens einem key property das definiert, von welchem Property auf dem eingeloggten Member (~= User) der Default-Wert bestimmt werden soll. Wenn LDAP-Properties via dem LDAPUserFolder Schema entsprechend gemappt sind, können auch diese als Default-Werte verwendet werden.

Optional unterstützt default_from_member auch die Angabe eines Mappings, und eines Fallback-Wertes der Verwendet wird wenn das Property nicht gefunden werden kann, oder einen Wert zurückgibt der Falsy ist.

Wenn ein Mapping verwendet wird, kann über den Parameter allow_unmapped gesteuert werden, ob Rückgabewerte erlaubt sind, die nicht im Mapping vorkommen:

  • allow_unmapped = False (default): Werte, die nicht im Mapping vorkommen, sind nicht erlaubt. Für solche Werte wird stattdessen das fallback verwendet.
  • allow_unmapped = True: Werte, die nicht im Mapping vorkommen, werden 1:1 als default zurückgegeben.

Beispiel:

{
  "name": "userid",
  "title": "User ID",
  "field_type": "text",
  "default_from_member": {
    "property": "username",
    "fallback": "<No username found>",
    "mapping": {
      "p.mueller": "peter.mueller",
      "h.meier": "hans.meier"
    }
  }
}

Existierende Property Sheets mutieren (PATCH)

Existierende Property Sheets können über einen PATCH request mutiert werden. Die PATCH-Semantik besagt grundsätzlich, dass Feldwerte, welche im Request nicht mitgeschickt werden, so belassen werden wie sie sind. Für Propertysheets gilt dies für die äusserste Ebene, nicht aber für verschachtelte Ebenen.

Das heisst, wenn entweder der assignments Key oder der fields Key weggelassen werden, behalten diese den vorherigen Wert. Wird aber ein fields Key mitgeschickt, und enthält weniger Felder als zuvor, werden diese fehlenden Felder gelöscht.

Beim Aktualisieren von einzelnen Feldern muss vom Client daher immer die komplette Feld-Liste, wie sie neu aussehen soll, mitgeschickt werden.

Es ist dementsprechend auch nicht möglich, ein Feld umzubenennen. Das Feld kann aber entfernt , und unter einem anderen Namen hinzugefügt werden. Dies führt aber dazu, dass Daten, welche auf Dossiers oder Dokumenten unter dem alten Feldnamen bereits erfasst wurden, verloren gehen und nicht dem neuen Feld zugeordnet sind.

Beispiel für einen PATCH-Request:

Beispiel-Request:

PATCH http://localhost:8080/fd/@propertysheets/question HTTP/1.1
Accept: application/json
Content-Type: application/json

{
  "assignments": ["IDocument.default"]
}

(Ändert die Assignments auf [“IDocument.default”]. Die Felder werden so belassen wie zuvor.)

Beispiel-Response:

HTTP/1.1 200 OK
Content-Type: application/json

{
    "assignments": [
        "IDocument.default"
    ],
    "fields": [
        {
            "description": "yes or no",
            "field_type": "bool",
            "name": "yesorno",
            "required": true,
            "title": "Y/N"
        }
    ],
    "id": "question"
}

(Die Response auf PATCH Requests enthält die komplette, neue Definition des Propertysheets.)

Das Ändern von dynamischen Defaults ist nur für Benutzer mit der Manager-Rolle erlaubt. Wenn jedoch ein dynamischer default für ein Feld bereits existiert, dann kann dieser in einem PATCH request auch von einem Benutzer mit der Rolle PropertySheetsManager mitgeschickt werden (um ihn zu erhalten), sofern der dynamische Default nicht geändert wird.

Serialisierung/Deserialisierung von Custom Properties

Im Moment sind Custom Properties auf Dokumenten, Mails und Dossiers unterstützt. Die Auswahl des zu validierenden Property Sheets basiert auf dem Wert des Feldes document_type bzw. dossier_type. Ausnahme ist dabei der Default-Slot IDocument.default bzw. IDossier.default welcher unabhängig des Typen Feldwertes immer dargestellt wird. Ist für den Assignment-Slot IDocumentMetadata.document_type.<document_type_value> ein Property Sheet registriert, so werden Feldwerte dieses Property Sheets validiert. Hat das Property Sheet also obligatorische Felder, so müssen die Custom Properties zwingend Daten für dieses Property Sheet beinhalten. Serialisierung und Deserialisierung der Custom Properties basiert auf folgendem Format:

{
    "custom_properties": {
        "<assignment_slot_name>": {
            "<property_sheet_field_name>": "<field value>"
    }
}

Es werden immer alle einmal gespeicherten Custom Properties serialisiert und ausgegeben, unabhängig vom Wert des Feldes document_type.

GET /ordnungssystem/dossier-23/document-123 HTTP/1.1
Accept: application/json
HTTP/1.1 200 OK
Content-Type: application/json

{
    "@id": "/ordnungssystem/dossier-23/document-123",
    "custom_properties": {
        "IDocumentMetadata.document_type.question": {
            "yesorno": false
        },
        "IDocumentMetadata.document_type.protocol": {
            "location": "Dammweg 9",
            "responsible": "Hans Muster",
            "protocol_type": {
                "title": "Kurzprotokoll",
                "token": "Kurzprotokoll"
            }
        }
    },
    "...": "..."
}

Beim Speichern der Custom Properties können Properties für alle erlaubten Assigmnet-Slots angegeben werden. Es werden immer alle angegebenen Custom Properties validiert. Das Speichern erfolg kumulativ, wenn man ein Subset der möglichen Assignment-Slots verwendet, werden die Custom Propterties anderer Slots nicht überschrieben.

PATCH /ordnungssystem/dossier-23/document-123 HTTP/1.1
Accept: application/json

{
    "custom_properties": {
        "IDocumentMetadata.document_type.protocol": {
            "location": "Dammweg 9",
            "responsible": "Hans Muster",
            "protocol_type": {
                "title": "Kurzprotokoll",
                "token": "Kurzprotokoll"
            }
        }
    }
}
HTTP/1.1 204 No content
Content-Type: application/json

Schemas für Propertysheets

JSON Schemas für existierende Propertysheets können über den @schema Endpoint abgerufen werden. Dazu wird ein GET Request auf @schema/virtual.propertysheet.<sheet_id> ausgeführt, wobei sheet_id die ID / der Name des entsprechenden Sheets ist.

Beispiel (für ein Sheet mit der ID question)

GET /@schema/virtual.propertysheet.question HTTP/1.1
Accept: application/json
HTTP/1.1 200 OK
Content-Type: application/json+schema

{
    "assignments": ["IDocumentMetadata.document_type.question"],
    "fieldsets": [
        {
            "behavior": "plone",
            "fields": ["yesorno"],
            "id": "default",
            "title": "Default"
        }
    ],
    "properties": {
        "yesorno": {
            "description": "yes or no",
            "factory": "Yes/No",
            "title": "Y/N",
            "type": "boolean"
        }
    },
    "required": ["yesorno"],
    "title": "question",
    "type": "object"
}

Schema für Propertysheet-Definitionen

Das JSON Schema für eine Propertysheet-Definition kann über den @propertysheet-metaschema Endpoint abgerufen werden:

GET /@propertysheet-metaschema HTTP/1.1
Accept: application/json
HTTP/1.1 200 OK
Content-Type: application/json+schema

{
    "$schema": "http://json-schema.org/draft-04/schema#",
    "type": "object",
    "title": "Propertysheet Meta Schema",
    "additionalProperties": false,
    "properties": {
        "id": {
            "type": "string",
            "title": "ID",
            "maxLength": 32,
            "description": "ID dieses Property Sheets",
            "additionalProperties": false,
            "pattern": "^[a-z_0-9]*$"
        },
        "fields": {
            "type": "array",
            "title": "Felder",
            "description": "Felder",
            "additionalProperties": false,
            "items": {
                "required": [
                    "name",
                    "field_type"
                ],
                "type": "object",
                "properties": {
                    "name": {
                        "pattern": "^[a-z_0-9]*$",
                        "maxLength": 32,
                        "type": "string",
                        "description": "Name (Alphanumerisch, nur Kleinbuchstaben)",
                        "title": "Name"
                    },
                    "field_type": {
                        "description": "Datentyp für dieses Feld",
                        "title": "Feld-Typ",
                        "enum": [
                            "int",
                            "multiple_choice",
                            "choice",
                            "bool",
                            "text",
                            "date",
                            "textline"
                        ],
                        "choices": [
                            [
                                "int",
                                "Integer"
                            ],
                            [
                                "multiple_choice",
                                "Multiple Choice"
                            ],
                            [
                                "choice",
                                "Choice"
                            ],
                            [
                                "bool",
                                "Yes/No"
                            ],
                            [
                                "text",
                                "Text"
                            ],
                            [
                                "date",
                                "Date"
                            ],
                            [
                                "textline",
                                "Text line (String)"
                            ]
                        ],
                        "enumNames": [
                            "Integer",
                            "Multiple Choice",
                            "Choice",
                            "Yes/No",
                            "Text",
                            "Date",
                            "Text line (String)"
                        ],
                        "type": "string"
                    },
                    "title": {
                        "title": "Titel",
                        "type": "string",
                        "description": "Titel",
                        "maxLength": 48
                    },
                    "description": {
                        "title": "Beschreibung",
                        "type": "string",
                        "description": "Beschreibung",
                        "maxLength": 128
                    },
                    "required": {
                        "type": "boolean",
                        "description": "Angabe, ob Benutzer dieses Feld zwingend ausfüllen müssen",
                        "title": "Pflichtfeld"
                    },
                    "default": {
                        "type": [
                            "integer",
                            "array",
                            "boolean",
                            "string"
                        ],
                        "description": "Default-Wert für dieses Feld",
                        "title": "Default"
                    },
                    "values": {
                        "uniqueItems": false,
                        "items": {
                            "title": "",
                            "type": "string",
                            "factory": "Text line (String)",
                            "description": ""
                        },
                        "type": "array",
                        "description": "Liste der erlaubten Werte für das Feld",
                        "title": "Wertebereich"
                    }
                }
            },
            "uniqueItems": false
        },
        "assignments": {
            "type": "array",
            "title": "Slots",
            "description": "Für welche Arten von Inhalten dieses Property Sheet verfügbar sein soll",
            "additionalProperties": false,
            "items": {
                "type": "string",
                "enum": [
                    "IDocument.default",
                    "IDocumentMetadata.document_type.question",
                    "IDocumentMetadata.document_type.request",
                    "IDocumentMetadata.document_type.report",
                    "IDocumentMetadata.document_type.offer",
                    "IDocumentMetadata.document_type.protocol",
                    "IDocumentMetadata.document_type.regulations",
                    "IDocumentMetadata.document_type.contract",
                    "IDocumentMetadata.document_type.directive",
                    "IDossier.default",
                    "IDossier.dossier_type.businesscase"
                ],
                "enumNames": [
                    "Dokument",
                    "Dokument (Typ: Anfrage)",
                    "Dokument (Typ: Antrag)",
                    "Dokument (Typ: Bericht)",
                    "Dokument (Typ: Offerte)",
                    "Dokument (Typ: Protokoll)",
                    "Dokument (Typ: Reglement)",
                    "Dokument (Typ: Vertrag)",
                    "Dokument (Typ: Weisung)",
                    "Dossier",
                    "Dossier (Typ: Geschäftsfall)"
                ],
                "choices": [
                    [
                        "IDocument.default",
                        "Dokument"
                    ],
                    [
                        "IDocumentMetadata.document_type.question",
                        "Dokument (Typ: Anfrage)"
                    ],
                    [
                        "IDocumentMetadata.document_type.request",
                        "Dokument (Typ: Antrag)"
                    ],
                    [
                        "IDocumentMetadata.document_type.report",
                        "Dokument (Typ: Bericht)"
                    ],
                    [
                        "IDocumentMetadata.document_type.offer",
                        "Dokument (Typ: Offerte)"
                    ],
                    [
                        "IDocumentMetadata.document_type.protocol",
                        "Dokument (Typ: Protokoll)"
                    ],
                    [
                        "IDocumentMetadata.document_type.regulations",
                        "Dokument (Typ: Reglement)"
                    ],
                    [
                        "IDocumentMetadata.document_type.contract",
                        "Dokument (Typ: Vertrag)"
                    ],
                    [
                        "IDocumentMetadata.document_type.directive",
                        "Dokument (Typ: Weisung)"
                    ],
                    [
                        "IDossier.default",
                        "Dossier"
                    ],
                    [
                        "IDossier.dossier_type.businesscase",
                        "Dossier (Typ: Geschäftsfall)"
                    ]
                ]
            },
            "uniqueItems": true
        }
    },
    "required": [
        "fields"
    ],
    "field_order": [
        "id",
        "fields",
        "assignments"
    ]
}