A model class is typically a simple Python class defined in a module. These classes are termed model constructors. Model instances make up the graph that repoze.bfg is willing to traverse when traversal is used. A model instance is also generated as a result of url dispatch. A model instance is exposed to view code as the context of a view.
An example of a model constructor, BlogEntry is presented below. It is 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. This makes it possible to register views against the interface itself instead of the class within view statements within the application registry. 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).
An interface simply tags the model object with a “type” that can be referred to within the application registry. A model object can implement zero or more interfaces. The interface must be an instance of a class that inherits from zope.interface.Interface.
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 API:
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()
directlyProvides(IBlogEntry, entry)
|
If a model object already has instance-level interface declarations that you don’t want to disturb, use the zope.interface.alsoProvides API:
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()
directlyProvides(IBlogEntry1, entry)
alsoProvides(IBlogEntry2, entry)
|
See the Views for more information about why providing models with an interface can be an interesting thing to do with regard to view lookup.
When traversal is used (as opposed to a purely url dispatch based application), mod:repoze.bfg expects to be able to traverse a graph 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 a instances in a model 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
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 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 are 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.
Note
If you’d rather not manage the __name__ and __parent__ attributes of your models “by hand”, an add-on package to repoze.bfg` named repoze.bfg.traversalwrapper can help you do this.
In order to use this helper feature, you must first install the repoze.bfg.traversalwrapper package (available from http://svn.repoze.org/repoze.bfg.traversalwrapper), then register its ModelGraphTraverser as the traversal policy, rather than the default BFG ModelGraphTraverser. To register the repoze.bfg.traversalwrapper ModelGraphTraverser as the traversal policy, your application will need to have the following in its configure.zcml file:
<adapter
factory="repoze.bfg.traversalwrapper.ModelGraphTraverser"
provides="repoze.bfg.interfaces.ITraverserFactory"
for="*"
/>
If this statement is made in ZCML, you will no longer need to manage the __parent__ and __name__ attributes on graph objects “by hand”. Instead, as necessary, during traversal 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.
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, find the URL of a model, the root model in a model graph, and so on.
The APIs provided by repoze.bfg.location are used against model instances. These can be used to walk down a model graph, or conveniently locate one object “inside” another.
Some APIs in repoze.bfg.security accept a model object as a parameter. For example, the 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 same module also accept context as an argument, and a context is always a model.