========================
Zope 3 Quick Start Guide
========================
Introduction
------------
This Quick Start assumes you are familiar with Python, Subversion (if you want
to run Zope 3 from a checkout), and the generalities of the web (HTML,
HTTP servers, etc.).
Installing Zope 3
-----------------
This quick start was written using a the 3.2 version of Zope. Later versions
may also work. You'll also need Python 2.4 (or the version of Python needed
by the later version of Zope 3 you decided to use).
Make a Directory
~~~~~~~~~~~~~~~~
First we'll need a directory to put our files in
(for the rest of this document substitute back slashes for forward slashes in
file names if you're using Windows)::
mkdir zope3_quick_start
cd zope3_quick_start
Using a Release
~~~~~~~~~~~~~~~
You may also use the 3.2 release (Zope 3 releases are available at
http://www.zope.org/Products/Zope3).
Follow the instructions in the included README.txt to install Zope 3.
Using a Checkout
~~~~~~~~~~~~~~~~
If you want to check out the 3.2 tag or the trunk you need Subversion. To do
a Subversion checkout follow these instructions::
svn checkout svn://svn.zope.org/repos/main/Zope3/tags/Zope-3.2.0 zope3
Then follow the appropriate directions below depending on your operating
system.
Linux
^^^^^
Run these commands (the trailing dot is part of the command)::
cd zope3
python setup.py build_ext -i install_data --install-dir .
cd ..
Windows
^^^^^^^
If you have an appropriate compiler installed, you can run the same commands
as listed above for Linux.
Otherise run these commands (the trailing dot is part of the command)::
cd zope3
python setup.py install_data --install-dir .
cd ..
And then download Tim Peter's Windows binaries for the Zope 3 C code and
install according to the instructions on the page (make sure you get the
version appropriate for the version of Python you're using):
http://www.zope.org/Members/tim_one/
Make an Instance
----------------
Zope 3 uses "instances" to contain the information relevant to a server (or
set of servers). Let's make an instance we can use to develop our app. This
command will be slightly different depending on how you installed Zope 3 and
what operating system you're on. See the README.txt included with Zope 3 for
detailed instructions on making your instance.
python zope3/bin/mkzopeinstance
You'll go through this dialog with the utility::
Please choose a directory in which you'd like to install Zope
'instance home' files such as database files, configuration files,
etc.
Directory: instance
Please choose a username for the initial administrator account.
This is required to allow Zope's management interface to be used.
Username: admin
Please select a password manager which will be used for encode the password of
the initial administrator account.
1. Plain Text
2. MD5
3. SHA1
Password Manager Number [1]:
'Plain Text' password manager selected
Please provide a password for the initial administrator account.
Password:
Verify password:
After you've made your instance, change to the instance directory and run::
bin/runzope
You should see something like this::
------
2005-09-28T20:40:11 INFO PublisherHTTPServer zope.server.http (HTTP) started.
Hostname: my-computer
Port: 8080
------
2005-09-28T20:40:11 INFO PublisherFTPServer zope.server.ftp started.
Hostname: my-computer
Port: 8021
------
2005-09-28T20:40:11 INFO root Startup time: 5.538 sec real, 3.120 sec CPU
The ZMI
-------
If you open a web browser and go to http://localhost:8080 you'll see the ZMI
(Zope Management Interface).
Go ahead and click the "Login" link at the upper right. Enter the user name
and password you gave when creating the instance. Now click on [top] under
"Navigation" on the right. Play around with adding some content objects (the
Zope 3 name for instances that are visible in the ZMI). Note how content
objects can be arranged in a hierarchy by adding "folders" which are special
content objects that can hold other content objects.
There is nothing special about the ZMI, it is just the default skin for
Zope 3. You can modify it to your liking, or replace it entirely.
When you're done exploring with the ZMI, go back to the window where you typed
"runzope" and see that each request from your browser was displayed there as
it happened. Press Control-C to stop Zope.
Hello World
-----------
We'll write a simple "Hello World" program.
Our First Content Object
~~~~~~~~~~~~~~~~~~~~~~~~
We need to go to the lib/python directory of our instance and create a
directory called "hello" mkdir instance/lib/python/hello and
create a directory called and in that directory a file called "hello.py".
Inside "hello.py", type this text::
import persistent
class HelloWorld(persistent.Persistent):
greeting = 'Hello'
subject = 'world'
This differs from a "normal" Python class in that it derives from
persistent.Persistent. The "Persistent" base class will note any changes made
to our instances and make sure they are written to the object database (ZODB).
There are a couple other things you should know about persistence, but we'll
look at those later.
We'll also need an empty file named "__init__.py" for the "hello" directory to
be treated as a package by Python.
Registering the Content Type
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
We need to let Zope 3 know that we want to see our content type in the "add
menu" in the ZMI. We need to create a "configure.zcml" file in our "hello"
directory and put a "browser:addMenuItem" directive there.
ZCML (Zope Configuration Markup Language) is an XML language for configuring
Zope components. It is always possible substitute the appropriate Python
code for a particular piece of ZCML, but ZCML is usually much more succinct,
composable, and maintainable.
The first thing in the file should be a "configure" tag. All ZCML files begin
with "configure" tags which contain the other tags. Zope makes use of XML
namespaces (the "xmlns" attribute) to identify the context for each tag.
The most fundamental tags are in the http://namespaces.zope.org/zope namespace.
Another namespace we'll use here is for browser-specific directives. The
"configure" tag also includes the (optional, but highly recommended)
"i18n_domain" attribute which describes the internationalization domain which
applies to the human-readable text in this ZCML file (like "title" below).
So our "configure.zcml" file looks like this::
The "class" attribute specifies the module path for the class, a leading dot
means to make the import relative to the package containing the ZCML file.
Therefore in this case Zope will import the hello module, then import
"HelloWorld" from that module.
The "title" attribute provides the title to display in the add menu (we prefix
the title with "QS" because Zope comes with another "hello world" object out
of the box, so be sure to use the right one).
The "permission" attribute is used to describe what permission is required for
a person to be able to add one of these objects. The "zope.ManageContent"
permission means that the user can add, remove, and modify content (the
"admin" user you created while making the instance is one such user).
We need to tell Zope to read our ZCML file, and the easiest way to do that is
to put a "slug" in the instance/etc/package-includes directory. A "slug" is a
ZCML file that just includes another file. Here's what our slug should look
like (save it as "hello-configure.zcml")::
Now if we start Zope back up, we can go to the ZMI and add our content type by
clicking on "QS Hello World" and entering a name for our object; name it
"hello". If we click on our object after adding it, we'll just see a generic
view that tells us what class the object is ("hello.hello.HelloWorld").
A New View
~~~~~~~~~~
We want to provide a more interesting view. Our view will use the greeting
and subject from the content object and construct a message from it.
A "view" is simply a way of rendering a particular content object. We'll
create a simple view that renders our content object to HTML. Views are
informed about their content object by being assigned a "context" attribute.
In other words, if you're writing code for your view class you can access the
content object you're providing a view of as "self.context".
Here's a view class to add to "hello.py"::
class MessageView(object):
def message(self):
return '%s %s!' % (self.context.greeting, self.context.subject)
def __call__(self):
return '
%s' % self.message()
So, why do we have views? There are a few of reasons:
- views are a way to separate the data from the presentation. Content
objects represent the problem domain objects in our application; the views
represent implementation objects.
- you might want more than one view of a single content object (HTTP, FTP,
JSON, XML, etc.)
- you might also want to apply the same view to different content objects
(like the built-in "Introspector")
Configuring the View
~~~~~~~~~~~~~~~~~~~~
We have to configure the view so it is available for our content object. This
is done in ZCML so we (or people using our code) can add, remove, or
substitute views later without changing the code.
Add the following to the end of hello/configure.zcml (but before
"")::
Now restart zope (Control-C, bin/runzope) and visit the view in your browser
at this URL: http://localhost:8080/hello.
Uh oh! What does "A system error occurred." mean? Whenever an exception is
raised but not caught the "system error" message will be generated.
You can go to the console window you have Zope running in and look at the line
that starts "ForbiddenAttribute". The rest of the line should look like this::
('greeting', )
That means that we tried to access an attribute (named "greeting") that we
don't have permission to see. That's because Zope 3 comes with an extensive
security system which assumes that any attribute is not accessible unless
declared otherwise.
Security
~~~~~~~~
So, to say that anyone can see the "greeting" (and just so we don't get
another ForbiddenAttribute exception, "subject" too) we'll add this to the end
of hello/configure.zcml::
In English, this says: Require the zope.Public permission for a user to be
able to read the "greeting" and "subject" attributes of HelloWorld objects.
Any user (even anonymous ones) have the zope.Public permission, so because our
view only uses those two attributes, anyone should be able to see it. Restart
Zope and go back to http://localhost:8080/hello and you will see the results
of the view.
Sidebar: So why doesn't Zope 3 just assume every attribute is public
until told otherwise? History has shown that writing secure software can
be difficult. Therefore it is best to make everything secure by default
and make conscious decisions to open up security when necessary. The
alternative of making everything public by default and having to "add in"
security is not practical.
Zope Page Templates
-------------------
You'll note that our view doesn't "cooperate" with the ZMI, the HTML we
generated was returned untouched to the browser. Sometimes that is exactly
what you want, but sometimes you want to include standard headings, menus,
etc. like the ZMI does.
The way you do that in Zope 3 is by using page templates. The main ZMI page
template provides a way for us to generate content for it to display using ZPT
macros. Create a file called "hello.pt" in our "hello" package. Make the
file look like this::
Note how the template calls the view's "message" method to get the contents
of the "big" tag and then fills the ZMI's "body" slot with the "big" tag (or
anything else we put in the slot).
Now we need to wire up the template so it will be used. Add this import to
the top of "hello.py"::
from zope.app import pagetemplate
And add this new __call__ to the "MessageView" class (deleting the old
"__call__" method)::
template = pagetemplate.ViewPageTemplateFile('hello.pt')
def __call__(self):
return self.template()
You can now restart Zope and refresh http://localhost:8080/hello and you'll
see the new template in action.
ZCML and Templates
~~~~~~~~~~~~~~~~~~
Because associating templates with views is so common and to make it easier to
substitute one template with another without having to change the view code,
there is a ZCML shortcut for doing it.
So, we can remove the three lines we added above so our view class just looks
like this (and remove the now unneccesary "from zope.zpp import pagetemplate"
line)::
class MessageView(object):
def message(self):
return '%s %s!' % (self.context.greeting, self.context.subject)
Then add the line 'template="hello.pt"' to the "browser:page" tag in hello's
"configure.zcml" so that it looks like this::
Now, if you restart the server you can refresh http://localhost:8080/hello and
see that we get the same results.
An Edit View
------------
Now we want to be able to edit the subject and greeting attributes of our
objects through the web. We could go through the effort of creating HTML
"form" tags and processing the responses, making sure to validate the user
input, but Zope makes it easier.
Interfaces
~~~~~~~~~~
First, we need to know what data the form will collect. Zope uses
"interfaces" with "schema fields" to do that. Add this simple interface
to the begining of "hello.py" (after the imports)::
from zope import interface, schema
class IHelloWorld(interface.Interface):
greeting = schema.TextLine()
subject = schema.TextLine()
Now that we have an interface describing our HelloWorld class, we can annotate
the class with the interface it implements. Add an "implements" call to the
HelloWorld class definition so that it looks like this::
class HelloWorld(persistent.Persistent):
interface.implements(IHelloWorld)
greeting = 'Hello'
subject = 'world'
The "interface.implements" line lets other parts of the system know what
interface(s) our instances promise to provide.
Forms
~~~~~
Now we can add an edit view to "hello.py". Instead of building an HTML form
"by hand", we'll use a standard zope package called "formlib". We have to
import "formlib" at the top of "hello.py" like so::
from zope.formlib import form
And then add our edit view to the end of the file::
class EditView(form.EditForm):
form_fields = form.Fields(IHelloWorld)
We want the form to render inside the ZMI, so we'll create a template named
"edit.pt" that fills the ZMI's "body" slot much like the page template above.
It should look like this::
Now all we have to do is to tell Zope about this new view, and its template.
So we add this to the "hello" package's "configure.zcml" file (this is the
same data we provided for our first view)::
Now, if we restart Zope and go to http://localhost:8080/hello/edit.html we'll
see an edit form.
Schemas
~~~~~~~
When we made the interface IHelloWorld we set the attributes to
"schema.TextLine". That is a way of describing what that attribute holds (a
line of text in this case). There are other schema fields, and they can also
get more descriptive. Let's flesh out our IHelloWorld schema a bit. First
we'll add titles and descriptions::
class IHelloWorld(interface.Interface):
greeting = schema.TextLine(
title=u'Greeting',
description=u'The salutation used to greet the subject.')
subject = schema.TextLine(
title=u'Subject',
description=u'Who or what is being greeted.')
If we restart Zope and refresh the edit form we'll see the results of our
changes: now the text fields have titles, and if we click (and hold) the mouse
button on the field title, we'll see the description.
There are several other settings for fields including: required/optional,
constraints (max/min, match regular expression, etc.), complex data types
(sequences, mappings, etc.).
More Security
~~~~~~~~~~~~~
Change the value of one of the fields and press "Apply". Another exception!
Click the back button and follow the "Errors" link. See the
"ForbiddenAttribute" error?
What happened was this: you tried to modify an attribute that you don't have
permission to modify. Remember the ZCML for the "greeting" and "subject"
attributes we added before (in "configure.zcml")::
That only let us read those attributes, not write them. Now we'll let anyone
with "zope.ManageContent" permission (like the "admin" user) change the
attributes. Instead of naming the attributes one-by-one like we did before,
we can leverage the fact that the interface already knows about them, so we'll
make the "content" element in "configure.zcml" look like this::
We replaced the "attributes" attribute with "interface", and added a "require"
directive indicating that only users with "zope.ManageContent" (like "admin")
can actually change the values. Now you can restart Zope and change the
values.
Adding Menu Entries
~~~~~~~~~~~~~~~~~~~
Now click on "[top]" and then click on "hello" and you'll see that our views
aren't displayed; Zope doesn't assume that we want them to be listed in the
ZMI view menu (along with "Introspector") just because they exist.
In this case we do want them displayed, so we'll register them as part of the
"zmi_views" menu. We could register them using only Python, but it's easier
to do with ZCML (and also means they'll be overridable by someone else if they
don't like our menu entries).
The ZMI view menu works by associating an interface with the views that should
be listed for it, therefore instead of the "for" attribute pointing directly
to our content class, we need to use the IHelloWorld interface instead. This
has the added benefit that *any* content object that implements the
IHelloWorld interface will also get our "Message" and "Edit" views! This also
means that it would be easy for a third party to add views to our content
objects.
We'll add "menu" and "title" attributes to the "browser:page" directive in
"configure.zcml". The two "browser:page" elements should look like this now::
Restart zope and go to http://localhost:8080/ and click on our "hello" object.
You'll see that both of our views are listed and can be clicked on. The
views are listed in order of "specificity" and then order of definition in the
ZCML. Our views are more specific because they are registered for only
IHelloWorld, where as the Introspector view is registered for the most basic
interface "Interface".
More Persistence
----------------
So far we haven't worried much about persistence, everything has just magically
worked. That's because we've been using attribute assignment to mutate our
instances, so it has been easy for Zope (actually ZODB) to know when our
objects change, but for more complex object models that isn't always the case.
Fortunately we only have to do a couple of simple things and Zope takes care
of the rest. If you want a persistent list or dict, use
persistent.list.PersistentList and persistent.dict.PersistentDict,
(respectively) instead of the built-in objects.
If you want to use a built-in or other instance for which there are no
persistent versions available (and you can't make your own), all you have to
do is make sure the persistent object to which it is attached knows that its
value has changed. There are two ways to do that. First, after mutating the
object re-assign it to it's attribute::
def doSomething(self):
self.some_mutable.mutate()
self.some_mutable = self.some_mutable
Alternatively you can directly set the _p_changed attribute::
def doSomething(self):
self.some_mutable.mutate()
self._p_changed = True
Note that you only have to think about persistence for objects that are
*actually* persistent, not for things like views. In practice even very large
Zope 3 applications won't have to do much of this.
Version
-------
This is version 0.6 of the Zope 3 Quick Start Guide.
License
-------
|CreativeCommons| This work is licensed under a `Creative Commons Attribution-ShareAlike 2.5 License `_.
Attribution in derivative works should be given as "Benji York (http://benjiyork.com)".
..
.. |CreativeCommons| image:: http://creativecommons.org/images/public/somerights20.png