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
|
=========================
Defining the Domain Model
=========================
The first change we'll make to our stock paster-generated application will be
to define two :term:`resource` constructors, one representing a wiki page,
and another representing the wiki as a mapping of wiki page names to page
objects. We'll do this inside our ``models.py`` file.
Because we're using :term:`ZODB` to represent our
:term:`resource tree`, each of these resource constructors represents a
:term:`domain model` object, so we'll 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.
The source code for this tutorial stage can be browsed via
`http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki/src/models/
<http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki/src/models/>`_.
Deleting the Database
---------------------
In the next step, we're going to remove the ``MyModel`` Python model
class from our ``models.py`` file. Since this class is referred to within
our persistent storage (represented on disk as a file named ``Data.fs``),
we'll 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's 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.
Making Edits to ``models.py``
-----------------------------
.. note::
There is nothing automagically special about the filename ``models.py``. A
project may have many models throughout its codebase in arbitrarily-named
files. Files implementing models often have ``model`` in their filenames,
or they may live in a Python subpackage of your application package named
``models``, but this is only by convention.
The first thing we want to do is remove the ``MyModel`` class from the
generated ``models.py`` file. The ``MyModel`` class is only a sample and
we're not going to use it.
Then, we'll add a ``Wiki`` class. We want it to inherit from the
:class:`persistent.mapping.PersistentMapping` class because it provides
mapping behavior, and it 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 :app:`Pyramid`
application, it means that it's the :term:`root` model. The ``__name__``
of the root model is also always ``None``.
Then we'll add a ``Page`` class. This class should inherit from the
:class:`persistent.Persistent` class. We'll also give it an ``__init__``
method that accepts a single parameter named ``data``. This parameter will
contain the :term:`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 don't specify these here
because both ``__name__`` and ``__parent__`` will be set by by a :term:`view`
function when a Page is added to our Wiki mapping.
As a last step, we want to change the ``appmaker`` function in our
``models.py`` file so that the :term:`root` :term:`resource` of our
application is a Wiki instance. We'll also slot a single page object (the
front page) into the Wiki within the ``appmaker``. This will provide
:term:`traversal` a :term:`resource tree` to work against when it attempts to
resolve URLs to resources.
We're using a mini-framework callable named ``PersistentApplicationFinder``
in our application (see ``__init__.py``). A ``PersistentApplicationFinder``
accepts a ZODB URL as well as an "appmaker" callback. This callback
typically lives in the ``models.py`` file. We'll just change this function,
making the necessary edits.
Looking at the Result of Our Edits to ``models.py``
---------------------------------------------------
The result of all of our edits to ``models.py`` will end up looking
something like this:
.. literalinclude:: src/models/tutorial/models.py
:linenos:
:language: python
Removing View Configuration
---------------------------
In a previous step in this chapter, we removed the
``tutorial.models.MyModel`` class. However, our ``views.py`` module still
attempts to import this class. Temporarily, we'll change ``views.py`` so
that it no longer references ``MyModel`` by removing its imports and removing
a reference to it from the arguments passed to the ``@view_config``
:term:`configuration decoration` decorator which sits atop the ``my_view``
view callable.
The result of all of our edits to ``views.py`` will end up looking
something like this:
.. literalinclude:: src/models/tutorial/views.py
:linenos:
:language: python
Testing the Models
------------------
To make sure the code we just wrote works, we write tests for the model
classes and the appmaker. Changing ``tests.py``, we'll write a separate test
class for each model class, and we'll write a test class for the
``appmaker``.
To do so, we'll retain the ``tutorial.tests.ViewTests`` class provided as a
result of the ``pyramid_zodb`` project generator. We'll add three test
classes: one for the ``Page`` model named ``PageModelTests``, one for the
``Wiki`` model named ``WikiModelTests``, and one for the appmaker named
``AppmakerTests``.
When we're done changing ``tests.py``, it will look something like so:
.. literalinclude:: src/models/tutorial/tests.py
:linenos:
:language: python
Declaring Dependencies in Our ``setup.py`` File
-----------------------------------------------
Our application now depends on packages which are not dependencies of the
original "tutorial" application as it was generated by the ``paster create``
command. We'll add these dependencies to our ``tutorial`` package's
``setup.py`` file by assigning these dependencies to both the
``install_requires`` and the ``tests_require`` parameters to the ``setup``
function. In particular, we require the ``docutils`` package.
Our resulting ``setup.py`` should look like so:
.. literalinclude:: src/models/setup.py
:linenos:
:language: python
Running the Tests
-----------------
We can run these tests by using ``setup.py test`` in the same way we
did in :ref:`running_tests`. Assuming our shell's current working
directory is the "tutorial" distribution directory:
On UNIX:
.. code-block:: text
$ ../bin/python setup.py test -q
On Windows:
.. code-block:: text
c:\pyramidtut\tutorial> ..\Scripts\python setup.py test -q
The expected output is something like this:
.. code-block:: text
.....
----------------------------------------------------------------------
Ran 5 tests in 0.008s
OK
|