Skip to content

Commit 71774a2

Browse files
committed
improve schemas chapter
1 parent c805227 commit 71774a2

File tree

1 file changed

+105
-95
lines changed

1 file changed

+105
-95
lines changed

docs/backend/schemas.md

Lines changed: 105 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,13 @@ substitutions:
1919
## Introduction
2020

2121
[Zope schemas](https://zopeschema.readthedocs.io) are a database-neutral and form-library-neutral way to
22-
describe Python data models.
22+
describe Python data models. Schemas extend the notion of interfaces to detailed descriptions of Attributes (but not methods). Every schema is an interface and specifies the public fields of an object. A field roughly corresponds to an attribute of a Python object. But a Field provides space for at least a title and a description. It can also constrain its value and provide a validation method. Besides you can optionally specify characteristics such as its value being read-only or not required.
2323

2424
Plone uses Zope schemas for these purposes:
2525

2626
- to describe persistent data models;
2727
- to describe HTML form data;
28+
- to describe Plone configuration data
2829
- to describe ZCML configuration data.
2930

3031
Since Zope schemas are not bound to e.g. a SQL database engine, it gives
@@ -102,95 +103,19 @@ class ICheckoutAddress(zope.interface.Interface):
102103

103104
This schema can be used in {ref}`classic-ui-forms-label` and Dexterity {ref}`backend-content-types-label` data models.
104105

105-
106-
## Advanced
107-
108-
We can use this class to store data based on our model definition in the ZODB
109-
database.
110-
111-
We use `zope.schema.fieldproperty.FieldProperty` to bind
112-
persistent class attributes to the data definition.
113-
114-
Example:
115-
116-
```
117-
from persistent import Persistent # Automagical ZODB persistent object
118-
from zope.schema.fieldproperty import FieldProperty
119-
120-
class CheckoutAddress(Persistent):
121-
""" Store checkout address """
122-
123-
# Declare that all instances of this class will
124-
# conform to the ICheckoutAddress data model:
125-
zope.interface.implements(ICheckoutAddress)
126-
127-
# Provide the fields:
128-
first_name = FieldProperty(ICheckoutAddress["first_name"])
129-
last_name = FieldProperty(ICheckoutAddress["last_name"])
130-
organization = FieldProperty(ICheckoutAddress["organization"])
131-
phone = FieldProperty(ICheckoutAddress["phone"])
132-
country = FieldProperty(ICheckoutAddress["country"])
133-
state = FieldProperty(ICheckoutAddress["state"])
134-
city = FieldProperty(ICheckoutAddress["phone"])
135-
postal_code = FieldProperty(ICheckoutAddress["postal_code"])
136-
street_address = FieldProperty(ICheckoutAddress["street_address"])
137-
```
138-
139-
For persistent objects, see {doc}`persistent object documentation
140-
</develop/plone/persistency/persistent>`.
141-
142-
143-
### More info
144-
145-
- [zope.schema](https://pypi.python.org/pypi/zope.schema) on PyPi
146-
- [zope.schema source code](http://github.com/zopefoundation/zope.schema) - definite source for field types and usage.
147-
- [zope.schema documentation](https://zopeschema.readthedocs.io)
148-
- [plone.schema](https://github.com/plone/plone.schema)
149-
150-
`zope.schema` and `plone.schema` provide a very comprehensive set of {ref}`backend-fields-label` out of the box.
151-
Finding good documentation for them, however, can be challenging. Here are
152-
some starting points:
153-
154-
- {ref}`reference list of fields used in Plone <backend-fields-label>`
155-
156-
157-
## Using schemas as data models
158-
159-
Based on the example data model above, we can use it in e.g. content type
160-
{doc}`browser views </develop/plone/views/browserviews>` to store arbitrary data as content
161-
type attributes.
162-
163-
Example:
164-
106+
```{note}
107+
In Dexterity {ref}`backend-content-types-label` the base class for a schema is `plone.supermodel.model.Schema`.
108+
This provides functionalities to export and import schemas via XML and TTW editor.
165109
```
166-
class MyView(BrowserView):
167-
""" Connect this view to your content type using a ZCML declaration.
168-
"""
169-
170-
def __call__(self):
171-
# Get the content item which this view was invoked on:
172-
context = self.context.aq_inner
173-
174-
# Store a new address in it as the ``test_address`` attribute
175-
context.test_address = CheckoutAddress()
176-
context.test_address.first_name = u"Mikko"
177-
context.test_address.last_name = u"Ohtamaa"
178110

179-
# Note that you can still add arbitrary attributes to any
180-
# persistent object. They are simply not validated, as they
181-
# don't go through the ``zope.schema`` FieldProperty
182-
# declarations.
183-
# Do not do this, you will regret it later.
184-
context.test_address.arbitary_attribute = u"Don't do this!"
185-
```
186111

187-
## Field constructor parameters
112+
### Field constructor parameters
188113

189114
The `Field` base class defines a list of standard parameters that you can
190115
use to construct schema fields. Each subclass of `Field` will have its own
191116
set of possible parameters in addition to this.
192117

193-
See the full list [here](http://docs.zope.org/zope3/Code/zope/schema/_bootstrapfields/Field/index.html).
118+
See [IField interface](https://zopeschema.readthedocs.io/en/latest/api.html#zope.schema.interfaces.IField) and [field implementation](https://zopeschema.readthedocs.io/en/latest/api.html#field-implementations) in `zope.schema` documentation for details.
194119

195120
Title
196121

@@ -220,9 +145,12 @@ is probably not what you intend. Instead, initialize objects in the
220145
221146
In particular, dangerous defaults are: `default=[]`, `default={}`,
222147
`default=SomeObject()`.
148+
149+
Use `defaultFactory=get_default_name` instead!
223150
```
224151

225-
## Schema introspection
152+
153+
### Schema introspection
226154

227155
The `zope.schema._schema` module provides some introspection functions:
228156

@@ -247,7 +175,7 @@ class IMyInterface(zope.interface.Interface):
247175
fields = zope.schema.getFields(IMyInterface)
248176
```
249177

250-
### Dumping schema data
178+
#### Dumping schema data
251179

252180
Below is an example how to extract all schema defined fields from an object.
253181

@@ -276,7 +204,7 @@ def dump_schemed_data(obj):
276204
return out
277205
```
278206

279-
### Finding the schema for a Dexterity type
207+
#### Finding the schema for a Dexterity type
280208

281209
When trying to introspect a Dexterity type, you can get a reference to the schema thus:
282210

@@ -291,13 +219,95 @@ schema = getUtility(IDexterityFTI, name=PORTAL_TYPE_NAME).lookupSchema()
291219
fields added to it at this stage, only the fields directly defined in your
292220
schema.
293221

294-
## Field order
222+
223+
### More info
224+
225+
- [zope.schema](https://pypi.python.org/pypi/zope.schema) on PyPi
226+
- [zope.schema source code](http://github.com/zopefoundation/zope.schema) - definite source for field types and usage.
227+
- [zope.schema documentation](https://zopeschema.readthedocs.io)
228+
- [plone.schema](https://github.com/plone/plone.schema)
229+
230+
`zope.schema` and `plone.schema` provide a very comprehensive set of {ref}`backend-fields-label` out of the box.
231+
Finding good documentation for them, however, can be challenging. Here are
232+
some starting points:
233+
234+
- {ref}`reference list of fields used in Plone <backend-fields-label>`
235+
236+
237+
## Advanced
238+
239+
We can use a schema class to store data based on our model definition in the ZODB
240+
database.
241+
242+
We use `zope.schema.fieldproperty.FieldProperty` to bind
243+
persistent class attributes to the data definition.
244+
245+
Example:
246+
247+
```
248+
from persistent import Persistent # Automagical ZODB persistent object
249+
from zope.schema.fieldproperty import FieldProperty
250+
251+
class CheckoutAddress(Persistent):
252+
""" Store checkout address """
253+
254+
# Declare that all instances of this class will
255+
# conform to the ICheckoutAddress data model:
256+
zope.interface.implements(ICheckoutAddress)
257+
258+
# Provide the fields:
259+
first_name = FieldProperty(ICheckoutAddress["first_name"])
260+
last_name = FieldProperty(ICheckoutAddress["last_name"])
261+
organization = FieldProperty(ICheckoutAddress["organization"])
262+
phone = FieldProperty(ICheckoutAddress["phone"])
263+
country = FieldProperty(ICheckoutAddress["country"])
264+
state = FieldProperty(ICheckoutAddress["state"])
265+
city = FieldProperty(ICheckoutAddress["phone"])
266+
postal_code = FieldProperty(ICheckoutAddress["postal_code"])
267+
street_address = FieldProperty(ICheckoutAddress["street_address"])
268+
```
269+
270+
For persistent objects, see {doc}`persistent object documentation
271+
</develop/plone/persistency/persistent>`.
272+
273+
274+
### Using schemas as data models
275+
276+
Based on the example data model above, we can use it in e.g. content type
277+
{doc}`browser views </develop/plone/views/browserviews>` to store arbitrary data as content
278+
type attributes.
279+
280+
Example:
281+
282+
```
283+
class MyView(BrowserView):
284+
""" Connect this view to your content type using a ZCML declaration.
285+
"""
286+
287+
def __call__(self):
288+
# Get the content item which this view was invoked on:
289+
context = self.context.aq_inner
290+
291+
# Store a new address in it as the ``test_address`` attribute
292+
context.test_address = CheckoutAddress()
293+
context.test_address.first_name = u"Mikko"
294+
context.test_address.last_name = u"Ohtamaa"
295+
296+
# Note that you can still add arbitrary attributes to any
297+
# persistent object. They are simply not validated, as they
298+
# don't go through the ``zope.schema`` FieldProperty
299+
# declarations.
300+
# Do not do this, you will regret it later.
301+
context.test_address.arbitary_attribute = u"Don't do this!"
302+
```
303+
304+
### Field order
295305

296306
The `order` attribute can be used to determine the order in which fields in
297307
a schema were defined. If one field was created after another (in the same
298308
thread), the value of `order` will be greater.
299309

300-
## Default values
310+
### Default values
301311

302312
To make default values of schema effective, class attributes must be
303313
implemented using `FieldProperty`.
@@ -324,7 +334,7 @@ something = SomeStorage()
324334
assert something.some_value == True
325335
```
326336

327-
## Validation and type constrains
337+
### Validation and type constrains
328338

329339
Schema objects using field properties provide automatic validation
330340
facilities, preventing setting badly formatted attributes.
@@ -387,7 +397,7 @@ class IContact(Interface):
387397
email = schema.TextLine(title=u'Email', constraint=isEmail)
388398
```
389399

390-
## Persistent objects and schema
400+
### Persistent objects and schema
391401

392402
ZODB persistent objects do not provide facilities for setting field defaults
393403
or validating the data input.
@@ -463,7 +473,7 @@ header = HeaderBehavior()
463473
assert header.alternatives = []
464474
```
465475

466-
## Collections (and multichoice fields)
476+
### Collections (and multichoice fields)
467477

468478
Collections are fields composed of several other fields.
469479
Collections also act as multi-choice fields.
@@ -475,7 +485,7 @@ For more information see:
475485
- Schema [field sources documentation](https://zopeschema.readthedocs.io/en/latest/sources.html#sources-in-fields)
476486
- [Choice field](https://zopeschema.readthedocs.io/en/latest/fields.html#choice)
477487

478-
### Single-choice example
488+
#### Single-choice example
479489

480490
Only one value can be chosen.
481491

@@ -518,7 +528,7 @@ class ISyncRunOptions(Interface):
518528
default=logging.INFO)
519529
```
520530

521-
### Multi-choice example
531+
#### Multi-choice example
522532

523533
Using zope.schema.List, many values can be chosen once.
524534
Each value is atomically constrained by *value_type* schema field.
@@ -546,7 +556,7 @@ class IMultiChoice(model.Schema):
546556
)
547557
```
548558

549-
## Dynamic schemas
559+
### Dynamic schemas
550560

551561
Schemas are singletons, as there only exist one class instance
552562
per Python run-time. For example, if you need to feed schemas generated dynamically
@@ -565,7 +575,7 @@ as the same copy is shared between different threads and
565575
if there are two concurrent HTTP requests problems occur.
566576
```
567577

568-
### Replacing schema fields with dynamically modified copies
578+
#### Replacing schema fields with dynamically modified copies
569579

570580
The below is an example for z3c.form. It uses Python `copy`
571581
module to copy f.field reference, which points to zope.schema
@@ -600,7 +610,7 @@ def fields(self):
600610
f.field = schema_field
601611
```
602612

603-
### Don't use dict {} or list \[\] as a default value
613+
#### Don't use dict {} or list \[\] as a default value
604614

605615
Because how Python object construction works, giving \[\] or {}
606616
as a default value will make all created field values to share this same object.

0 commit comments

Comments
 (0)