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.
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
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).
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:
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.
<dtml-comment> This is a comment. </dtml-comment>
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.
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>
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.
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.)
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)">
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.
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.
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>
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"> )
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
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')">
C'est presque pareil : la différence avec les documents DTML est subtile et m'échappe.
Respectivement : un répertoire, un fichier quelconque (a priori, un fichier HTML), une image.
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).
À 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.
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>
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.
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.
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');
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.
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"> ...
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.
Permet d'intercaler un cache (par exemple squid) entre le client et le serveur.
Idem, mais en utilisant la mémoire à la place de squid (probablement une mauvaise idée).
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.
Pour indexer un site, i.e., c'est un moteur de recherche local. (Pas lu)
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.
A FAIRE
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>
On peut afficher le contenu de ces variables ainsi
<dtml-var REQUEST>
L'objet courrant (celui qui est en train d'être exécuté)
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.
La réponse envoyée au client (avec un en-tête, des cookies, etc.)
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).
On peut faire exactement la même chose pour appeler des méthodes depuis Python ou Perl.
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.
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">
<dtml-if expr="_.has_key('sort') and sort=='date'">
<dtml-in expr="objectValues('File')" sort="bobobase_modification_time">
<dtml-call expr="RESPONSE.setHeader('content-type', 'text/xml')">
<dtml-if expr="_.len(PARENTS) > 2">
(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)
Ne pas confondre les deux types de guillemets " et ' (Eh non, ils ne sont pas interchangeables).
<dtml-sqlvar pseudo type="string">
(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.
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>
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>
Aucun (i.e., Zope va prendre celui qui est dans le répertoire racine.
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>
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>
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>
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))
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"> )
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>
Maintenant, il faut chercher les erreurs... Les messages d'erreur sont particulièrement obscurs.
Je n'y comprends rien. On recommence tout.
Attention, ce qui suit ne marche toujours pas. C'est la liste des « erreurs » que j'ai commises.
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.
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">
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.
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.
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...
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 »...
Maintenant, il n'y a plus qu'à ajouter une routine d'affichage des fiches.
display
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)
Plus standard, plus rapide, mais pas d'interface Web. Pour les programmeurs, c'est l'idéal, mais pour les autres...
A regarder. Propose aussi une interface Web.
C'est peut-être très bien, mais la documentation est très mal faite.
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