Defining the Domain Model

Let's make changes to our stock cookiecutter-generated application. We will define two resource constructors, one representing a wiki page, and another representing the wiki as a mapping of wiki page names to page objects. We will do this inside our models.py file.

Because we are using ZODB to represent our resource tree, each of these resource constructors represents a domain model object. We will call these constructors "model constructors". Both our Page and Wiki constructors will be class objects. A single instance of the "Wiki" class will serve as a container for "Page" objects, which will be instances of the "Page" class.

See also

We will introduce a lot of concepts throughout the remainder of this tutorial. See also the chapter Resources for a complete description of resources and the chapter Traversal for the technical details of how traversal works in Pyramid.

Delete the database

In the next step, we will remove the MyModel Python model class from our models package. Since this class is referred to within our persistent storage (represented on disk as a file named Data.fs), we will have strange things happen the next time we want to visit the application in a browser.

Remove the Data.fs from the tutorial directory before proceeding any further. It is always fine to do this as long as you don't care about the content of the database. The database itself will be recreated as necessary.

Edit models package

Note

There is nothing special about the package name models. A project may have many models throughout its codebase in arbitrarily named files and directories. Files that implement models often have model in their names, or they may live in a Python subpackage of your application package named models, but this is only by convention.

Open tutorial/models/__init__.py file and edit it to look like the following:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
from persistent import Persistent
from persistent.mapping import PersistentMapping

class Wiki(PersistentMapping):
    __name__ = None
    __parent__ = None

class Page(Persistent):
    def __init__(self, data):
        self.data = data

def appmaker(zodb_root):
    if 'app_root' not in zodb_root:
        app_root = Wiki()
        frontpage = Page('This is the front page')
        app_root['FrontPage'] = frontpage
        frontpage.__name__ = 'FrontPage'
        frontpage.__parent__ = app_root
        zodb_root['app_root'] = app_root
    return zodb_root['app_root']

Remove the MyModel class from the generated models/__init__.py file. The MyModel class is only a sample and we're not going to use it.

Next we add an import at the top for the persistent.Persistent class. We will use this for a new Page class in a moment.

1
2
from persistent import Persistent
from persistent.mapping import PersistentMapping

Then we add a Wiki class.

4
5
6
class Wiki(PersistentMapping):
    __name__ = None
    __parent__ = None

We want it to inherit from the persistent.mapping.PersistentMapping class because it provides mapping behavior. It also makes sure that our Wiki page is stored as a "first-class" persistent object in our ZODB database.

Our Wiki class should have two attributes set to None at class scope: __parent__ and __name__. If a model has a __parent__ attribute of None in a traversal-based Pyramid application, it means that it is the root model. The __name__ of the root model is also always None.

Now we add a Page class.

 8
 9
10
class Page(Persistent):
    def __init__(self, data):
        self.data = data

This class should inherit from the persistent.Persistent class. We will give it an __init__ method that accepts a single parameter named data. This parameter will contain the reStructuredText body representing the wiki page content.

Note that Page objects don't have an initial __name__ or __parent__ attribute. All objects in a traversal graph must have a __name__ and a __parent__ attribute. We do not specify these here. Instead both __name__ and __parent__ will be set by a view function when a Page is added to our Wiki mapping. We will create this function in the next chapter.

As a last step, edit the appmaker function.

12
13
14
15
16
17
18
19
20
def appmaker(zodb_root):
    if 'app_root' not in zodb_root:
        app_root = Wiki()
        frontpage = Page('This is the front page')
        app_root['FrontPage'] = frontpage
        frontpage.__name__ = 'FrontPage'
        frontpage.__parent__ = app_root
        zodb_root['app_root'] = app_root
    return zodb_root['app_root']

The root resource of our application is a Wiki instance.

We will also slot a single page object (the front page) into the Wiki within the appmaker. This will provide traversal a resource tree to work against when it attempts to resolve URLs to resources.

View the application in a browser

We cannot. At this point, our system is in a "non-runnable" state We will need to change view-related files in the next chapter to be able to start the application successfully. If you try to start the application (See Start the application), you will wind up with a Python traceback on your console that ends with this exception:

ImportError: cannot import name MyModel

This will also happen if you attempt to run the tests.