Accueil » Tutoriels » Zope 3 et les paquetages communautaires (z3c.*)

Zope 3 et les paquetages communautaires (z3c.*)

Document Actions

Par tflorac le 14/02/2008 23:46

Catégories : Zope3

Zope 3 et les paquets communautaires (z3c.*)

Auteur:Paul Carduner <paulcarduner AT gmail.com>
Traducteur:Thierry Florac <tflorac AT ulthar.net>
Relectures:AFPY / http://www.afpy.org
Version: 0.0.3
BZR Revision:27
Copyright: (C) 2008 Paul Carduner, Placé sous licence GNU GPL version 2, ou (à votre convenance) toute version ultérieure

Note

Je suppose que vous utilisez un système Ubuntu Gutsy ou similaire.

1   Introduction

Depuis neuf mois environ, peu de temps après que Philipp von Weitershausen a publié la seconde version de Web Component Development with Zope 3, Zope 3 a subi des changements et une restructuration massifs. L'application autrefois connue sous le nom de Zope 3 a été éclatée en une centaine de morceaux en faveur du « Framework Zope 3 ». Chaque composant de Zope 3 est devenu un « paquet » (package) séparé distribué via les « oeufs » (eggs) Python et un arbre de dépendances complexe. Un autre changement important survenu pendant ces neuf derniers mois a été la création de nombreux paquets maintenus par la communauté des développeurs mais qui ne sont pas considérés comme faisant partie du « coeur » de Zope. Ce sont les paquet « z3c.* ». Écrits principalement par des hackers « rebelles » de Zope 3, cherchant à rendre Zope encore plus puissant, les paquets z3c.* sont rapidement devenus prépondérants. Malheureusement pour le reste d'entre nous, ceux qui savaient comment utiliser les paquets z3c.* étaient tellement occupés qu'ils n'avaient jamais une minute pour écrire un livre, un tutoriel ou une documentation facile d'accès. Ce document est donc destiné à apaiser les souffrances de ceux qui font encore les choses « à l'ancienne » en leur proposant une humble introduction aux paquets « z3c.* ».

2   Pour commencer

2.1   Paramétrer un environnement virtuel

Avant de commencer à travailler sur une application, c'est toujours une bonne idée de définir un environnement de travail Python qui soit séparé de l'environnement pré-installé avec la distribution Linux que l'on utilise (dans mon cas, une Ubuntu Gutsy). En créant un environnement Python « virtuel », on peut facilement éviter de nombreux problèmes associés aux systèmes Python tels que des paquets brisés, des conflits de dépendances, etc.

Un environnement Python virtuel peut être créé facilement en utilisant le script virtualenv.py disponible à l'adresse http://svn.colorstudy.com/virtualenv/trunk/virtualenv.py. Pour consulter la documentation relative à ce paquet, consultez la page Pypi à l'adresse http://pypi.python.org/pypi/virtualenv. Téléchargez ce script dans votre répertoire personnel et créez un nouvel environnement appelé sandbox (NdT : « bac à sable »):

$ python ~/virtualenv.py sandbox

Dans les faits, vous pouvez placer cet environnement où bon vous semble et le nommer comme vous voulez. Le nom indiqué correspond dans tous les cas à un nouveau répertoire avec un sous-répertoire bin contenant un nouvel exécutable Python et un script easy_install. Il contient également un script bash pour positionner votre shell dans ce nouvel environnement sans avoir à saisir le chemin d'accès complet vers cet exécutable Python. Exécutez le simplement avec:

$ cd sandbox
$ source bin/activate

Après cela, vous pouvez voir si tout fonctionne correctement:

(sandbox)$ which python
/home/pcardune/sandbox/bin/python

À partir de ce point, chaque fois que vous verrez une commande du shell à exécuter, vous pouvez supposer sauf mention contraire qu'il faut l'exécuter après avoir activé votre environnement virtuel.

2.2   Utiliser zopeproject

Il est pratique de démarrer avec un squelette d'application basique, pour éviter de devoir créer toute la configuration initiale à partir de rien. Heureusement, Philipp von Weitershausen a écrit un excellent outil appelé zopeproject pour réaliser cela.

zopeproject peut être installé en utilisant easy_install. Entrez simplement:

$ easy_install zopeproject

Vous allez ensuite créer un nouveau projet appelé « ZContact ». zopeproject crée toute la configuration de base dont vous aurez besoin pour démarrer immédiatement une application Zope vide et s'y connecter avec un compte d'administration. Il vous sera demandé de saisir un code utilisateur et un mot de passe pour ce compte d'administration (qui pourra être changé à tout moment), ainsi qu'un emplacement pour l'installation des composants nécessaires:

$ zopeproject zcontact
Enter user (Name of an initial administrator user): manager
Enter passwd (Password for the initial administrator user): zcontact
Enter eggs_dir (Location where zc.buildout will look for and place packages) ['/home/pcardune/buildout-eggs']: eggs

Cette étape peut demander plusieurs minutes car beaucoup de code doit être téléchargé et une partie doit être compilée. Pendant que vous attendez, je vous conseille de jeter un oeil sur la documentation de zopeproject pour avoir une idée plus précise de ce qu'il fait.

2.3   Démarrer le serveur

Une fois que c'est fait, vous pouvez vous précipiter et démarrer votre application naissante qui s'exécutera par défaut sur le port 8080:

$ cd zcontact
$ ./bin/paster serve deploy.ini
Starting server in PID 23818.
serving on http://127.0.0.1:8080

Vous devriez maintenant obtenir un écran zope vide qui ressemble à celui ci-dessous :

images/screen1.png

Le fichier deploy.ini indiqué en paramètre de la commande ./bin/paster contient les options nécessaires au démarrage du serveur, tel que le numéro de port utilisé. zopeproject génère également un fichier debug.ini qui intègre un filtre WSGI pour le traitement des erreurs ; si vous rencontrez des erreurs, vous pouvez utiliser ce fichier de configuration pour inspecter votre code, pendant l'exécution, dans votre navigateur.

2.4   Ajuster les paramètres de sécurité

Pour que les choses restent simples dans ce tutoriel, vous allez ajuster les permissions générées par zopeproject de façon à ne plus devoir vous en préoccuper par la suite. Ajoutez donc les lignes suivantes au fichier site.zcml situé dans le répertoire racine de votre application :

1
2
<role id="zope.Anonymous" title="Everybody" />
<grantAll role="zope.Anonymous" />

Cela attribue aux utilisateurs anonymes un accès complet à toute l'application, sans aucune restriction.

2.5   Créer une interface simple et son implémentation

Avant de pouvoir plonger dans l'univers des paquets z3c.*, vous devez disposer de quelques objets pour pouvoir jouer avec. Comme ZContact est censé être un gestionnaire de contacts, vous allez démarrer avec un objet contact. Toujours dans le but de rester simple, il ne contiendra que deux attributs, un prénom et un nom.

Note

Tout le code source réside dans le répertoire zcontact/src/zcontact de telle sorte que le fichier zcontact/src/zcontact/interfaces.py puisse être importé via import zcontact.interfaces.

2.5.1   L'interface IContact

Ouvrez le fichier interfaces.py et ajoutez ceci :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import zope.interface
import zope.schema

class IContact(zope.interface.Interface):
    """A simple contact."""

    firstName = zope.schema.TextLine(
        title=u"First Name",
        required=True)

    lastName = zope.schema.TextLine(
        title=u"Last Name",
        required=True)

2.5.2   Implémenter l'interface IContact

Vous pouvez maintenant construire une implémentation rapide de cette interface. Ouvrez le fichier contact.py et ajoutez ceci :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import zope.interface
from zope.schema.fieldproperty import FieldProperty

import interfaces

class Contact(object):
    """See ``zcontact.interfaces.IContact``."""
    zope.interface.implements(interfaces.IContact)

    firstName = FieldProperty(interfaces.IContact['firstName'])
    lastName = FieldProperty(interfaces.IContact['lastName'])

2.5.3   Sécuriser la classe Contact

Vous devez également « effectuer l'inscription » (register) de la classe Contact dans un fichier ZCML et définir les permissions qui permettront d'accéder à ses différents attributs. Ajoutez les lignes suivantes au fichier src/zcontact/configure.zcml :

1
2
3
4
5
6
7
8
<class class="zcontact.contact.Contact">
  <require
      interface=".interfaces.IContact"
      permission="zope.View" />
  <require
      set_schema=".interfaces.IContact"
      permission="zope.ManageContent" />
</class>

Lorsque c'est fait, vous pouvez vous attaquer aux vues, qui vont utiliser les composants z3c.*.

Caution!

N'oubliez pas que dans le cadre d'un vrai projet vous devrez commencer par écrire des tests unitaires, ne serait-ce que pour s'assurer que la classe Contact implémente réellement toute l'interface IContact.

3   Formulaires avec z3c.form

L'étape suivante pour ZContact est d'être capable d'ajouter des contacts en utilisant un formulaire qui demande le nom et le prénom d'un nouveau contact. Un tel formulaire peut être généré à partir du schéma (IContact) qui a été défini dans interfaces.py en utilisant le paquet z3c.form.

3.1   Ajouter les dépendances z3c.form et z3c.formui

Avant de pouvoir utiliser le paquet z3c.form, vous devez le définir en tant que dépendance de votre application.

Pour ajouter z3c.form comme dépendance, ouvrez le fichier setup.py à la racine de votre application et ajoutez 'z3c.form', au paramètre install_required (normalement aux environs de la ligne 25). Vous pouvez aussi ajouter à cette liste de dépendances z3c.formui et z3c.layer, qui sont nécessaires pour avoir un rendu plus sympathique des formulaires.

Ces paquets nécessitent une configuration particulière ; il faut donc inclure les fichiers de configuration ZCML de z3c.form et z3c.formui dans le fichier zcontact/src/configure.zcml. z3c.form utilise lui-même de nombreux autres composants qui définissent de nouvelles directives ZCML. Pour utiliser ces directives, vous devez inclure leur fichier « meta » au début du fichier configure.zcml avant toute autre inclusion :

1
2
3
4
<include package="zope.viewlet" file="meta.zcml" />
<include package="z3c.form" file="meta.zcml" />
<include package="z3c.macro" file="meta.zcml" />
<include package="z3c.template" file="meta.zcml" />

Intégrez ensuite les paquets z3c.form et z3c.formui eux-mêmes à la fin du fichier configure.zcml :

1
2
<include package="z3c.form" />
<include package="z3c.formui" />

Maintenant, tout ce qu'il vous reste à faire est de relancer le processus de construction initial de telle sorte que les nouveaux oeufs pour z3c.form et z3c.formui soient téléchargés et rendus disponibles. Pour cela, lancez simplement:

$ ./bin/buildout -Nv

L'option -N évite de télécharger une nouvelle fois les paquets qui l'ont déjà été (hautement recommandé).

3.2   Paramétrer l'application pour fonctionner avec z3c.form

Malheureusement, tout n'est pas encore tout à fait prêt pour utiliser z3c.form. Dans la mesure où les paquets z3c ont mis en place des motifs de conception d'applications assez différents, il reste quelques étapes à franchir pour que tout fonctionne. L'une de ces étapes consiste à définir votre propre « couche » (layer) et votre « habillage » (skin). Car malheureusement, de nombreuses directives ZCML ne vous imposent pas de spécifier explicitement pour quelle couche vous souhaitez que votre page ou votre vue soit inscrite. Si vous n'indiquez aucune couche spécifique, elles sont donc inscrites pour une couche par défaut. Il s'ensuit que beaucoup de paquets (y compris Rotterdam) inscrivent toutes leurs vues dans la couche par défaut, ce qui la rend hautement polluée avec des choses dont vous ne voulez pas toujours. Pour éviter cette pollution, les paquets z3c.* inscrivent toutes leurs vues dans des couches spécifiques au paquet en question. Pour utiliser un paquet z3c.* correctement, vous devez donc étendre ces couches avec votre propre couche.

Vous allez donc maintenant créer une couche qui va vous permettre d'utiliser les vues du paquet z3c.form.

3.2.1   Créer une couche

Créez un nouveau fichier, src/zcontact/layer.py, et ajoutez-y le code suivant :

1
2
3
4
5
from z3c.form.interfaces import IFormLayer
from z3c.layer.pagelet import IPageletBrowserLayer

class IZContactBrowserLayer(IFormLayer, IPageletBrowserLayer):
    """ZContact browser layer with form support."""

L'interface IFormLayer dispose de vues pour tous les composants graphiques que l'on retrouve dans les formulaires qui vont être générés, et IPageletBrowserLayer fournit des vues pour les erreurs ainsi que d'autres vues utiles.

3.2.2   Créer un habillage

Pour accéder à cette couche depuis un navigateur, vous devez créer un habillage. Créez donc un autre fichier, src/zcontact/skin.py, auquel vous ajouterez le code suivant :

1
2
3
4
5
6
7
import z3c.formui.interfaces

from zcontact import layer

class IZContactBrowserSkin(z3c.formui.interfaces.IDivFormLayer,
                           layer.IZContactBrowserLayer):
    """The ZContact browser skin using the div-based layout."""

Notez bien que cet habillage va hériter de la couche IDivFormLayer, définie dans le paquet z3c.formui. Lorsque les formulaires sont rendus en HTML, les champs sont intégrés dans des tags <div> plutôt que dans des tables. Il existe d'autres couches de rendu qui utilisent des tables, et grâce à l'architecture de composants, il vous est également possible d'écrire votre propre couche de rendu personnalisée (ce qui ne sera pas fait ici).

Maintenant, vous devez inscrire l'habillage en ZCML dans un nouveau fichier, src/zcontact/skin.zcml. Pour rendre cet habillage accessible à partir de http://localhost:8080/++skin++ZContact/, il faut quelque chose comme ceci :

1
2
3
4
5
6
7
8
9
<configure xmlns="http://namespaces.zope.org/zope">

  <interface
      interface=".skin.IZContactBrowserSkin"
      type="zope.publisher.interfaces.browser.IBrowserSkinType"
      name="ZContact"
      />

</configure>

N'oubliez pas d'inclure ce nouveau fichier ZCML dans zcontact/configure.zcml, avec la ligne <include package="zcontact" file="skin.zcml" />.

3.3   Créer un formulaire d'ajout

Vous allez commencer en créant un nouveau module zcontact.browser, en créant un répertoire browser dans le répertoire src/zcontact/ contenant en premier lieu uniquement un fichier vide __init__.py. Puis vous pourrez créer un nouveau fichier zcontact/browser/contact.py dans lequel vous allez définir tous les formulaires.

Commencez par ajouter le code suivant au fichier browser/contact.py :

1
2
3
4
5
6
7
8
9
from z3c.form import form, field

from zcontact import interfaces


class ContactAddForm(form.AddForm):
    """A simple add form for contacts."""

    fields = field.Fields(interfaces.IContact)

La classe form.AddForm dont on hérite définit seulement des méthodes pour créer et ajouter votre nouvel objet, que vous surchargerez plus tard, ainsi que des boutons que l'on trouve typiquement sur une fenêtre de création.

Vous pouvez ensuite créer une page qui utilise cette classe pour afficher le formulaire. Ouvrez zcontact/browser/configure.zcml et ajoutez ceci :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<configure xmlns="http://namespaces.zope.org/browser">

  <page
      name="addContact.html"
      for="zope.app.folder.interfaces.IFolder"
      permission="zope.Public"
      layer="zcontact.layer.IZContactBrowserLayer"
      class=".contact.ContactAddForm"
      />

</configure>

Cette page est inscrite sous le nom « addContact.html » et pour l'interface IFolder. Comme le répertoire racine de chaque nouvelle instance Zope implémente IFolder, vous devriez pouvoir accéder à cette page à l'adresse http://localhost:8080/++skin++ZContact/@@addContact.html. N'oubliez pas d'inclure le paquet browser dans zcontact/configure.zcml en ajoutant la ligne <include package=".browser" /> avant la fin du fichier.

Vous devriez maintenant être prêt pour l'action. Redémarrez le serveur en exécutant ./bin/paster serve deploy.ini (ou debug.ini si vous voulez) et accédez à l'adresse http://localhost:8080/++skin++ZContact/@@addContact.html. Vous devriez voir un formulaire très simple tel que celui-ci :

images/addFormSimple.png

3.4   Terminer la fenêtre d'ajout

Si vous avez essayé d'utiliser le formulaire, et cliqué sur le bouton d'ajout, vous avez dû obtenir une erreur NotImplemented. Si vous choisissez de lancer le fichier de configuration debug.ini au lieu de deploy.ini avec paster, alors vous avez probablement obtenu un bel écran comme celui-ci :

images/serverError.png

À partir de cet écran, vous pouvez développer n'importe quelle ligne de la trace d'erreur en cliquant sur les « + », et entrer dans le code Python pour déverminer le problème. J'ai été très impressionné la première fois que j'ai vu ça !

Pour corriger ce problème, vous devez implémenter trois méthodes de la classe ContactAddForm: create, add and nextURL. J'ai décidé de les implémenter comme ci-dessous pour les garder aussi courtes que possible, mais vous pouvez laisser aller votre créativité :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from z3c.form import form, field

from zcontact import interfaces
from zcontact.contact import Contact


class ContactAddForm(form.AddForm):
    """A simple add form for contacts."""

    fields = field.Fields(interfaces.IContact)

    def create(self, data):
        contact = Contact()
        form.applyChanges(self, contact, data)
        return contact

    def add(self, contact):
        self._name = "%s-%s" % (contact.lastName.lower(),
                                contact.firstName.lower())
        self.context[self._name] = contact

    def nextURL(self):
        return '/'

Dans la méthode create, la fonction form.applyChanges est utilisée pour affecter les valeurs aux attributs firstName et lastName du nouveau contact. Le paramètre data passé à la méthode create est un « mapping » entre les noms des champs et les données saisies, déjà converties dans les types de données Python corrects. Pour l'exemple, il aurait donc été possible de faire contact.firstName = data['firstName'].

La méthode nextURL retourne un chemin d'accès codé en dur qui devrait vous ramener à la page d'accueil de l'habillage par défaut (Rotterdam) ; vous pouvez voir le contact que vous venez de créer dans la liste des contenus. On fait cela ici car les vues sur vos contenus pour votre propre couche/habillage ne sont pas encore disponibles, et il faut donc se rabattre pour l'instant sur l'habillage par défaut.

3.5   Formulaire d'affichage et de modification

Les formulaires d'affichage et de modification sont encore plus simples que les formulaires de création, car vous n'avez aucune méthode à surcharger. Commencez donc par créer un formulaire d'affichage.

3.5.1   Créer un formulaire d'affichage

Pour créer un formulaire d'affichage, vous devez avoir une nouvelle classe qui hérite de form.Form. Pour que les composants graphiques s'affichent sous la forme de texte simple plutôt que de zones de saisie, vous devez forcer le mode de la fenêtre à DISPLAY_MODE, qui est une constante que l'on peut importer de z3c.form.interfaces. Ouvrez donc zcontact/browser/contact.py et ajoutez le code suivant :

1
2
3
4
5
class ContactDisplayForm(form.Form):
    """A simple display form for contacts."""

    fields = field.Fields(interfaces.IContact)
    mode = DISPLAY_MODE

N'oubliez pas d'inscrire le nouveau formulaire dans le fichier configure.zcml avec :

1
2
3
4
5
6
7
<page
    name="index.html"
    for="..interfaces.IContact"
    permission="zope.Public"
    layer="zcontact.layer.IZContactBrowserLayer"
    class=".contact.ContactDisplayForm"
    />

Maintenant que vous avez un formulaire d'affichage, vous pouvez modifier la méthode nextURL de la classe ContactAddForm de façon à accéder directement au contact nouvellement créé. Elle devrait ressembler à ceci :

1
2
def nextURL(self):
    return absoluteURL(self.context[self._name], self.request)

N'oubliez pas d'inclure la ligne from zope.traversing.browser.absoluteurl import absoluteURL au début du fichier. Si tout va bien, vous devriez pouvoir redémarrer le serveur et ajouter un nouveau contact à l'adresse http://localhost:8080/++skin++ZContact/@@addContact.html avant d'être redirigé vers le formulaire d'affichage. Il ressemble à ceci :

images/DisplayFormScreenShot.png

3.5.2   Ajouter des boutons à un formulaire

Vous allez ajouter deux boutons, l'un pour modifier le contact et l'autre pour le supprimer. Commencez par ajouter from z3c.form import button au début du fichier contact.py. Quand un utilisateur clique sur un bouton, le formulaire est envoyé vers l'URL spécifiée par l'attribut action du formulaire. Par défaut, cette action pointe vers l'URL du formulaire lui-même, donc lorsque l'on clique sur un bouton le formulaire est rechargé. Lorsque ce formulaire est traité, il vérifie quel est le bouton qui a été utilisé et appelle les gestionnaires d'événements appropriés qui sont définis comme des méthodes de la classe ContactDisplayForm.

Avec z3c.form, on peut définir en même temps un bouton et son gestionnaire d'événement en utilisant un décorateur. Pour le bouton de suppression, on veut supprimer le contact et rediriger l'utilisateur vers le formulaire d'ajout. Ajoutez le code suivant à la classe ContactDisplayForm :

1
2
3
4
5
6
7
@button.buttonAndHandler(u'Delete', name='delete')
def handleDelete(self, action):
    name = getName(self.context)
    parent = getParent(self.context)
    del parent[name]
    nextURL = absoluteURL(parent, self.request)+'/@@addContact.html'
    self.request.response.redirect(nextURL)

N'oubliez pas d'ajouter les imports suivants au début du fichier :

1
2
3
4
from z3c.form import form, field, button
from z3c.form.interfaces import DISPLAY_MODE
from zope.traversing.browser.absoluteurl import absoluteURL
from zope.traversing.api import getParent, getName

Maintenant vous allez créer le bouton d'édition. Vous n'avez pas encore le formulaire de modification, aussi ce bouton va vous renvoyer vers une page qui n'existe pas. Ajoutez néanmoins le code suivant à la classe ContactDisplayForm :

1
2
3
4
@button.buttonAndHandler(u'Edit', name="edit")
def handleEdit(self, action):
    nextURL = absoluteURL(self.context, self.request) + '/@@editContact.html'
    self.request.response.redirect(nextURL)

Maintenant vous pouvez redémarrer le serveur et tester les boutons. Votre formulaire doit ressembler à ceci :

images/DisplayFormButtonScreenShot.png

3.5.3   Créer un formulaire d'édition

Maintenant, vous devez pratiquement être devenu un pro de ces histoires de formulaires générés automatiquement. Votre dernière étape consistera à créer un formulaire de modification, qui est le type de formulaire le plus simple de tous. Voici le code à ajouter :

1
2
3
4
class ContactEditForm(form.EditForm):
    """A simple edit form for contacts."""

    fields = field.Fields(interfaces.IContact)

sans oublier la configuration ZCML nécessaire :

1
2
3
4
5
6
7
<page
    name="editContact.html"
    for="..interfaces.IContact"
    permission="zope.Public"
    layer="zcontact.layer.IZContactBrowserLayer"
    class=".contact.ContactEditForm"
    />

Allez-y, et essayez ce formulaire d'édition en cliquant sur le bouton « Edit » à partir du formulaire d'affichage. Vous remarquez que l'on dispose déjà du bouton d'enregistrement pour soumettre les modifications. Après validation, vous vous retrouvez devant un message d'état qui indique le succès ou l'échec. À ce niveau, pour ajouter un moyen de revenir vers votre formulaire d'affichage initial, vous devez ajouter un bouton de retour à la classe ContactEditForm avec ce code :

1
2
3
@button.buttonAndHandler(u'Done', name='done')
def handleDone(self, action):
    self.request.response.redirect(absoluteURL(self.context, self.request))

Mais attendez ! En créant ce bouton de la sorte, vous surchargez la liste des boutons déclarés dans la classe form.EditForm. Pour empêcher cela, vous devez étendre la classe form.EditForm en ajoutant la clause form.extends(form.EditForm) juste après la déclaration de la classe ContactEditForm. Vous devriez alors obtenir une fenêtre d'édition qui ressemble à ça :

images/EditFormScreenShot.png

3.6   Compléter l'application avec une page d'accueil

Avant d'attaquer l'habillage, vous allez construire une page supplémentaire pour compléter cette application. Cette nouvelle page sera la page d'accueil principale et disposera d'un lien vers le formulaire de création d'un nouveau contact, et de liens vers chacun des contacts existants. Cela peut être fait simplement dans un gabarit (template), dans zcontact/browser/frontpage.pt :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<h3>Welcome to ZContact</h3>
<p>Please tell me what you would like to do:</p>
<ul>
  <li><a href="@@addContact.html">Add a Contact</a></li>
  <li>Look at contacts:
    <ul>
      <li tal:repeat="contact context/values">
      <a tal:attributes="href contact/@@absolute_url"
         tal:content="string:${contact/lastName}, ${contact/firstName}">Last, First</a>
      </li>
  </ul>
  </li>
</ul>

Pour qu'elle s'affiche comme page d'accueil de votre application, vous devez l'inscrire en ZCML pour l'interface IRootFolder. Ajoutez dans le fichier zcontact/browser/configure.zcml :

1
2
3
4
5
6
7
<page
    name="index.html"
    for="zope.app.folder.interfaces.IRootFolder"
    permission="zope.Public"
    layer="zcontact.layer.IZContactBrowserLayer"
    template="frontpage.pt"
    />

Redémarrez le serveur ; en chargeant l'URL http://localhost:8080/++skin++ZContact/, vous devriez obtenir quelque chose qui ressemble à ceci :

images/FrontPageScreenShot.png

Vous voilà maintenant en place avec une application qui fonctionne de façon décente, vous devriez être prêt à jeter un oeil à d'autres paquets z3c.*.

4   Habillage avec z3c.layout, z3c.pagelet et z3c.template

Dans ce chapitre, vous allez apprendre à créer un joli habillage pour votre application. Dans un environnement Zope 3 « classique », on se sert de macros qui sont ensuite appelées dans chaque gabarit. Typiquement, ces macros sont définies dans une vue appelée standard_macros. Avec les composants z3c.*, par contre, les gabarits deviennent complètement insensibles à toute macro dans laquelle ils seraient placés.

4.1   Ajouter les dépendances

Commencez par le commencement, vous devez tout d'abord inclure la configuration des nouvelles dépendances liées à cet habillage. Ouvrez le fichier src/zcontact/configure.zcml et ajoutez <include package="z3c.pagelet" file="meta.zcml" /> vers le début du fichier après l'inclusion des autres fichiers « metas ». Ceci vous permettra de disposer de la nouvelle balise ZCML z3c:pagelet. Plus loin dans ce même fichier, vous pouvez ajouter deux inclusions supplémentaires :

1
2
<include package="z3c.pagelet" />
<include package="zope.contentprovider" />

4.2   Créer une mise en page (layout)

Voilà la partie intéressante. Vous allez créer ce qu'on appelle une mise en page (layout), qui est assez similaire à une macro mais en beaucoup plus simple. Plutôt que de créer une macro compliquée que chaque page doit utiliser (via les syntaxes « use-macro » et « fill-slot »), vous allez simplement créer une page quelconque avec une balise particulière destinée à accueillir le contenu effectif des pages. Vous pouvez définir des mises en pages différentes pour différents habillages, voire pour différentes interfaces d'objets. Ouvrez le fichier src/zcontact/layout.pt et faites en sorte qu'il ressemble à quelque chose comme ceci (laissez vous aller, faites parler votre créativité) :

 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
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <title>ZContact</title>
    <style type="text/css">
      <!--
       * {
         margin: 0;
         padding: 0;
       }
       body {
         padding: 1em;
       }
       #content{
         border: 1px solid #999;
         padding: 1em;
         background: #eee;
       }
      -->
    </style>
  </head>
  <body>
    <h1>ZContact</h1>

    <!-- THE MAGIC HAPPENS IN THE LINE BELOW -->
    <div id="content"
         tal:content="structure provider:pagelet">Page Content</div>

    <i>ZContact Tutorial Application</i>
  </body>
</html>

Remarquez plus particulièrement les lignes proches de la fin du fichier qui indiquent :

1
2
3
<!-- THE MAGIC HAPPENS IN THE LINE BELOW -->
<div id="content"
     tal:content="structure provider:pagelet">Page Content</div>

Comme vous pouvez vous en douter, c'est ici que la magie opère. La partie provider:pagelet demande à Zope de rechercher un producteur de contenu appelé pagelet, qui via z3c.pagelet s'avèrera être le contenu de la page que vous recherchez, qu'il s'agisse d'un formulaire ou d'un autre gabarit. Donc tout cela est assez sympathique, mais vous devez inscrire cette mise en page pour votre habillage en ZCML. Ouvrez donc le fichier zcontact/skin.zcml et ajoutez les directives suivantes, qui normalement sont assez claires :

1
2
3
4
5
<z3c:layout
    for="*"
    layer="zcontact.layer.IZContactBrowserLayer"
    template="layout.pt"
    />

Aussi, n'oubliez pas d'ajouter l'espace de noms XML « z3c » dans la balise « configure », avec xmlns:z3c="http://namespaces.zope.org/z3c".

4.3   Utiliser les mises en page avec les pagelets (et les gabarits)

Maintenant que la mise en page est faite, vous devez modifier quelque peu vos pages existantes pour qu'elles utilisent ces nouveaux modèles de présentation. La première étape est d'ouvrir le fichier zcontact/browser/configure.zcml et d'ajouter l'espace de noms XML "z3c" dans la balise configure : xmlns:z3c="http://namespaces.zope.org/z3c". Maintenant, partout où vous utilisiez une balise page, remplacez-la simplement par z3c.pagelet, sans rien changer d'autre. Et 90% du travail sera fait. Une particularité des pagelets qui les rend vraiment différentes des pages classiques est qu'elles n'ont rien à voir avec les gabarits de pages. La directive pagelet n'accepte PAS d'attribut template et requiert obligatoirement un attribut class. Pour la plupart des pages utilisées jusqu'ici, qui n'ont pour contenu que des formulaires générés automatiquement, ce n'est pas un problème. Pour la page d'accueil par contre, il faut bien un gabarit spécifique.

Commencez donc par remplacer la dernière directive destinée à la page d'accueil par ce qui suit :

1
2
3
4
5
6
7
<z3c:pagelet
    name="index.html"
    for="zope.app.folder.interfaces.IRootFolder"
    permission="zope.Public"
    layer="zcontact.layer.IZContactBrowserLayer"
    class=".contact.FrontPage"
    />

L'attribut template a donc été remplacé par un attribut class qui pointe sur la classe FrontPage qu'il vous reste à écrire. C'est la dernière étape pour achever cette conversion de l'habillage. Ouvrez donc le fichier zcontact/browser/contact.py et ajoutez ceci à la fin :

1
2
class FrontPage(BrowserPagelet):
    """Pagelet for the front page."""

en plus de from z3c.pagelet.browser import BrowserPagelet au début. Vous devriez alors vous dire, « qu'est-il advenu de mon gabarit, et cette classe semble bien inutile. » On pourrait le penser mais cette classe en héritant de BrowserPagelet fait déjà quelque chose. La classe BrowserPagelet fournit en effet une méthode « __call__ » spéciale, qui utilise une recherche d'adaptateur pour retrouver... le gabarit. Vous devez vous dire, « Une recherche d'adaptateur pour retrouver un gabarit ? Est-ce que cela signifie que je peux utiliser différents gabarits pour le même pagelet ? » La réponse est un « Oui » retentissant ! Vous pouvez en effet soit indiquer le gabarit du pagelet dans sa classe (en utilisant template = ViewPageTemplateFile('frontpage.pt')), soit inscrire un gabarit en ZCML pour un habillage particulier. Pour la démonstration, vous allez utiliser la seconde méthode. Retournez donc au fichier zcontact/browser/configure.zcml et ajoutez cette directive juste après celle liée au pagelet de la page d'accueil :

1
2
3
4
5
<z3c:template
    template="frontpage.pt"
    for=".contact.FrontPage"
    layer="zcontact.layer.IZContactBrowserLayer"
    />

4.4   Utiliser des mises en page dans les formulaires

Il reste encore une mise en garde avant que votre application ne soit toute scintillante. Le paquet z3c.form a été conçu d'une façon qui permette son utilisation en dehors de ces notions de mises en page et de pagelets (ce qui est une bonne chose). Heureusement, le paquet z3c.formui fournit un support pour les mises en page dans son propre module de prise en charge des formulaires. Pour convertir vos formulaires au mécanisme des modèles de mise en page, vous avez simplement à remplacer quelques imports de façon telle qu'au lieu d'avoir from z3c.form import form, vous ayez from z3c.formui import form. Les imports en tête du fichier zcontact/browser/contact.py devraient maintenant ressembler à quelque chose comme ça :

1
2
3
4
5
6
7
8
9
from z3c.form import field, button
from z3c.form.interfaces import DISPLAY_MODE
from z3c.formui import form
from z3c.pagelet.browser import BrowserPagelet
from zope.traversing.browser.absoluteurl import absoluteURL
from zope.traversing.api import getParent, getName

from zcontact import interfaces
from zcontact.contact import Contact

Ceci conclut la transformation de votre habillage, et vous devriez maintenant pouvoir redémarrer le serveur et profiter d'un magnifique habillage (sauf si vous avez honteusement copié mes fichiers CSS !). Voici quelques copies d'écran avec les nouvelles pages :

images/skinFrontPageScreenShot.png images/skinAddFormScreenShot.png

5   Menus de navigation avec z3c.menu et les viewlets

À ce stade, il est assez difficile de naviguer entre les pages de votre application. Ce serait vraiment bien d'avoir un ensemble de liens qui apparaissent sur toutes les pages pour naviguer entre la page d'accueil et le formulaire d'ajout d'un nouveau contact. Vous ne pouvez pas simplement positionner des liens en dur dans le modèle car vous ne savez pas quelle sera l'URL utilisée pour accéder au serveur, et les chemins d'accès relatifs peuvent être différents en fonction de la page que vous consultez. C'est alors l'intérêt d'un paquet comme z3c.menu.

z3c.menu est un paquet minimaliste qui fournit simplement quelques classes assistantes pour créer des menus. Sa puissance réelle provient d'un paquet de base de Zope, zope.viewlet. Voilà l'idée : au lieu de travailler avec les anciens menus de Zope, qui sont assez inflexibles, vous allez utiliser les viewlets pour définir plus facilement quand et comment vos menus vont s'afficher. Votre menu de navigation sera un gestionnaire de viewlets, et chaque lien dans ce menu sera un viewlet. Si vous n'avez pas encore travaillé avec les viewlets auparavent, je vais essayer de vous expliquer ce dont il s'agit.

5.1   Définir un gestionnaire de viewlets

Qu'est-ce donc qu'un gestionnaire de viewlets ? Ce gestionnaire est censé représenter une certaine région d'une page web qui doit contenir n'importe quel nombre de « morceaux de contenu » générés dynamiquement. Un exemple simple est la zone, dans beaucoup de blogs, qui contient la photo du blogger, une courte description du blog, une liste des articles récents, un petit calendrier et peut-être une liste de marqueurs. En termes Zope, vous pouvez considérer chacun de ces éléments comme un viewlet (pensez à une « mini-vue »), qui sont tous assemblés dans un gestionnaire de contenu unique.

Vous allez donc créer ce gestionnaire de contenu. Commencez par écrire le fichier src/zcontact/skin.py et ajoutez le code suivant :

1
2
3
4
5
6
7
8
from zope.viewlet.interfaces import IViewletManager
from zope.viewlet.manager import WeightOrderedViewletManager

class INavigationMenu(IViewletManager):
    """Navigation Menu Viewlet Manager."""

class NavigationMenu(WeightOrderedViewletManager):
    zope.interface.implements(INavigationMenu)

Quand vous créez des viewlets, ils sont inscrits pour un gestionnaire particulier en fonction d'une interface. Il s'agit donc ici de commencer par créer cette interface appelée INavigationMenu, juste pour le menu de navigation, qui hérite de IViewletManager (lignes 4-5). Ensuite, on crée l'implémentation de cette interface (lignes 7-8) ; la classe WeightOrderedViewletManager peut trier ses viewlets en fonction d'un poids donné, ce qui peut être utile pour un menu de navigation.

Il faut ensuite inscrire l'instance du ViewletManager en ZCML, afin qu'il puisse être rendu depuis un gabarit de page. Ouvrez donc le fichier src/zcontact/skin.zcml et ajoutez la directive suivante :

1
2
3
4
5
6
7
<browser:viewletManager
    name="INavigationMenu"
    provides="zcontact.skin.INavigationMenu"
    class="zcontact.skin.NavigationMenu"
    layer="zcontact.layer.IZContactBrowserLayer"
    permission="zope.Public"
    />

Ne pas oublier d'ajouter xmlns:browser="http://namespaces.zope.org/browser" dans la balise configure pour prendre en charge l'espace de noms browser.

Vous avez maintenant un gestionnaire de viewlets qui représente la région où seront affichés les liens de navigation. Mais encore faut-il l'intégrer dans le gabarit de votre habillage, ce que vous pouvez faire en modifiant le fichier src/zcontact/layout.pt. Vous pouvez placer ce bout de code n'importe où dans le gabarit, là où vous voulez que le menu apparaisse. Par exemple, sous l'en-tête « ZContact ».

1
<div tal:content="structure provider:INavigationMenu">Navigation Menu</div>

5.2   Ajouter des viewlets

Maintenant que vous avez paramétré votre gestionnaire de menu, il reste à lui ajouter des viewlets, les entrées du menu. Tout ce que vous avez à faire pour ces viewlets est une inscription ZCML au même endroit que là où vous avez fait l'inscription des pagelets sur lesquels vont pointer ces entrées de menus. Ouvrez donc le fichier zcontact/browser/configure.zcml et ajoutez ces deux balises ZCML :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<viewlet
    name="Add Contact"
    viewURL="@@addContact.html"
    for="*"
    manager="zcontact.skin.INavigationMenu"
    class="z3c.menu.simple.menu.GlobalMenuItem"
    permission="zope.Public"
    layer="zcontact.layer.IZContactBrowserLayer"
    weight="2"
    />

<viewlet
    name="Contact List"
    viewURL="@@index.html"
    for="*"
    manager="zcontact.skin.INavigationMenu"
    class="z3c.menu.simple.menu.GlobalMenuItem"
    permission="zope.Public"
    layer="zcontact.layer.IZContactBrowserLayer"
    weight="1"
    />

L'attribut « nom » indique le libellé attribué au lien et l'attribut « viewURL » indique l'URL relative depuis la racine du site vers la vue en question. Le gestionnaire de viewlets est positionné sur l'interface INavigationMenu que vous avez créé dans la section précédente. Enfin, on peut voir la première utilisation de z3c.menu avec l'utilisation de la classe GlobalMenuItem pour le viewlet. Cette classe détermine l'URL de base en fonction du contexte en cours et la concatène avec l'attribut « viewURL ».

Une fois ceci terminé, vous pouvez redémarrer votre serveur et vérifier qu'un nouveau menu de navigation qui fonctionne parfaitement s'affiche désormais sur chaque page de votre application.

Comme d'habitude, une petite copie d'écran :

images/menuScreenShot.png

Merci Thierry

Posté par jpcw2002 le 25/02/2008 11:16
Peut-on dire on en veut encore ?
Aidez l'AfPy
Dernières news AFPY
Les 6 dernières news
Python WAW 2010 : Semaine 11
21/03/2010 06:05
Python WAW 2010 : Semaine 10
14/03/2010 06:05
Solution Linux 2010
14/03/2010 06:01
Python WAW 2010 : Semaine 9
07/03/2010 00:00
Python WAW 2010 : Semaine 8
28/02/2010 01:00
Python WAW 2010 : Semaine 7
21/02/2010 00:00