Archive for September, 2007

Zope3 for Djangoers. Part 3: Views and templates

Wednesday, September 12th, 2007

I’ve been talking about how to make a web application in my last two posts but still no single html code has been written so far. What an hoax you may think. Well don’t panic, in this article you will finally get some pixels on the screen. :-)

As usually I’ll start remembering how to write a view in Django. We’ll use the models of our last article to keep things simple. So, let’s say you want a view that renders a list of your records, each one with a list of songs. Not too hard. You start writing your view function in a file called views.py inside your app directory:

from django.shortcuts import render_to_response
from myapp.models import Record

def all_my_records(request):
    records = Record.objects.all()
    return render_to_response('record_list.html', dict(records=records))

Now you need to write your template file, in this case we’ve called it record_list.html and it will live inside a templates directory in the root directory of your Django project:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1 Strict//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head><title>Record list</title></head>
<body>
  <h1>My records:</h1>
  {% for record in records %}
    <h2>{{ record.title }} - {{ record.artist }}</h2>
    <ul>
    {% for song in record.song_set.all() %}
      <li>{{ song.title }} - {{ song.length }} seconds</li>
    {% endfor %}
    </ul>
  {% endfor %}
</body>
</html>

Now we need to do a couple of administrative tasks to make this work. First, we need to tell Django that our templates directory is that one. We said so in the settings.py file of our project:

TEMPLATE_DIRS = ('/home/lgs/DJProject/templates',)

Next, we should write a urls.py file inside the project and another one inside the application. Let’s start with the one inside the application directory:

from django.conf.urls.defaults import patterns

urlpatterns = patterns('',
    (r'^my_records/$', 'DJProject.DJApp.views.all_my_records'),
)

As you can see we associate a url (/my_records/) with a view function. Now we just need to include this urls from the master urls.py file in the project directory:

from django.conf.urls.defaults import patterns, include

urlpatterns = patterns('',
(r'^/', include('DJProject.DJApp.urls')
)

And finally a line in the settings.py file:

ROOT_URLCONF = 'DJProject.urls'

Even that Django does not have a configuration language (ZCML) it needs quite a few of configuration settings. This is good indeed since it makes it a lot more powerful and flexible. Note that the settings.py changes and the urls.py of the project directory needs to be changed only onced. If we were to write another view for the DJApp only the urls.py of this directory would need to be changed.

Another good point for Django is its template system. It’s quite readable and pretty powerful. But as any other template system, simple examples like this one are no real issue for it. We will push it harder later on and we will see how good it actually is.

You can test your view by starting the web server and pointing your browser to http://127.0.0.1:8000/my_records/
You will probably get an empty list since there is no records in your database yet :-)

Time to move to Zope3. Let’s try to write the same view but first let’s do an assumption. Our record objects will be stored in the root of the ZODB tree. This will make things simple enough to concentrate in the view issue.

As a convention we will put our HTTP view code in a subpackage called ‘browser’. So let’s create that directory inside our project package and put this code in its __init__.py file:

from myapp.interfaces import IRecord

class AllMyRecordsView(object):
    def records(self):
        for obj in self.context.values():
            if IRecord.providedBy(obj):
                yield obj

This time it’s not a function but a class. Not much harder than the Django version, huh? It just have a method that returns an iterator of the records in the context object. Here the context will be the root container. We need the extra if just in case there is something else in the root container.

Now time for the template, introducing the ZPT (Zope Page Template) language:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1 Strict//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head><title>Record list</title></head>
<body>
  <h1>My records:</h1>
  <div tal:repeat="record view/records" tal:omit-tag="">
    <h2 tal:content="python:'%s - %s' % (record.title, record.artist)">Sticky Fingers - Rolling Stones</h2>
    <ul>
      <li tal:repeat="song record/values"
             tal:content="python:'%s - %d seconds' % (song.title, song.length)">Wild Horses - 220 seconds</li>
    </ul>
  </div>
</body>
</html>

I will tell you that the final html is the same in both versions. I think the Django one is easier to read but we have an advantage with the Zope one is that it is a valid xhtml file that can be edited and previsualized in any HTML editor so you can split your work between programmers and web designers. As you have seen in the previous example we even provided some default values (they will be replaced with the real ones in the runtime version) so we can get a grasp of how it will look like at the end.

Supposed we save the previous template in a file called record_list.pt. We will need to plug the pieces with a bit of configuration code. Put this code in a configure.zcml file inside your browser package:

<configure xmlns="http://namespaces.zope.org/browser">

<page
    name="my_records"
    for="zope.app.folder.interfaces.IFolder"
    class=".AllMyRecordsView"
    template="record_list.pt"
    permission="zope.Public" />

</configure>

Now include this browser package from your main configure.zcml file in the root of your app:

<include package=".browser"/>

And you are ready to go. Start the server and go to http://127.0.0.1:8080/my_records

But having no way to add content to our web application is not really fun. We could add it from a pure Python app but we should expect something else from this great frameworks. See you in part 4 of these series where we’ll discover the black magick mistery of automatic web forms for adding and editing content!

Zope3 for Djangoers. Part 2: Writing your models

Wednesday, September 12th, 2007

Yesterday I wrote an (introduction) article about how to start developing webapps with Zope coming with a Django background. We got to the point where a development environment for both frameworks was ready to start coding. It’s time to get our hands dirty.

As any modern web framework, both Django and Zope are based on a more or less formal variant of the MVC pattern. Usually in this pattern you start writing your models. You do this in Django creating a models.py file inside your app and writing classes that inherit from django.db.models.Model. For example if you are modeling a multimedia library application you will probably have the following models:

from django.db.import models

class Record(models.Model):
    artist = models.CharField('Artist', maxlength=80)
    title = models.CharField('Title', maxlength=150)
    length = models.PositiveIntField('Length', blank=True)
    date = models.DateField('Date', blank=True)

class Song(models.Model):
    title = models.CharField('Title', maxlength=150)
    length = models.PositiveIntField('Length', blank=True)
    record = models.ForeignKey(Record)

class Movie(models.Model):
    title = models.CharField('Title', maxlength=150)
    director = models.CharField('Director', maxlength=80, blank=True
    date = models.DateField('Date', blank=True)
    length = models.PositiveIntField('Length', blank=True)

Ok, that’s enough to have a semi-realistic example. Let’s see how to do the same with your Zope3 app. As pretty much everything else in Zope3 a model is a component, made up by an interface and an implementation. The interface says how the model looks like and what kind of things you allow others on your data. The implementation is an example of something that support your interface. You write your interfaces in a interfaces.py file and the implementation in another file or files. Note that the name of the file ‘interfaces.py’ is just a convention. The ‘models.py’ file in you Django app need to be called that way. Not a big deal though. Let’s write our interfaces.py file:

import zope.app.container.constraints
import zope.app.container.interfaces
import zope.schema

class IRecord(zope.app.container.interfaces.IContainer):
    artist = zope.schema.TextLine(title=u'Artist', description=u'Author of the record')
    title = zope.schema.TextLine(title=u'Title', description=u'Title')
    length = zope.schema.Int(title=u'Length', description=u'Length in seconds', min=0, required=False)
    date = zope.schema.Date(title=u'Date', description=u'Release date', required=False)

    contains('.ISong')

    def length_in_minutes():
        """Return a pretty string with the length in the mm:ss format"""

class ISong(zope.app.container.interfaces.IContained):
    title = zope.schema.TextLine(title=u'Title', description=u'Title')
    length = zope.schema.Int(title=u'Length', description=u'Length in seconds', min=0, required=False)

    containers(IRecord)

    def length_in_minutes():
        """Return a pretty string with the length in the mm:ss format"""

class IMovie(zope.app.container.interfaces.IContained):
    title = zope.schema.TextLine(title=u'Title', description=u'Title')
    director = zope.schema.TextLine(title=u'Director', description=u'Director', required=False)
    date = zope.schema.TextLine(title=u'Date', description=u'Release date', required=False)
    length = zope.schema.Int(title=u'Length', description=u'Length in minutes', min=0, required=False)

Now, time for some comments. Even that our Django models.py and our Zope3 interfaces.py have a lot of things in common as they are defining our models they have some important differences:

  • The classes in interfaces.py are not classes but interfaces. In Python there is no support for interfaces (as there is in Java) so we use the ‘class’ word but they are not clases. You can see they are not classes as the methods we defined doesn’t have the ’self’ or ‘cls’ arguments.
  • In Zope3 every string you put in your code must be unicode. Looks like a drawback but believe me, it will make your life much easier later on.
  • Both in Django and Zope3 a field is mandatory by default. If you want to make a field optional you use the blank=True or required=False argument respectively. I prefer the wording of Zope3 much more.
  • In Django you have a ORM that finally use a Relational Database (hint: ForeignKey field is no surprise here). In Zope3 you use an object oriented database (ZODB). To represent the 1:n relationship between Records and Songs you justs say a Record is a container for Songs. This is much more intuitive and easier to model with an object oriented database. Having said that I must tell you can use a relational database with Zope3. We will do that in following articles.
  • The ‘I’ prefix for our interfaces is a common convention to remind you they are interfaces, not classes.

We still need to implement our interfaces in Zope3 so we’ll write a file called multimedia.py where we can put the implementations. Usually I use several files, one implementation in each one but we’ll put everything in the same file for the sake of simplicity:

import persistent
import zope.interfacefrom zope.app.container.btree import BTreeContainer
from zope.app.container.contained import Contained

import myapp.interfaces

class Record(BTreeContainer):
    zope.interface.implements(myapp.interfaces.IRecord)
    def __init__(self, artist, title, length=None, date=None):
        super(Record, self).__init__()
        self.artist, self.title, self.length, self.date = artist, title, length, date

class Song(Contained, persistent.Persistent):
    zope.interface.implements(myapp.interfaces.ISong)
    def __init__(self, title, length=None):
        super(Song, self).__init__()
        self.title, self.length = title, length

class Movie(Contained, persistent.Persistent):
    zope.interface.implements(myapp.interfaces.IMovie)
    def __init__(self, title, director=None, date=None, length=None):
        super(Song, self).__init__()
        self.title, self.director, self.date, self.length = title, director, date, length

Ok, that’s it. Not as scary as you thought thanks to the existence of a lot of useful base classes we can use. We use BTreeContainer and Contained to make our models aware of the relationship between Records and Songs. We also use Persistent to allow our objects to persist in the ZODB. We could use SQLAlchemy, Storm or even Django ORM here if we wanted to persist our objects in a Relational Database.

Now we must hook the models into the main app. We do this in Django with a two steps process:

  1. Include our application in the settings.py file, in the INSTALLED_APPS tuple
  2. Run the manage.py script with the syncdb argument. This will create a bunch of tables in our database backend. If you also installed the django.contrib.auth, here this command will ask you for a admin username and password. Something Zope3 did the first time the server was run.

In Zope3 we need to register the interfaces. In the next article, we’ll also write some security declarations to our implementations. The database is not affected until we create objects. So we open the configure.zcml file in the root of our application source directory and add this lines:

<interface interface=".interfaces.IRecord"
    type="zope.app.content.interfaces.IContentType" />
<interface interface=".interfaces.ISong"
    type="zope.app.content.interfaces.IContentType" />
<interface interface=".interfaces.IMovie"
    type="zope.app.content.interfaces.IContentType" />

What we are doing is fill some records for the Zope3 component registry, which is a small database of components that is kept globaly in memory but can be overriden with values in the ZODB database. Remember, it’s all about the component architecture: that’s what gives you real power. But every great power comes with great responsability and here the responsability is to correctly fill the configure.zcml file. I hear you little boy crying and winning about that ugly XML file. As we will see later, having a registry of components and the relationships between them allows you to do pretty amazing things.

So I’d say this is the first really important (and probably biggest) difference between Django and Zope3: the component architecture. Love it or hate it, that’s one thing that Django lacks. Before you complaint about this let met tell you a little history: I onced had to explain Django to a PHP experienced developer. As many PHP developers he used to write the <?php > tag scattered in his html files. No templates, no models, no views. Controller? what’s that?. I tried to explain him that using the MVC pattern was a good thing that will help him to design and maintain his applications. He was not sure that this extra learn effort was worthy. I ask the Django developer the same question: is the Component Architecture twist of mind worthy for you?

The answer will depend on your project. As a rule of thumb all I can say is: the more complex your project is the more value you will get from the CA. Every time I hear Zope3 is too hard, it has a very sloppy learning curve, etc, etc. I can’t say that’s not true. That is indeed true for every software project with a component architecture: think about Mozilla XUL, Microsoft COM or OpenOffice.org UNO. Zope3 looks easy next to them :-)

See you in the next article, where we will see how to render pixel on the screen: the exciting part!

Zope3 for Djangoers. Part 1: Installation

Monday, September 10th, 2007

I’m been working on a couple of Django projects for two months and I must say it’s a great platform for making web applications. Even when this is not the first time I play with Django. I tend to forget things too easily…

Having said that I must say I still prefer another Python web framework. One that’s pretty big but at the same time it’s small. One that it’s new but at the same time quite old. Yes, I’m talking about Zope. Zope3 precisely.

But I still think there is no perfect framework and each one has its strengths and its weaknesses. So, as always, you should study your requirements and your problem and decide which tool is best for you. As an overly simplified statement I’d say take Django for small 3-5 months projects that won’t change in the future and take Zope3 for everything else. Don’t use the same hammer for every nail.

Now, one of the things Django gets much better than Zope3 is documentation. It’s much easier to get started with Django not only because it doesn’t have a component architecture but because its tutorial and the rest of documentation really rocks.

I’ll try to write a series of posts to give the Zope3 newbie a little bit of knowledge so he/she doesn’t get lost in the Zope3 forrest. And more important, I’ll write them from a Djangoer perspective so you can always compare both frameworks.

Note of caution: in the zope3 world there are a lot of ways to do the same things. You may like this or not but that’s the way it is. In these articles I’ll just choose one of such ways. Probably not the best and hopefully not the worst. Just don’t think it is the canonical zope3 development process. I don’t think there is such one.

In this first article we’ll talk about installation. In Django you just need to have Python installed in your box and a database management system like sqlite, Mysql or Postgresql. That’s pretty much everything you need. Then you write this command:

django-admin.py startproject DJProject

and it creates an environment where you can start writing your code. Actually your are supposed to create applications inside of your project. I don’t like this wording since for me, a project is an application most of the time. Usually I split my project in components following best practices but they are not applications by their own. I know, Django lets you live without creating apps but things get out of the main road and you start living by your own. So let’s be good citizen and create our application:

cd DJProject
./manage.py startapp DJApp

Finally if you want to start your development server all you have to do is type this command:

./manage.py runserver

Ok, let’s jump into the zope3 side. In the old days Zope was a big fat framework and your app was just a module that you hooked into it. Now recently Zope3 has been splitted into lots of small components distributed as Python eggs. This approach has the advantage that you only use what you need keeping it simpler. The disadvantage is that usually you don’t know what you need :-) At least at the beginning. With some practice everything get easier.

Zope3 doesn’t currently work with Python 2.5 due to some changes in the C extensions mechanism that the 2.5 version introduced. There is quite a lot of C code in Zope3 to optimize things: some examples are the ZODB and the security system. That’s why you should stick to 2.4 until the 2.5 support is finished.

To work with Python eggs you need Easy Install which is the tool than manages them. Installing it is super simple:

wget http://peak.telecommunity.com/dist/ez_setup.py
python ez_setup.py

This will download everything needed to get started. Next step is install zopeproject. It is a great tool to start a Zope3 project. Its philosophy is that Zope3 is just a library and your app should use that library, not the other way around. It creates a WSGI application with a lot of default configuration made for you. Check out this great article to learn why WSGI is such a great thing. Ok, let’s get back to our project:

easy_install zopeproject
zopeproject Z3Project

The last command will ask you for a directory to place the zope3 eggs. This way other applications can use those eggs without duplicating them. It will also ask you for an administrator username and password. Something similar to what you get when you syncdb your Django project after adding the django.contrib.auth application.

Now you have a Z3Project directory pretty similar to your myproject Django directory. Running an http development server is quite simple:

cd Z3Project
bin/paste serve deploy.ini

But wait, I’ve got a bunch of files I don’t understand!, what are they for? And where is my code supposed to live? Well, don’t panic. I’ll try to answer these questions. The environment we just created is managed by buildout. This software handle the dependencies for your project and create/update the starting points for running, testing and debugging it among other things. These are the files you get:

  • setup.py: it’s a regular distutils file that says how to make an egg for your application. The important thing is that it lists the list of eggs your app depends on.
  • zope.conf: them main zope configuration file. You put your database here and the ZCML file the application will load on startup. Kind of a part of the settings.py file of Django.
  • site.zcml: the file which configures the component architecture of Zope. More on this later.
  • buildout.cfg: the buildout configuration file that basically tells buildout to build the egg each time it is executed taking care of the dependencies and to update the scripts in the bin directory.
  • deploy.ini: as we use Paste for the http server, this file tell it which port and IP to use.

And your code is supposed to live in the z3project subdirectory inside the Z3Project that zopeproject created. You will see a configure.zcml and an application.py files inside that directory, that is a Python package actually. The configure.zcml is included from the site.zcml we already saw and it loads a bunch of sane defaults for Zope3. It’s also the place where you should register your own components. The application.py defines a WSGI application factory that Paste will use to run the http server.

We’ll see how to write our models in the next article. See you then!