summaryrefslogtreecommitdiff
path: root/docs/narr/models.rst
blob: c6f896119c4e9ba57804ebcf9f9a5a6a4b0f5f4d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
Models
======

A :term:`model` class is typically a simple Python class defined in a
module.  References to these classes and instances of such classes are
omnipresent in :mod:`repoze.bfg`:

- Model instances make up the graph that :mod:`repoze.bfg` is
  willing to walk over when :term:`traversal` is used.

- The ``context`` and ``containment`` arguments to
  :meth:`repoze.bfg.configuration.Configurator.add_view` often
  reference a model class.

- A :term:`root factory` returns a model instance.

- A model instance is generated as a result of :term:`url dispatch`
  (see the ``factory`` argument to
  :meth:`repoze.bfg.configuration.Configurator.add_route`).

- A model instance is exposed to :term:`view` code as the
  :term:`context` of a view.

Model objects typically store data and offer methods related to
mutating that data.

.. note::

   A terminology overlap confuses people who write applications that
   always use ORM packages such as SQLAlchemy, which has a very
   different notion of the definition of a "model".  When using the API
   of common ORM packages, its conception of "model" is almost
   certainly not the same conception of "model" used by
   :mod:`repoze.bfg`.  In particular, it can be unnatural to think of
   :mod:`repoze.bfg` model objects as "models" if you develop your
   application using :term:`traversal` and a relational database.  When
   you develop such applications, the object graph *might* be composed
   completely of "model" objects (as defined by the ORM) but it also
   might not be.  The things that :mod:`repoze.bfg` refers to as
   "models" in such an application may instead just be stand-ins that
   perform a query and generate some wrapper *for* an ORM "model" or
   set of ORM models.  This naming overlap is slightly unfortunate.
   However, many :mod:`repoze.bfg` applications (especially ones which
   use :term:`ZODB`) do indeed traverse a graph full of literal model
   nodes.  Each node in the graph is a separate persistent object that
   is stored within a database.  This was the use case considered when
   coming up with the "model" terminology.  However, if we had it to do
   all over again, we'd probably call these objects something
   different to avoid confusion.

.. index::
   single: model constructor

Defining a Model Constructor
----------------------------

An example of a model constructor, ``BlogEntry`` is presented below.
It is implemented as a class which, when instantiated, becomes a model
instance.

.. code-block:: python
   :linenos:

   import datetime

   class BlogEntry(object):
       def __init__(self, title, body, author):
           self.title = title
           self.body =  body
           self.author = author
           self.created = datetime.datetime.now()

A model constructor may be essentially any Python object which is
callable, and which returns a model instance.  In the above example,
the ``BlogEntry`` class can be "called", returning a model instance.

.. index::
   single: model interfaces

.. _models_which_implement_interfaces:

Model Instances Which Implement Interfaces
------------------------------------------

Model instances can optionally be made to implement an
:term:`interface`.  An interface is used to tag a model object with a
"type" that can later be referred to within :term:`view
configuration`.

Specifying an interface instead of a class as the ``context`` or
``containment`` arguments within :term:`view configuration` statements
effectively makes it possible to use a single view callable for more
than one class of object.  If your application is simple enough that
you see no reason to want to do this, you can skip reading this
section of the chapter.

For example, here's some code which describes a blog entry which also
declares that the blog entry implements an :term:`interface`.

.. code-block:: python
   :linenos:

   import datetime
   from zope.interface import implements
   from zope.interface import Interface

   class IBlogEntry(Interface):
       pass

   class BlogEntry(object):
       implements(IBlogEntry)
       def __init__(self, title, body, author):
           self.title = title
           self.body =  body
           self.author = author
           self.created = datetime.datetime.now()

This model consists of two things: the class which defines the model
constructor (above as the class ``BlogEntry``), and an
:term:`interface` attached to the class (via an ``implements``
statement at class scope using the ``IBlogEntry`` interface as its
sole argument).

The interface object used must be an instance of a class that inherits
from :class:`zope.interface.Interface`.

A model class may *implement* zero or more interfaces.  You specify
that a model implements an interface by using the
:func:`zope.interface.implements` function at class scope.  The above
``BlogEntry`` model implements the ``IBlogEntry`` interface.

You can also specify that a *particular* model instance provides an
interface (as opposed to its class).  To do so, use the
:func:`zope.interface.directlyProvides` function:

.. code-block:: python
   :linenos:

   from zope.interface import directlyProvides
   from zope.interface import Interface

   class IBlogEntry(Interface):
       pass

   class BlogEntry(object):
       def __init__(self, title, body, author):
           self.title = title
           self.body =  body
           self.author = author
           self.created = datetime.datetime.now()

   entry = BlogEntry('title', 'body', 'author')
   directlyProvides(entry, IBlogEntry)

:func:`zope.interface.directlyProvides` will replace any existing
interface that was previously provided by an instance.  If a model
object already has instance-level interface declarations that you
don't want to replace, use the :func:`zope.interface.alsoProvides`
function:

.. code-block:: python
   :linenos:

   from zope.interface import alsoProvides
   from zope.interface import directlyProvides
   from zope.interface import Interface

   class IBlogEntry1(Interface):
       pass

   class IBlogEntry2(Interface):
       pass

   class BlogEntry(object):
       def __init__(self, title, body, author):
           self.title = title
           self.body =  body
           self.author = author
           self.created = datetime.datetime.now()

   entry = BlogEntry('title', 'body', 'author')
   directlyProvides(entry, IBlogEntry1)
   alsoProvides(entry, IBlogEntry2)

:func:`zope.interface.alsoProvides` will augment the set of interfaces
directly provided by an instance instead of overwriting them like
:func:`zope.interface.directlyProvides` does.

For more information about how model interfaces can be used by view
configuration, see :ref:`using_model_interfaces`.

.. index::
   single: model graph
   single: traversal graph
   single: object graph
   single: container nodes
   single: leaf nodes

Defining a Graph of Model Instances for Traversal
-------------------------------------------------

When :term:`traversal` is used (as opposed to a purely :term:`url
dispatch` based application), :mod:`repoze.bfg` expects to be able to
traverse a graph composed of model instances.  Traversal begins at a
root model, and descends into the graph recursively via each found
model's ``__getitem__`` method.  :mod:`repoze.bfg` imposes the
following policy on model instance nodes in the graph:

- Nodes which contain other nodes (aka "container" nodes) must supply
  a ``__getitem__`` method which is willing to resolve a unicode name
  to a subobject.  If a subobject by that name does not exist in the
  container, ``__getitem__`` must raise a :exc:`KeyError`.  If a
  subobject by that name *does* exist, the container should return the
  subobject (another model instance).

- Nodes which do not contain other nodes (aka "leaf" nodes) must not
  implement a ``__getitem__``, or if they do, their ``__getitem__``
  method must raise a :exc:`KeyError`.

See :ref:`traversal_chapter` for more information about how traversal
works against model instances.

.. index::
   pair: location-aware; model

.. _location_aware:

Location-Aware Model Instances
------------------------------

.. sidebar::  Using :mod:`repoze.bfg.traversalwrapper`

  If you'd rather not manage the ``__name__`` and ``__parent__``
  attributes of your models "by hand", an add on package named
  :mod:`repoze.bfg.traversalwrapper` can help.

  In order to use this helper feature, you must first install the
  :mod:`repoze.bfg.traversalwrapper` package (available via `SVN
  <http://svn.repoze.org/repoze.bfg.traversalwrapper>`_), then
  register its ``ModelGraphTraverser`` as the traversal policy, rather
  than the default :mod:`repoze.bfg` traverser. The package contains
  instructions.

  Once :mod:`repoze.bfg` is configured with this feature, you will no
  longer need to manage the ``__parent__`` and ``__name__`` attributes
  on graph objects "by hand".  Instead, as necessary, during traversal
  :mod:`repoze.bfg` will wrap each object (even the root object) in a
  ``LocationProxy`` which will dynamically assign a ``__name__`` and a
  ``__parent__`` to the traversed object (based on the last traversed
  object and the name supplied to ``__getitem__``).  The root object
  will have a ``__name__`` attribute of ``None`` and a ``__parent__``
  attribute of ``None``.

Applications which use :term:`traversal` to locate the :term:`context`
of a view must ensure that the model instances that make up the model
graph are "location aware".

In order for :mod:`repoze.bfg` location, security, URL-generation, and
traversal functions (such as the functions exposed in
:ref:`location_module`, :ref:`traversal_module`, and :ref:`url_module`
as well as certain functions in :ref:`security_module` ) to work
properly against the instances in an object graph, all nodes in the
graph must be :term:`location` -aware.  This means they must have two
attributes: ``__parent__`` and ``__name__``.

The ``__parent__`` attribute should be a reference to the node's
parent model instance in the graph.  The ``__name__`` attribute should
be the name that a node's parent refers to the node via
``__getitem__``.

The ``__parent__`` of the root object should be ``None`` and its
``__name__`` should be the empty string.  For instance:

.. code-block:: python

   class MyRootObject(object):
       __name__ = ''
       __parent__ = None

A node returned from the root item's ``__getitem__`` method should
have a ``__parent__`` attribute that is a reference to the root
object, and its ``__name__`` attribute should match the name by which
it is reachable via the root object's ``__getitem__``.  *That*
object's ``__getitem__`` should return objects that have a
``__parent__`` attribute that points at that object, and
``__getitem__``-returned objects should have a ``__name__`` attribute
that matches the name by which they are retrieved via ``__getitem__``,
and so on.

.. warning:: If your root model object has a ``__name__`` argument
   that is not ``None`` or the empty string, URLs returned by the
   :func:`repoze.bfg.url.model_url` function and paths generated by
   the :func:`repoze.bfg.traversal.model_path` and
   :func:`repoze.bfg.traversal.model_path_tuple` APIs will be
   generated improperly.  The value of ``__name__`` will be prepended
   to every path and URL generated (as opposed to a single leading
   slash or empty tuple element).

.. index::
   single: model API functions
   single: url generation (traversal)

:mod:`repoze.bfg` API Functions That Act Against Models
-------------------------------------------------------

A model instance is used as the :term:`context` argument provided to a
view.  See :ref:`traversal_chapter` and :ref:`urldispatch_chapter` for
more information about how a model instance becomes the context.

The APIs provided by :ref:`traversal_module` are used against model
instances.  These functions can be used to find the "path" of a model,
the root model in an object graph, or generate a URL to a model.

The APIs provided by :ref:`location_module` are used against model
instances.  These can be used to walk down an object graph, or
conveniently locate one object "inside" another.

Some APIs in :ref:`security_module` accept a model object as a
parameter.  For example, the
:func:`repoze.bfg.security.has_permission` API accepts a "context" (a
model object) as one of its arguments; the ACL is obtained from this
model or one of its ancestors.  Other APIs in the
:mod:`repoze.bfg.security` module also accept :term:`context` as an
argument, and a context is always a model.