A model class is typically a simple Python class defined in a module. References to these classes and instances of such classes are omnipresent in repoze.bfg:
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 repoze.bfg. In particular, it can be unnatural to think of repoze.bfg model objects as “models” if you develop your application using 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 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 repoze.bfg applications (especially ones which use 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.
An example of a model constructor, BlogEntry is presented below. It is implemented as a class which, when instantiated, becomes a model instance.
1 2 3 4 5 6 7 8 | 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.
Model instances can optionally be made to implement an interface. An interface is used to tag a model object with a “type” that can later be referred to within view configuration.
Specifying an interface instead of a class as the context or containment arguments within 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 interface.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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 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 zope.interface.Interface.
A model class may implement zero or more interfaces. You specify that a model implements an interface by using the 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 zope.interface.directlyProvides() function:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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)
|
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 zope.interface.alsoProvides() function:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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)
|
zope.interface.alsoProvides() will augment the set of interfaces directly provided by an instance instead of overwriting them like zope.interface.directlyProvides() does.
For more information about how model interfaces can be used by view configuration, see Using Model Interfaces In View Configuration.
When traversal is used (as opposed to a purely url dispatch based application), 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. repoze.bfg imposes the following policy on model instance nodes in the graph:
See Traversal for more information about how traversal works against model instances.
Applications which use traversal to locate the context of a view must ensure that the model instances that make up the model graph are “location aware”.
In order for repoze.bfg location, security, URL-generation, and traversal functions (such as the functions exposed in repoze.bfg.location, repoze.bfg.traversal, and repoze.bfg.url as well as certain functions in repoze.bfg.security ) to work properly against the instances in an object graph, all nodes in the graph must be 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:
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 repoze.bfg.url.model_url() function and paths generated by the repoze.bfg.traversal.model_path() and 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).
A model instance is used as the context argument provided to a view. See Traversal and URL Dispatch for more information about how a model instance becomes the context.
The APIs provided by repoze.bfg.traversal 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 repoze.bfg.location 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 repoze.bfg.security accept a model object as a parameter. For example, the 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 repoze.bfg.security module also accept context as an argument, and a context is always a model.