Coverage for gws-app/gws/base/model/__init__.py: 100%
3 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-05 13:35 +0200
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-05 13:35 +0200
1"""Data models.
3A data model, or simply model, is an object that deals with features from
4external sources, like database tables, shape files, GML responses, etc. The
5job of a model is to read features from the source and convert them to a form
6suitable for the client. An editable model can also accept features back from
7the client, parse and validate them, and store them in the source.
9This package provides the base classes for models and their components. Concrete
10models (e.g. ``postgres``, ``qgis``, ``geojson``) live in ``gws.base.database`` and
11in plugins, concrete fields, values, validators and widgets live in the
12``gws.plugin.model_field``, ``gws.plugin.model_value``, ``gws.plugin.model_validator``
13and ``gws.plugin.model_widget`` packages.
15Submodules
16----------
18- ``core`` - the base model class (`Object`) with the model configuration protocol,
19 props and the conversion between features and props.
20- ``default_model`` - the ``default`` model, used when no model is configured. It copies
21 attributes between records, features and props as they are.
22- ``manager`` - the model manager (``root.app.modelMgr``), which looks up models
23 by uid or by the objects they belong to, and lists editable models. Editable models
24 are collected from the project layers, the project and the application, together with
25 their related models the user can read.
26- ``field`` - the base model field class, with flags, values, validators, widget and validation.
27- ``scalar_field`` - the base class for fields that map to one source attribute (column).
28- ``related_field`` - the base class for fields that link features of other models,
29 with the relationship description and database helpers.
30- ``value`` - the base class for value objects.
31- ``validator`` - the base class for validator objects.
32- ``widget`` - the base class for client widgets.
33- ``util`` - helpers to create model contexts and to iterate over related features.
35Features
36--------
38A feature is a collection of named attributes. One of these attributes can
39act as a unique ID (``uid``), and another one can be the feature
40geometry (``shape``). ``uid`` is required for editable models, ``shape`` is always optional.
42There are three kinds of objects that represent features:
44The feature object (`gws.Feature`) is the internal representation of a
45feature. It provides storage for attributes and convenience methods to extract
46or mutate them. A feature also contains a dict of ``views``, which are chunks of HTML,
47rendered by templates and used to represent a feature in the client.
49A record object (`gws.FeatureRecord`) is a data object that only contains a
50dict of attributes and, optionally, some metadata properties, depending on the
51source. For example, GML feature records usually contain the layer name. It
52represents raw source data.
54A props object (`gws.FeatureProps`) contains the data necessary to display a feature
55in the client. When viewing features, the client only needs their ``uid``, ``shape`` and
56``views``. In the edit context, the props object also contains a dict of attributes.
57View props (``feature_to_view_props``) only keep the uid and geometry attributes, under
58the names ``uid`` and ``geometry``.
60Operations
61----------
63Models are used to perform several abstract operations (`gws.ModelOperation`):
65- ``read`` - the client provides a search query (`gws.SearchQuery`) and expects a list of matching props
66- ``create`` - the client sends feature props and wants to create new features in the source
67- ``update`` - the client sends feature props and wants to update existing features
68- ``delete`` - the client sends feature props and wants respective features to be deleted
70Before ``create``, the client can also request a new empty feature to be initialized
71(``init_feature``) and sent back.
73Fields
74------
76Most models contain a list of field objects (`gws.ModelField`). A field
77deals with a subset of feature data, converts it between representations
78and validates it.
80When a model performs an operation, it delegates it to all its fields in turn.
82Fields are either configured explicitly (``fields``), or created automatically from the
83source columns (``withAutoFields``, or when no fields are configured), see
84`core.Object.configure_auto_fields`.
86There are two kinds of fields: scalar fields represent one attribute (column)
87in the source itself, and related fields represent features from other models,
88linked to the current model.
90Values
91------
93A field can have value objects (`gws.ModelValue`) attached to it.
94Value objects provide a ``compute`` method. When a model performs an operation,
95and a field has a value object configured for this operation, its ``compute`` method
96is called, and the returned value is used as the field value.
98Validators
99----------
101A field can also have validator objects (`gws.ModelValidator`) attached. On
102``create`` and ``update``, ``validate_feature`` runs the validators of all fields
103and collects a `gws.ModelValidationError` for each field that fails. Each field always has
104a ``notEmpty`` and a ``format`` validator: an empty value is an error only for required fields,
105and further validators are not run for an empty value. The ``notEmpty`` validator runs first,
106then ``format``, then the other validators configured for the operation; validation of a field
107stops at the first failure. The error message of a validator defaults to ``validationError_<type>``.
109Widgets
110-------
112A field can have a widget object (`gws.ModelWidget`), which describes how the client
113displays and edits the field value. Without a configured widget, related fields of type
114``feature`` get a ``featureSelect`` widget, and fields of type ``featurelist`` get a
115``featureList`` widget.
117Permissions
118-----------
120To perform a model operation, the user must have the matching permission (`gws.Access`)
121on the model: ``read`` to find features, ``create`` to initialize and create features,
122``write`` to update and ``delete`` to delete them. Database models raise `gws.ForbiddenError`
123when the permission is missing.
125Each field can also have permissions, interpreted as follows:
127- ``read`` - the content of the field can be read from the source and sent to the client
128- ``write`` - user input for this field can be written to the source
130It is *not* an error to read or write a field without permission.
131The attempt is silently ignored.
133If a field has attached value objects, these are applied regardless of field permissions.
135Data flow
136---------
138Database models (`gws.base.database.model`) move data between three representations:
139the record (``feature.record.attributes``, raw source values), the feature (``feature.attributes``,
140python values) and the props (``feature.props.attributes``, client values).
141Scalar fields convert between them with ``raw_to_python``, ``python_to_raw``,
142``prop_to_python`` and ``python_to_prop``.
144Reading::
146 find_features(search, mc)
147 for each field: before_select(mc)
148 add columns and conditions to mc.dbSelect
149 run the select, create a feature with a record for each row
150 for each field: after_select(features, mc)
151 from_record: record -> feature
153Sending to the client::
155 feature_to_props(feature, mc)
156 for each field: to_props(feature, mc)
157 feature -> props, skipped if the user cannot read the field
159Writing::
161 feature_from_props(props, mc)
162 for each field: from_props(feature, mc)
163 props -> feature
165 create_feature(feature, mc) / update_feature(feature, mc)
166 for each field: before_create / before_update
167 to_record: feature -> record, skipped for auto and virtual fields
168 insert or update the row from the record
169 for each field: after_create / after_update
171When a scalar field reads a value (``from_record``, ``to_record``), it uses the first value
172object configured for the current operation. A value object that is not marked as default
173always provides the value. Otherwise the value is taken from the source, if the user
174has access to the field, and the default value object is used only if the source has no value.
176Context
177-------
179All model operations require a context data object (`gws.ModelContext`), usually called ``mc``. This object contains:
181- the operation (``read``, ``update`` etc.)
182- the user performing the operation
183- the current project
184- the depth of related features to load (``relDepth``, ``maxDepth``)
185- other properties, mostly database related
187Models provide methods to perform operations, while fields contain callback methods invoked by the model.
189For example, here is how a database model implements the ``update`` operation::
191 class Model
193 def update_feature (feature, mc)
195 check if mc.user is allowed to write to this model
197 attach an empty record to feature
199 open a transaction in the source
201 for each field in this model
202 invoke the "before_update" callback
203 to transfer data from feature.attributes to feature.record
205 write changes to the source, using feature.uid as a key and feature.record as data
206 (e.g. UPDATE source SET ...record... WHERE id=feature.uid)
208 for each field in this model
209 invoke the "after_update" callback
210 to synchronize updated data, e.g. update a linked model
212 commit the transaction
214Examples
215--------
217An editable database model with a value and a validator::
219 models+ {
220 type "postgres"
221 tableName "edit.poi"
222 isEditable true
223 permissions.edit "allow all"
225 fields+ { name "id" type "integer" isPrimaryKey true permissions.edit "deny all" }
226 fields+ { name "name" type "text" isRequired true widget { type "input" } }
227 fields+ {
228 name "updated"
229 type "datetime"
230 values+ { type "currentTimestamp" forRead false }
231 }
232 fields+ { name "geom" type "geometry" }
233 }
235Reading features of a model in Python::
237 mc = gws.ModelContext(op=gws.ModelOperation.read, target=gws.ModelReadTarget.map, user=user)
238 features = model.find_features(gws.SearchQuery(uids=['1', '2']), mc)
239 props = [model.feature_to_props(f, mc) for f in features]
240"""
242from .core import Config, Object, Props
244from . import manager, default_model, util, field, related_field
246from .util import (
247 iter_features,
248 copy_context,
249 secondary_context,
250)