ZOPE

Installation
Navigation
DTML
Les différents types
Les différents types : DTML document
Les différents types : DTML method
Les différents types : folder, file, image
Les différents types : External method
Les différents types : Mail host
Les différents types : Z SQL Method
Les différents types : Z Gadfly Database Connection
Les différents types : User Folder
Les différents types : Script (Python)
Les différents types : Script (Perl)
Les différents types : page template
Les différents types : Version
Les différents types : Accelerated HTTP Cache Manager
Les différents types : RAM Cache
Les différents types : Virtual Monster, Site Root, Set Access Rule
Les différents types : ZCatalog
ZEO
Sessions
Variables
Objets
Appel d'une méthode sur un objet
Méthodes
Remarques diverses
Application : premier essai
Application : deuxième essai
Concurrents
Moralité

Zope est un logiciel de création et de gestion de site Web, centré sur Python (bien qu'on puisse utiliser Perl), très semblable à Apache+mod_perl+HTML::Mason, avec la particularité supplémentaire qu'on peut tout faire à distance, à l'aide d'un navigateur.

Installation

Une fois n'est pas coutume, j'installe une version binaire.

  % ./install

  ------------------------------------------------------------------------------
  Compiling python modules
  ------------------------------------------------------------------------------
  ------------------------------------------------------------------------------
  creating default inituser file
  Note:
          The initial user name and password are 'admin'
          and 'Lh6V4JjT'.

          You can change the name and password through the web
          interface or using the 'zpasswd.py' script.
        
  chmod 0600 /tmp/ZOPE/Zope-2.5.0-linux2-x86/inituser
  chmod 0711 /tmp/ZOPE/Zope-2.5.0-linux2-x86/var
  ------------------------------------------------------------------------------
  setting dir permissions
  ------------------------------------------------------------------------------
  creating default database
  chmod 0600 /tmp/ZOPE/Zope-2.5.0-linux2-x86/var/Data.fs
  ------------------------------------------------------------------------------
  Writing the pcgi resource file (ie cgi script),
  /tmp/ZOPE/Zope-2.5.0-linux2-x86/Zope.cgi
  chmod 0755 /tmp/ZOPE/Zope-2.5.0-linux2-x86/Zope.cgi
  ------------------------------------------------------------------------------
  Creating start script, start
  chmod 0711 /tmp/ZOPE/Zope-2.5.0-linux2-x86/start
  ------------------------------------------------------------------------------
  Creating stop script, stop
  chmod 0711 /tmp/ZOPE/Zope-2.5.0-linux2-x86/stop
  ------------------------------------------------------------------------------

  Done!

On peut maintenant lancer Zope.

  % /tmp/ZOPE/Zope-2.5.0-linux2-x86/start
  ------
  2002-03-01T19:28:50 INFO(0) ZODB Opening database for mounting:
  '142197616_1015010930.970929'
  ------
  2002-03-01T19:28:50 INFO(0) ZODB Mounted database '142197616_1015010930.970929'
  at /temp_folder
  ------
  2002-03-01T19:29:48 INFO(0) Zope New disk product detected, determining if we
  need to fix up any ZClasses.
  ------
  2002-03-01T19:29:48 INFO(0) ZServer HTTP server started at Fri Mar  1 20:29:48 2002
          Hostname: localhost
          Port: 8080
  ------
  2002-03-01T19:29:48 INFO(0) ZServer FTP server started at Fri Mar  1 20:29:48 2002
          Hostname: localhost.localdomain
          Port: 8021
  ------
  2002-03-01T19:29:48 INFO(0) ZServer PCGI Server started at Fri Mar  1 20:29:48 2002
          Unix socket: /tmp/ZOPE/Zope-2.5.0-linux2-x86/var/pcgi.soc

Et on regarde la page

  konqueror http://localhost.localdomain:8080/

*

Ou plutôt la page http://localhost.localdomain:8080/manage

*

*

Navigation

En cliquant un peu partout il est possible de faire du copier-coller, pour copier des objets d'un endroit de l'arborescence vers un autre, ou du couper-coller, pour les déplacer.

*

Le système de gestion de version permet de revenir en arrière et d'annuller toutes les modifications.

*

Il est possible de mettre le site à jour à l'aide d'un simple navigateur Web, par une connection FTP, ou à l'aide de WebDAV (un protocole du même genre que la commande PATCH de HTTP, mais un peut plus élaborée --- mais je n'ai jamais vu de client WebDAV).

Interrogation existentielle

Où sont stockés les objets ? Ils ne sont visiblement pas dans des fichiers...

On peut y accéder par ftp, mais ce ne sont pas des fichiers (on ne les trouve pas avec find).

  ncftp -u admin ftp://localhost:8021/
  NcFTP 3.0.0 beta 18 (February 19, 1999) by Mike Gleason.
  Connecting to 127.0.0.1...                                                      
  Password for user "admin" at 127.0.0.1: 
  ------
  2002-03-14T22:54:28 INFO(0) ZServer Incoming connection from 127.0.0.1:3076
  
  localhost.localdomain FTP server (Medusa Async V1.18 [experimental]) ready.
  ------
  2002-03-14T22:54:28 INFO(0) ZServer Successful login.

  Login successful.
  Logged in to localhost.                                                         
  Current remote directory is /.

  ncftp / > ls -l
  drwxrwx---   1 Zope     Zope                    0 Mar  1 19:37 Control_Panel
  drwxrwx---   1 Zope     Zope                    0 Mar  1 19:29 Examples
  drwxrwx---   1 Zope     Zope                    0 Jan 20  2001 QuickStart
  ----------   1 Zope     Zope                    0 Dec 30  1998 acl_users
  ----------   1 System Processes Zope            0 Mar  1 19:28 browser_id_manager
  -rw-rw----   1 Zope     Zope                   92 Jan 20  2001 index_html
  ----------   1 System Processes Zope            0 Mar  1 19:28 session_data_manager
  -rw-rw----   1 Zope     Zope                 1365 Jan 20  2001 standard_error_message
  -rw-rw----   1 Zope     Zope                   53 Jan 20  2001 standard_html_footer
  -rw-rw----   1 Zope     Zope                   80 Jan 20  2001 standard_html_header
  -rw-rw----   1 System Processes Zope          282 Mar  1 19:29 standard_template.pt
  drwxrwx---   1 Zope     Zope                    0 Mar 14 21:42 temp_folder
  drwxrwx---   1 admin    Zope                    0 Mar 12 16:24 zoo

On peut faire cela à partir d'Xemacs, en chagreant un fichier appelé.

  /admin@localhost#8021:

DTML

Les exemples qui suivent proviennent du tutoriel.

Dans Zope, on ne manipule pas des fichiers HTML, mais essentiellement des « objets » DTML. (Sous Unix, on a l'habitude de dire « tout est un fichier », mais il est plus à la mode de dire « tout est un objet » -- ça veut dire exactement la même chose). Un objet DTML ressemble à du HTML, avec quelques balises particulières en plus.

Commentaires

  <dtml-comment>
    This is a comment.
  </dtml-comment>

index_html

C'est le fichier dtml par défaut, l'équivalent d'index.html ou index.php. Si on demande un URL du genre /foo/, Zope va renvoyer l'objet /foo/index_html.

Créer un nouveau fichier DTML

On commence par créer un nouveau répertoire (« folder »).

Dans ce répertoire, on ajoute un objet DTML.

  Select Type to add
    DTML Method
  Add

Le fichier DTML a la forme suivante

  <dtml-var standard_html_header>
  ... (some HTML code) ...
  <dtml-var standard_html_footer>

On crée des objets DTML standard_html_header et standard_html_footer qui contiennent

  <html>
    <head>
      <title><dtml-var title_or_id></title>
    </head>
    <body bgcolor="#FFFFFF">

et

    </body>
  </html>

Variables (propriétés)

Aller dans « properties » pour définir une nouvelle variable, attachée au document courrant (typiquement, un répertoire : elle sera ainsi accessible depuis les fichiers dtml de ce répertoire). Il y a déjà une variable « title », que l'on peut utiliser par exemple dans standard_html_header.

À un objet, on peut associer des propriétés : par exemple son titre (le nom complet, avec éventuellement une phrase complète, avec plein d'espaces, par opposition à l'identificateur (le nom de fichier)), ou d'autres informations que l'on peut juger utiles.

*

Boucles

La balise <dtml-in> permet d'itérer sur les éléments d'une liste. Dans le corps de la boucle, on récupère l'objet (i.e., le fichier HTML, si c'est un objet DTML, ou la balise <IMG ...>, s'il s'agit d'une image) à l'aide de la balise <dtml-var sequence-item>. Dans cet exemple, photoArchive est le nom d'un répertoire.

  <dtml-var standard_html_header>
  <h2><dtml-var title></h2>
  <dtml-in expr="photoArchive.objectValues()">
    <p>
      <dtml-var sequence-item>
      <a href="<dtml-var absolute_url>"><dtml-var title></a>
      (<dtml-var getSize> bytes)
    </p>
  </dtml-in>
  <dtml-var standard_html_footer>

(Il est possible que le fait que la variable de boucle n'aie pas de nom pose quelques problèmes si jamais on envisage d'imbriquer des boucles.)

Formulaires

On peut récupérer le contenu d'un formulaire et le mettre dans un répertoire.

  <dtml-call
    expr="photoArchive.manage_addImage(
      id='', file=file, title=photo_title)">

Cookies

On définit un cookie de la manière suivante.

  <dtml-call expr="RESPONSE.setCookie('lastVisited', _.DateTime())">

Si le cookie n'a pas de valeur, on peut lui en donner une par défaut.

  <dtml-unless lastVisited>
    <dtml-call "REQUEST.set('lastVisited','1999/01/01')">
  </dtml-unless>

Exemple d'utilisation de la valeur d'un cookie :

  <dtml-in "sightingsFolder.objectValues()">
    <dtml-if expr="bobobase_modification_time() > _.DateTime(lastVisited)">
      <b>New</b>
    </dtml-if>
    <dtml-var sequence-item>
  </dtml-in>

C'est très bizarre comme nom de fonction, bobobase_modification_time, mais c'est bien ça.

Mail

Il est possible d'envoyer un mail à partir d'une page (la variable send_to est définie dans l'onglet "properties" du répertoire courrant). On commance par créer un objet « Mail Host » (qui s'appelle ici « mailhost ») qui contient les coordonnées du serveur SMTP à utiliser. L'objet DTML traitant le contenu du formulaire contenant le message est alors :

  <dtml-sendmail mailhost="mailhost">
  to: <dtml-var send_to>
  from: <dtml-var send_to>
  subject: Elvis Sighting

  Elvis was spotted by <dtml-var author> in <dtml-var location>.
  <dtml-var description>
  </dtml-sendmail>

il est aussi possible d'envoyer un mail composé de plusieures parties MIME.

Bases de données

Mettre un objet « Z Gadfly Database Connection » dans le répertoire courrant et y définir la ou les tables dont on a besoin. Créer un objet « Z SQL Method », intitulé getData, qui utilise la base précédente et lance une commande du genre « SELECT * FROM table ». Créer ensuite un document DTML appelant cet objet (date, location, name, description sont les titres des différentes colonnes de la table).

  <dtml-in getData>
    <p><dtml-var date> -- <dtml-var location></p>
    <p>Reported by <dtml-var name></p>
    <p><dtml-var description></p>
  </dtml-in>

Mise à jour de la base de données par les visiteurs

L'objet DTML traitant le contenu du formulaire appelle un objet ZSQL.

  <dtml-call insertSighting>

Cet objet ZSQL contient

  INSERT INTO elvis_sightings
    VALUES (
      <dtml-sqlvar location type="string">,
      <dtml-sqlvar date type="string">,
      <dtml-sqlvar name type="string">,
      <dtml-sqlvar description type="string"> 
    )

Fin

Le tutoriel s'arrête ici.

Le tutoriel a utilisé les conventions de nommage suivantes.

  fooForm
  fooAction  to process the values submitted in fooForm
  getFoo     Z SQL method (called by foo)
  insertFoo  Z SQL method (called by fooAction)
  foo

Les différents types

*

Les différents types : DTML document

C'est un mélange de HTML et de DTML (déjà vu).

Insertion du contenu d'une variable (il peut s'agir d'un objet du répertoire courrant ou d'un répertoire parent ; d'une propriété de l'objet courrant, du répertoire courrant ou d'un répertoire parent ; d'une variable donnée par l'utilisateur dans un formulaire (POST) ou directement dans l'URL (GET)).

  <dtml-var foo>

Autre syntaxe :

  &dtml-foo;

C'est parfois plus lisible :

  <img  WIDTH=320 HEIGHT=256 SRC="&dtml-foo;" >
  <img  WIDTH=320 HEIGHT=256 SRC="<dtml-var foo>" >

On peut aussi préciser ce qu'il faut faire si la variable n'est pas là.

  <dtml-var foo missing="undefined foo">

Ou encore :

  <dtml-if foo>
    <dtml-var foo>
  <dtml-else>
    <p>Undefined <b>foo</b>.</p>
  </dtml-if>

Si l'objet que l'on veut insérer est un script Python, il peut exiger des paramètres.

  <!-- pas d'argument -->
  <dtml-var foo>

  <!-- pas d'argument -->
  <dtml-var expr="foo()">

  <!-- des arguments -->
  <dtml-var expr="foo(1,2)">

  <!-- ERREUR : Zope insére l'objet sans l'exécuter -->
  <dtml-var expr="foo">

  <!-- ERREUR (idem) -->
  <dtml-var "foo">

Conditionnelles

  <dtml-if expr="1==0">
    ...
  </dtml-if>

  <dtml-if expr="1>0">
    ...
  </dtml-if>

  <dtml-if foo>
    ...
  </dtml-if>

  <dtml-if expr="foo > bar">
    ...
  <dtml-elif expr="foo == bar">
    ...
  <dtml-else>
    ...
  </dtml-if>

Boucles

  <ul>
  <dtml-in expr="objectValues('File')">
    <li><a href="&dtml-adsolute_url;"><dtml-var title_or_id></a></li>
  </dtml-in>
  </ul>

  <table>
  <dtml-in expr="objectValues('File')">
    <dtml-if sequence-even>
      <tr bgcolor="blue">
    <dtml-else>
      <tr bgcolor="red">
    </dtml-if>
    <td><a href="&dtml-adsolute_url;"><dtml-var title_or_id></a></td>
    </tr>
  </dtml-in>
  </table>

On peut trier les éléments sur lesquels on boucle

  <dtml-in expr="objectValues('File')"
           sort="bobobase_modification_time"
           reverse>

À l'intérieur d'une boucle, on a accès aux variables suivantes :

  sequence-item
  sequence-index
  sequence-odd
  sequence-even
  sequence-number (comme sequence-index, mais on commence à 1 et pas à 0)
  sequence-start  (première itération)
  sequence-end    (dernière itération)

On peut fixer la variable d'une boucle (pour imbriquer des boucles).

  <dtml-in prefix="loop" expr="_.range(3)">
    Carré de <dtml-var loop_item> = <dtml-var expr="loop_item*loop_item"><br>
  </dtml-in>

Autre exemple d'imbrication de boucles.

  <dtml-let rows="(1,2,3)" cols="(4,5,6)">
    <dtml-in rows prefix="row">
      <dtml-in cols prefix="col">
        ... <dtml-var row_item> ... <dtml-var expr="row_item*col_item"> ...
      </dtml-in>
    </dtml-in>
  </dtml-let>

On peut aussi utiliser le DTML pour créer des fichiers XML (et pas HTML ni XHTML).

  <dtml-call expr="RESPONSE.setHeader('content-type', 'text/xml')">

Il est possible d'appeler des objets qui n'affichent rien mais qui renvoient une valeur (typiquement, des scripts en Python).

  <dtml-call expr="manage_changeProperties(animalName=REQUEST['animalName'])">

On peut même définir des « variables locales »

  <dtml-let target="'http://foo.bar.com/'">
  <dtml-call expr="RESPONSE.redirect(target)">

Il y a une commande qui permet de représenter un arbre (une arborescence de fichiers, ou d'objets --- tout est un objet). C'est le même genre d'arbre qui se trouve dans la partie gauche de la fenêtre de Zope.

  <dtml-tree>
    <dtml-var getId>
  </dtml-tree>

  <a href="&dtml-URL0;?expand_all=1">Expand all</a>
  <a href="&dtml-URL0;?collapse_all=1">Collapse all</a>
  <dtml-tree>
    <a href="&dtml-absolute_url;"><dtml-var title_or_id></a>
  </dtml-tree>

Comme avec tout langage à la mode, on peut jouer avec des exceptions.

  <dtml-raise type="404">Not found</dtml-raise>

  <dtml-raise NotFound>Not found</dtml-raise>

  <dtml-if expr="...">
    ...
  <dtml-else>
    <dtml-raise type="Problem foo bar">
      <p>Some explanatory text</p>
    </dtml-raise>
  </dtml-if>

  <dtml-try>
    ...
  <dtml-except ZeroDivisionError>
    N/A
  </dtml-try>

  <dtml-try>
    ...
  <dtml-except ExceptionA>
    ...
  <dtml-except ExceptionB>
    ...
  <dtml-except>
     Error type: <dtml-var error_type>
     Error value: <dtml-var error_value>
  </dtml-try>

  <dtml-try>
    ...
  <dtml-finally>
    (this is always done, wether there has been a problem or not)
  </dtml-try>

On peut appeler des scripts

  <dtml-call updateInfo>

  <dtml-call expr="updateInfo(color='brown', pattern='spotted')">

Les différents types : DTML method

C'est presque pareil : la différence avec les documents DTML est subtile et m'échappe.

Les différents types : folder, file, image

Respectivement : un répertoire, un fichier quelconque (a priori, un fichier HTML), une image.

Les différents types : External method

C'est l'équivalent d'un CGI, i.e., un programme (externe) que l'on va lancer, par exemple un script en Python qui ne respecterait pas les restrictions de sécurité usuelles (par exemple en utilisant des bibliothèques non standard).

Les différents types : Mail host

À l'intérieur d'un document DTML, on peut envoyer un mail. Il faut pour cela se connecter à un serveur SMTP, qui doit être défini dans un objet Mail Host.

Les différents types : Z SQL Method

C'est une requête SQL, dont on peut récupérer les résultats depuis un document DTML.

Lorsqu'on la crée, on précise quels sont ses arguments, séparés par des espaces.

  id  name text

On peut préciser des valeurs par défaut

  id name text='no comment'

*

Une requète ressemble à

  INSERT INTO data (id, name, text) 
  VALUES (<dtml-sqlvar id   type='int'>,
          <dtml-sqlvar name type='string'>,
          <dtml-sqlvar text type='string'>)

On remarquera que, comme en Perl avec DBI, il n'est pas nécessaire de mettre de guillemets : Zope s'en charge tout seul.

Autre exemple :

  SELECT * FROM employees
  WHERE salary > 10000

  SELECT * FROM employees
  WHERE <dtml-sqltest salary op=gt type=float>

La syntaxe suivante donne un résultat correct même si certains paramètres ne sont pas spécifiés.

  SELECT * FROM employees
  <dtml-sqlgroup where>
    <dtml-sqltest salary op=gt type=float optionnal>
    <dtml-and>
    <dtml-sqltest first  op=eq type=nb multiple optionnal>
    <dtml-and>
    <dtml-sqltest last   op=eq type=nb multiple optionnal>
  </dtml-sqlgroup>

Utilisation des résultats d'une requète :

  <dtml-in search_all>
    <tr>
      <td><dtml-var id></td>
      <td><dtml-var name></td>
      <td><dtml-var text></td>
    </tr>
  </dtml-in>

Les différents types : Z Gadfly Database Connection

C'est une connection à une base de données : toute méthode SQL doit faire référence à un tel objet.

*

Comme indiqué :

  Note: The Zope Gadfly product is a free Zope database adapter
  intended for demonstration purposes only. It is only suitable for
  learning about Zope SQL Methods. Database operations are performed in
  memory, so it should not be used to create large databases. This
  installation is using a non-optimized version of Gadfly and should not
  be used to assess the performance of either Gadfly or Zope.

Il convient donc d'utiliser une vraie base de données, par exemple MySQL (simpliste, peu fiable, mais rapide). Il faut pour cela installer un morceau supplémentaire (DA, ou "Data Adapter"), spécifique à la base de donnée que l'on va utiliser.

Les différents types : User Folder

C'est un objet qui contient une liste d'utilisateurs.

*

*

Chaque utilisateur a un rôle. Les rôles ont la même fonction que les groupes (/etc/groups) dans un système UNIX : s'il y a bien un utilisateur par personne physique, on ne fixera pas les droits de chacun, mais on regroupera les utilisateurs dans des groupes (ou rôles) et on précisera ce que les membres de ce groupe ont le droit de faire. Comme un utilisateur ne peut avoir qu'un seul rôle (contrairement à ce qui se passe sous UNIX), si une même personne physique a plusieures fonctions, elle correspondra à plusieurs utilisateurs.

Il y a une autre différence avec les groupes sous Unix : sous Unix, un fichier appartient à un groupe, qui a (presque) tous les droits dessus. Sous Zope, les droits d'accès à un objets peuvent varier selon le rôle.

Créer un nouveau rôle, dans le répertoire voulu (il sera utilisable dans tous les sous-répertoires, mais pas au dessus). Pour cela, aller dans l'onglet « security » du répertoire en question et regarder tout en bas.

*

On peut maintenant ajouter un utilisateur : pour cela, aller dans l'objet acl_users du répertoire en question (s'il n'y en a pas, en créer un) et ajouter un utilisateur avec notre nouveau rôle (un utilisateur peut visiblement avoir plusieurs rôles).

*

*

*

On peut maintenant préciser ce que les utilisateurs de ce rôle ont le droit de faire, dans l'onglet « security » du répertoire courrant.

*

En particulier, on prendra garde à la colonne de gauche, qui précise que si le droit en question a été accordé dans un répertoire parent, il reste acquis.

Les différents types : Script (Python)

Un script en Python, pour faire des choses plus compliquées que ce que nous permet DTML.

Pour des raisons de sécurité, les scripts sous soumis à certaines restrictions : pas d'expressions régulières, pas d'eval, pas d'ouverture de fichier, etc.

On peut contourner ces limitations à l'aide de « méthodes extérieures ».

Les scripts commencent par

  ## Script (Python) "fooBar"
  ## parameters=a,b,c
  "This script does so and so"

Appel d'une méthode :

  context.updateInfo(color='brown', pattern='spotted');

Les différents types : Script (Perl)

Heu... Ce type d'objet est sensé exister (d'après le manuel), mais je ne le vois pas dans la liste ???

C'est plus conpliqué. Il faut préalablement installer python (le python qui vient avec Zope ne suffit pas), puis pyperl (un programme en python permettant d'utiliser du Perl au milieu d'autres programmes en Python), et enfin Zope-perl (qui utilise pyperl, et qui contient le type "Script (Perl)" que l'on cherche).

  http://www.zope.org/Wikis/zope-perl/FAQ
  http://downloads.activestate.com/Zope-Perl/

(Je n'ai pas essayé de l'installer.)

Pour des raisons de sécurité, les scripts sous soumis à certaines restrictions, comme l'instruction eval.

On peut contourner ces limitations à l'aide de « méthodes extérieures ».

Les scripts commencent par :

  my $self = shift;

(Il y a souvent une confusion dans le Zope book : ils appellent cette variable tantôt $self, tantôt $context.)

Appel d'une méthode :

  $self->updateInfo(color => 'brown', pattern => 'spotted');

Exemple :

  my $context = shift;
  my $date = $context->getProperty('dilbert_url_date');
  if($date==null or $now-$date>1){
    my $url = $context->get_dilbert_url();
    $context->manage_changeProperties( dilbert_url      => $url,
                                       dilbert_url_time => $now );
  }
  return $context->getProperty('dilbert_url');

  # Le script get_dilbert_url s'écrit similairement, 
  # à l'aide de LWP::Simple.

Les différents types : page template

Le manuel s'attarde très longuement dessus.

Alors que les fichiers DTML sont des morceaux de fichiers HTML, incomplets (le début et la fin d'un même document sont souvent dans deux objets différents) et pas vraiment conformes (on peut mettre des balises DTML à l'intérieur des attrobuts des balises HTML), les Templates sont des fichiers XHTML parfaitement conformes, avec certaines balises supplémentaires, dans leur propre espace de nommage.

Typiquement : un créateur de site Web, qui ne connait rien ni à la programmation, ni même au HTML, utilise un logiciel WYSIWYG pour créer un exemple de page Web. Ensuite, un programmeur (quelqu'un qui sait lire le HTML) édite le fichier et rajoute certaine balises (ou certains attributs), dans l'espace de nomage « tal » (Template Attribute Language).

par exemple, si le graphiste a écrit :

  <title>Page Title</title>

on peut le modifier en

  <title tal:content="here/title">Page Title</title>

ce qui remplacera « Page Title » par la valeur de « here/title ».

Au lieu de remplacer le contenu d'une balise, on peut remplacer du texte (en enlevant la balise, on mettra donc une balise qui n'a aucun effet comme span ou div).

  Voici <span tal:replace="template/title">le titre</span>.

Exemples de « variables » (avant le slash, c'est le nom d'un objet, après, le mon d'une méthode).

  template/title       le titre du template
  request/URL
  user/getUserName
  container/objectIds  liste des Ids des objets dans le même répertoire
  request/cookies/foo  Un cookie « foo ».

Il est possible de faire des boucles (ici, item est le nom que l'on donne à la variable de boucle).

  <table>
    <tr>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
    </tr>
    <tr tal:repeat="item container/objectValues">
      <td tal:content="repeat/item/number">#</td>
      <td tal:content="item/getId">id</td>
      <td tal:content="item/meta_type">type</td>
      <td tal:content="item/title">title</td>
    </tr>
  </table>

On a aussi des conditionnelles.

  <p tal:condition="request/cookies/foo | nothing">
    There is a cookie named foo.
  </p>

On aurait pu en utiliser une dans notre tableau ci-dessus :

  <table tal:condition="container/objectValues>
    ...
  </table>

On peut aussi changer les attributs d'une balise HTML.

  <img  WIDTH=320 HEIGHT=256 SRC="1.gif" 
       tal:attributes="src item/icon">

En cas d'erreur dans un Template, Zope met le message d'erreur dans le fichier lui-même.

Comme les commandes TAL sont dans un espace de nammage qui leur est propre, on peut les utiliser dans un fichier XML (et les editeurs HTML WYSIWYG laisseront ces balises sans y toucher).

  <!DOCTYPE html 
    PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "DTD/xhtml1-transitional.dtd">
  <html xmlns:tal="http://xml.zope.org/namespaces/tal">
  ...

Les différents types : Version

Utile pour modifier le site alors que des clients sont en train de l'utiliser : on peut essayer des changement, commettre des erreurs, les corriger, en prenant tout son temps --- il y aura deux versions du site, celle vue par les visiteurs, et celle sur laquelle on travaille. Quand on est satisfait des modifications, on les valide.

Les différents types : Accelerated HTTP Cache Manager

Permet d'intercaler un cache (par exemple squid) entre le client et le serveur.

*

Les différents types : RAM Cache

Idem, mais en utilisant la mémoire à la place de squid (probablement une mauvaise idée).

Les différents types : Virtual Monster, Site Root, Set Access Rule

Utiles pour créer des serveurs virtuels, i.e., plusieurs sites Web, avec des noms différents (correspondant à une seule adresse IP), sur une même machine.

Les différents types : ZCatalog

Pour indexer un site, i.e., c'est un moteur de recherche local. (Pas lu)

ZEO

Il est possible (pour les sites Web qui comptent plus d'un million de requètes par jour, ou ceux qui veulent une fiabilité accrue), d'utiliser plusieures machines.

Sessions

  A FAIRE

Variables

Les variables auxquelles on a accès sont réparties dans plusieurs espaces de nommage (pour simplifier, un espace de nommage, c'est un objet --- tout est un objet) : on les cherche tout d'abord dans l'« objet DTML client » (en gros, le répertoire courrant), puis ensuite dans l'objet REQUEST. À l'intérieur d'une boucle, on regarde d'abord dans l'espace de nommage de la variable de boucle.

On peut spécifier explicitement un espace de nommage.

  <dtml-var expr="Reptiles.getReptileInfo()">

  <dtml-with Reptiles>
    <dtml-var getReptileInfo>
  </dtml-with>

  <dtml-with expr="REQUEST.form">
    dtml-var id>
  </dtml-with>

Objets

On peut afficher le contenu de ces variables ainsi

  <dtml-var REQUEST>

_

L'objet courrant (celui qui est en train d'être exécuté)

REQUEST

Les variables qui ont été envoyées au script (par GET, POST, mais aussi les cookies). Il y a aussi l'URL (URL ou URL0), l'URL parent (URL1), etc.

RESPONSE

La réponse envoyée au client (avec un en-tête, des cookies, etc.)

Appel d'une méthode sur un objet

URL

On peut appeler une méthode sur un objet directement, en construisant un URL adéquat. Par exemple, dans la situation

  /a/a/a/b/b/b/method
  /a/a/a/c/c/c/object

On peut mancer la méthode sur l'objet en invoquant l'URL

  /a/a/a/b/b/b/c/c/c/object/method

Cela signifie la chose suivante : on se place dans le répertoire /a/a/a/b/b/b/b, on y cherche l'objet c/c/c/objet (comme il n'y en a pas, on cherche dans les répertoires parents, et on finit par le trouver), puis on invoque la méthode method (qui se trouve dans le répertoire).

Scripts

On peut faire exactement la même chose pour appeler des méthodes depuis Python ou Perl.

XML-RPC

On peut aussi les appeler depuis XML-RPC (cela revient à passer les paramètres d'une manière un peu différente), par exemple depuis un script en Perl utilisant le module Frontier::Client.

Méthodes

objectValues

Répertoires fils

  <dtml-in expr="objectValues('Folder')>

Fichiers dans le répertoire courrant

  <dtml-in expr="objectValues('File')>

Idem, triés.

  <dtml-in expr="objectValues('File')" sort="bobobase_modification_time">

getId

absolute_url

title_or_id

has_key

  <dtml-if expr="_.has_key('sort') and sort=='date'">

bobobase_modification_time

  <dtml-in expr="objectValues('File')" sort="bobobase_modification_time">

setHeader

  <dtml-call expr="RESPONSE.setHeader('content-type', 'text/xml')">

len

  <dtml-if expr="_.len(PARENTS) > 2">

divers

(Voir une liste plus complète dans l'appendice B du Zope Book.)

  <dtml-var expr="_.SecurityGetUser().getUserName()">

  <dtml-if expr="SecurityCheckPermissions('Add Documents, Images, and Files', this())">
    ...
  </dtml-if>

  <dtml-call expr="RESPONSE.redirect(target)">

  <dtml-call expr="manage_changeProperties(animalName=REQUEST['animalName'])">

  has_role(roles, object=None)
  getRoles()
  has_permission(permission,object)
  getRolesInContext(object)
  getContentType()
  update_data(data, content_type=None, size=None)
  getSize()
  manage_addFile(id, file, title, preconditionn, content_type)
  manage_addFolder(id, title)
  manage_addImage(id, file, title)
  objectValues(type=None)
  objectIds(type=None)
  title_or_id()
  getId()
  absolute_url()
  this()
  title_and_id()
  getProperty(id)
  hasProperty(id)
  manage_delProperties(ids)
  manage_changeProperties(...)
  manage_addProperty(id, value, type)
  get_header(name)
  setHeader(name, value)
  setCookie(name, value)
  setStatus(status)
  redirect(location)

Remarques diverses

Ne pas confondre les deux types de guillemets " et ' (Eh non, ils ne sont pas interchangeables).

  <dtml-sqlvar pseudo type="string">

Application : premier essai

(Attention, ce qui suit ne marche pas : voir plus loin.)

Nous allons créer une base de données que tout un chacun pourra compléter, sur le modèle de http://crookshanks.free.fr/dvdhk/

Aller dans le répertoire racine (Root Folder)

Créer un répertoire (Folder) "dvdhk"

Aller dans ce répertoire.

index_html

Ajouter un document DTML "index_html" contenant

  <dtml-var standard_html_header>
  <dtml-if title>
    <p>Voici toutes les fiches.</p>
  <dtml-else>
    <p>Voici les résultats de votre requête "<dtml-var title>".</p>
  </dtml-if>
  <dtml-in expr="search_entries">
    <dtml-var display_item>
  </dtml-in>
  <dtml-var standard_html_footer>

standard_html_header

Ajouter un document DTML "standard_html_header" contenant

  <html>
  <head>
  <title>DVDHK</title>
  </head>
  <body>
  <h1>Votre avis sur la qualité des sous-titres anglais des DVDHK</h1>
  <p>
    <a href="index_html">Liste de toutes les fiches</a><br>
    <a href="form_add_html">Ajouter une fiche</a><br>
    <dtml-var form_search>
  </p>
  <hr>

standard_html_footer

Aucun (i.e., Zope va prendre celui qui est dans le répertoire racine.

form_add_html

En Perl, j'aurais fais une boucle...

  <dtml-var standard_html_header>
  <h1>Nouvelle fiche</h1>
  <form action="add_entry" method="POST">
    <table>
      <tr>
        <td>Pseudo</td>
        <td><input type="text" name="pseudo" size="40" value=""/></td>
      </tr>
      <tr>
        <td>Titre</td>
        <td><input type="text" name="titre" size="40" value=""/></td>
      </tr>
      <tr>
         <td>Ref</td>
         <td><input type="text" name="ref" size="40" value="" /></td>
      </tr>
      <tr>
        <td>Marque</td>
        <td><input type="text" name="marque" size="40" value="" /></td>
      </tr>
      <tr>
        <td>Episodes</td>
        <td><input type="text" name="épisodes" size="40" value="" /></td>
      </tr>
      <tr>
        <td>Sous-titres</td>
        <td><input type="text" name="soustitres" size="40" value="" /></td>
      </tr>
    </table>
    <p>
      Commentaires
      <br>
      <input type="textarea" name="commentaires" value="" />
      <br>
      <input type="submit" name="submit" value="Ajouter la fiche"/>
    </p>
  </form>
  <dtml-var standard_html_footer>

form_search

Ajouter un objet form_search, qui affiche le formulaire pour la recherche d'une fiche.

  <form action="index_html" method="POST">
  <input type="submit" name="submit" value="Chercher">
  <input type="text" name="title" size=40 value="">
  </form>

display_item

Ajouter un objet display_item, qui affiche une fiche.

  <table>
    <tr>
      <td>Pseudo</td>
      <td><dtml-var pseudo></td>
    </tr>
    <tr>
      <td>Titre</td>
      <td><dtml-var titre></td>
    </tr>
    <tr>
       <td>Ref</td>
       <td><dtml-var ref></td>
    </tr>
    <tr>
      <td>Marque</td>
      <td><dtml-var marque></td>
    </tr>
    <tr>
      <td>Episodes</td>
      <td><dtml-var episodes></td>
    </tr>
    <tr>
      <td>Sous-titres</td>
      <td><dtml-var soustitres></td>
    </tr>
  </table>

dvdhk_base

Ajouter une base de donnée Gadfly

À partir de l'onglet Test de cette base de données, créer une nouvelle table.

  CREATE TABLE dvdhk (
    id           INTEGER,
    pseudo       VARCHAR(255),
    titre        VARCHAR(255),
    episodes     VARCHAR(255),
    soustitres   VARCHAR(255),
    marque       VARCHAR(255),
    ref          VARCHAR(255),
    commentaires VARCHAR(255))

*

add_entry

Créer une Z SQL méthode add_entry pour ajouter une entrée. Ses arguments sont

  pseudo titre episodes soustitres marque ref commentaires

La requète est :

  INSERT INTO dvdhk 
    (pseudo, titre, episodes, soustitres, marque, ref, commentaires)
  VALUES (
    <dtml-sqlvar pseudo       type="string">,
    <dtml-sqlvar titre        type="string">,
    <dtml-sqlvar soustitres   type="string">,
    <dtml-sqlvar marque       type="string">,
    <dtml-sqlvar ref          type="string">,
    <dtml-sqlvar commentaires type="string"> )

search_entries

Créer une Z SQL méthode search_entries pour faire une recherche et/ou afficher toutes les fiches. Ses arguments sont

  title

Son code est

  SELECT * FROM dvdhk 
  <dtml-sqlgroup where>
    <dtml-sqltest title op=like type=string optionnal>
  </dtml-sqlgroup>

Erreurs

Maintenant, il faut chercher les erreurs... Les messages d'erreur sont particulièrement obscurs.

Je n'y comprends rien. On recommence tout.

Application : deuxième essai

Attention, ce qui suit ne marche toujours pas. C'est la liste des « erreurs » que j'ai commises.

Restons simple

Recommençons tout en faisant les choses plus progressivement. Tout d'abord on crée les fichiers DTML, sans aucun code compliqué, sans manipuler la moindre variable, sans la moindre boucle, sans base de donnée (juste du HTML avec des SSI). (Je crée ces « fichiers » par FTP.)

  index_html (liste des 5 dernières fiches)
  standard_html_header (CSS, liste de liens)
  standard_html_footer (rien)
  digest_html (liste des titres)
  search_html (résultat d'une recherche)
  add_html (formulaire à remplir)

Ensuite, on rajoute une base de données, quelques méthodes SQL.

  dvdhk (base de donnée)
  digest_sql (liste des titres)
  index_sql (liste des 5 dernières fiches)
  search_sql (recherche d'un titre)

Les problèmes commencent.

Problème

Si je lance la commande suivante dans l'onglet « Test » de la base de donnée, j'ai les résultats que j'attend, par contre, si je la met dans un objet ZSQL, il ne me renvoie rien (comme si la base de données était vide ?)...

  SELECT DISTINCT titre FROM dvdhk ORDER BY titre

Même comportement étrange avec la requête plus simple

  SELECT * FROM dvdhk

Par contre l'exemple suivant marche correctement.

  SELECT * FROM dvdhk 
  WHERE titre LIKE <dtml-sqlvar titre type="string">

Autre problème : les droits

Comme j'utilise le même navigateur pour créer le site et pour le tester, je suis reconnu comme un seul utilisateur, qui a tous les droits. D'une part, je ne sais pas si ça marche pour un utilisateur quelconque, d'autre part, je me retrouve parfois avec des fenêtres qu'un utilisateur normal de doit pas voir... Pour le vérifier, devenir anonymous, essayer, redevenir admin, modifier les permissions, redevenir anonymous, etc. C'est particulièrement pénible.

Autre problème

Les pages d'erreur sont parfois un peu étranges. Ainsi, dans l'exemple suivant, je me retrouve avec le fichier standard_html_header qui est inclus deux fois (un peu plus bas, on a un message d'erreur parfaitement compréhensible). Ça n'est pas grave, mais ça ne fait pas sérieux.

*

Autre problème

Les messages d'erreur SQL ne sont pas très clairs.

  Error, exceptions.SyntaxError: unexpected token sequence.near :: 'hk
  \nWHERE titre'*" LIKE '%ess%'" ******************************* current
  state = 57 expects: 'HAVING', 'UNION', 'GROUP', 'VARCHAR', 'DESC',
  'SELECT', 'ORDER', 'WHERE', 'AS', 'EXCEPT', '*', 'IN', 'INTERSECT',
  'FLOAT', '+', '(', ')', '.', '/', ',', '-', 'AND', 'FROM', '*', ';', 'INTEGER',
  'NOT', '>', 'OR', '=', 'BETWEEN', 'ASC', '<', 'VALUES', ('nomatch1',)
  current token = ((-8, 'user_defined_name'), 'LIKE') 

  SQL used:

  SELECT * FROM dvdhk 
  WHERE titre LIKE '%ess%'

J'ai compris ce que ça veut dire : GadFly ne connait pas le mot-clef LIKE...

Autre problème

Quand j'essaye de chercher une fiche, je pars d'un formulaire du genre

  <form action="search_html" method="POST">
    <input type="submit" name="submit" value="Chercher une fiche">
    <input type="text" name="title" size=40 value="">
  </form>

...

Ah, j'ai trouvé : c'était juste une faute de frappe, « title » au lieu de « titre »...

Suite

Maintenant, il n'y a plus qu'à ajouter une routine d'affichage des fiches.

  display

Autre problème

FTP me donne parfois des erreurs (que je ne comprends pas, bien sûr).

  (1) (error/warning) Error in process filter: (ftp-error Opening output file FTP Error: "426 Error creating file." /admin@localhost#8021:/dvdhk2/display)

  (2) (error/warning) Error in process filter: (ftp-error Opening output file FTP Error: "426 Error creating file." /admin@localhost#8021:/dvdhk2/display)

  (3) (error/warning) Error in process filter: (ftp-error Opening output file FTP Error: "426 Error creating file." /admin@localhost#8021:/dvdhk2/display)

  (4) (error/warning) Error in process filter: (ftp-error Opening output file FTP Error: "426 Error creating file." /admin@localhost#8021:/dvdhk2/display)

Concurrents

apache + mod_perl + HTML::Mason (ou HTML::Template)

Plus standard, plus rapide, mais pas d'interface Web. Pour les programmeurs, c'est l'idéal, mais pour les autres...

Roxen

A regarder. Propose aussi une interface Web.

Midgard

C'est peut-être très bien, mais la documentation est très mal faite.

Moralité

Sur le papier, Zope est très simple, très puissant ; dans la pratique, il est un peu lourd (en particulier pour manipuler des « fichier », car ces fichiers ne sont pas des fichiers : il faut soit utiliser un navigateur soit passer par FTP) et la période d'apprentissage n'est pas aussi courte qu'on pouvait l'espérer.

Vincent Zoonekynd
<zoonek@math.jussieu.fr>
latest modification on Tue Apr 2 13:55:34 CEST 2002