Pour 100 lignes, t'as un site web, un vrai !

Ce site est simple, même simpliste (un habillage CSS serait le bienvenu), mais propose les fonctions de bases d'un site web :
  • Affichage du titre de la page
  • Affichage de l'URL et de la date de mise à jour (potentiellement tout autre donnée de ce type : copyright et autres mentions légales)
  • Navigation ascendante (breadcrumb), descendante (menu local), locale (articles dans la page)
  • Plan du site
  • Recherche sur le site
  • Gestion de la sécurité : la navigation, le plan, et la recherche tiennent compte d'éventuelles restrictions d'accès
L'écriture du code pourrait être beaucoup plus compacte ; ici on a voulu rester pédagogique : par exemple, l'écriture des tags "dtml-var" différencie clairement les appels aux objets des expressions Python qui sont notées entres "cotes".

Structure

Les pages sont structurées très classiquement (en-tête / corps en 2 colonnes / pied). La configuration "standard" est la suivante : chaque rubrique forme une page, composée par les articles entreposés à ce niveau.
On peut créer autant de rubriques que l'on veut ; les rubriques "Plan" et "Recherche" proposent des interfaces évidemment spécifiques.

Le site isole la zone de paramétrage et le site proprement dit :

  • la racine globale stocke les codes définissant la logique (scripts, méthodes) ainsi qu'un ZCatalog ;
  • la racine contient aussi le sous-répertoire "site" qui contient les rubriques ;
  • en desous de "site", les rubriques "plan" et "recherche" abritent des versions surchargés de la méthode d'affichage du contenu.
Les mécanismes d'acquisition garantissent l'homogénéité de l'ensemble des rubriques, tout du long de l'arborescence. Les rédacteurs ne risquent pas de modifier malencontreusement une fonction d'affichage ; au contraire, il leur est possible de surcharger localement la présentation.

Les systèmes de navigation utilisés permettent de restreindre l'accès aux des rubriques sans que cela ne perturbe les liens (les documents non-accessibles ne sont jamais "cliquables").

Objets manipulés

Ce site utilise 6 types d'objets Zope :
  • 10 "DTML Method", pour structurer la présentation
  • 1 "Python Scripts", pour réaliser certains traitements logiques (navigation)
  • 1 "ZCatalog", pour indexer et rechercher les contenus
  • des "DTML Document", pour présenter les articles (autant que d'articles)
  • des "Folder", au moins 4, pour structurer le site.

DTML

Le DTML est un langage à balises : <tag>quelque chose</tag>

Le site fait usage des balises DTML suivantes :

  • "dtml-var" : pour "rendre" des objets, pour "évaluer" le résultat d'expressions Python
  • "dtml-if" ("dtml-elif" "dtml-else") : pour effectuer du traitement conditionnel (présentation)
  • "dtml-in" : pour traiter des listes
  • "dtml-with" : pour surcharger l'espace de noms courant
  • "dtml-tree" : pour afficher le plan du site
  • "dtml-try ("dtml-except" "dtml-finally") : pour protéger d'une exception éventuelle
Le parcours des listes (et des arbres) utilise le modificateur "skip_unauthorized" qui permet de lever les exceptions liées aux restrictions d'accès.

Fonctions et expressions

Plusieurs fonctions et expressions Python sont utilisées.

Certaines font directement référence à l'objet placé au sommet de l'espace de nom :

  • title_or_id : retourne le titre ou l'identifiant
  • absolute_url : retourne l'URL absolue depuis la racine
  • bobo_base_modification_time : date de mise à jour (ancien et conservé)
  • objectValues : liste des objets contenus (s'applique aux objets conteneurs)
  • aq_parent : place le conteneur de l'objet au sommet de l'espace de noms
  • getId : retourne l'identifiant
  • getObject : retourne l'objet lui-même
  • sequence-item : l'objet courant dans une boucle "dtml-in"
Les variables globales REQUEST (la requète HTPP, sous forme de dictionnaire Python) et PARENTS (les "id" des conteneurs successifs de l'objet, sous forme de liste Python) sont fournies par Zope.

Sécurité

La gestion de la sécurité permet ici de créer des rubriques en accès réservé sans que celles-ci soient, via le plan, la navigation, ou la recherche ... proposées à l'utilisateur dépourvu des droits nécessaires.

On utilise une protection implicite au sein du parcours des listes, à l'aide du modificateur "skip_unauthorized"

  • si l'utilisateur dispose de la permission adéquate, l'objet est listé
  • l'objet est purement ignoré sinon, sans risque de renvoi d'une banière de login et d'une erreur "page non autorisée"
  • . Zope propose bien sûr des mécanismes de plus bas niveau qui permettent un contrôle particulièrement fin des droits d'accès (permissions élémentaires, rôle ou profil de permissions, rôle local). Ce système est utilisé dans la page de recherche : les ressources protégées sont listées mais ne sont pas atteignables.

    Le code (validé>

    DTML Method : index_html
    <dtml-var header_html>
    <dtml-var body_html>
    <dtml-var footer_html>
    
    Il fallait y penser : décrire logiquement le contenu d'une page web, aggréger les différents éléments, supporter l'enchassement d'éléments. Après, il n'y a plus qu'à définir une correspondance entre les éléments, stockés dans une base de donnée orientée objet, et les URLs ... Voilà l'un des aspects les plus immédiats de Zope pour un concepteur web.
    DTML Method : header_html
    <html>
      <head><title><dtml-var "title_or_id()"></title></head>
      <body>
    
    Vous avez compris l'aggrégation ? la page HTML va se construire peu à peu via la suite des ordres DTML et l'enchassement des éléments ...
    DTML Method : footer_html
    <p><table width="100% cellpadding="2">
      <tr align="center"><td width="50%"><dtml-var "absolute_url()"></td>
      <td><dtml-var "bobobase_modification_time().strftime('%d/%m/%Y')"></td>
      </tr></table>
      </body>
    </html>
    
    Evidement, on termine ici la page HTML.

    L'URL de la page courante et sa date de mise à jour (ici l'objet considéré est le Folder courant) sont automatiquement fournies.
    Mais quelle facétie a poussé les concepteurs à conserver cette fonction ancestrale et au nom barbare ("bobo" est l'ancêtre de Zope ...) ?

    DTML Method : body_html
    <table width="100%" cellpadding="5">
      <tr><td width="25%" valign="top"><small><dtml-var page_menu_html></small></td>
      <td><small>Vous êtes ici : <dtml-var breadcrumb_py></small>
      <p><dtml-var page_content></td></tr>
    </table>
    
    Et voilà pour l'enchassement ! Le menu, la navigation, les contenus : tout est intégré dans la page !
    Ici, on récupère très simplement la navigation ascendante (les breadcrumbs) à l'aide d'un script Python.
    DTML Method : page_menu_html
    Rubriques :
    <dtml-in "objectValues(['Folder'])" skip_unauthorized>
      <li><a href="<dtml-var absolute_url>"><dtml-var "title_or_id()"></a></li>
    </dtml-in><p>
    Articles :
    <dtml-in "objectValues(['DTML Document'])">
      <li><a href="#<dtml-var "getId()">"><dtml-var "title_or_id()"></a></li>
    </dtml-in>
    
    Deux listes sont parcourues, elle sont obtenues en listant le contenu de la rubrique sur tel ou tel type d'objet, répertoires pour l'une (sous-rubriques), documents pour l'autre (articles).

    Nous avons besoin de contrôler les permissions d'accès avant de proposer la liste des sous-rubriques ; c'est immédiat avec le filtre "skip_unauthorized" particulièrement explicite.

    Python Script : breadcrumb_py
    links=[]
    for parent in context.REQUEST.PARENTS:
        links.insert(0, """<a href="%s">%s</a>""" % (parent.absolute_url(), parent.getId()))
        if parent.getId() == "site":
            break
    return " / ".join(links)
    
    Un peu de calcul et un peu de mise en forme ; on accède à la variable contextuelle PARENTS pour récupérer toute la hiérarchie supérieure.
    DTML Method : page_content_html
    <h1><dtml-var "title_or_id()"></h1>
    <dtml-in "objectValues(['DTML Document'])" sort="id">
      <a name="<dtml-var "getId()">"></a>
      <h2><dtml-var "title_or_id()"></h2>
      <dtml-var sequence-item>
    </dtml-in>
    
    Construction de la page : les documents disponibles sont chargés par ordre alphabétique ; on positionne l'ancre nécessaire à la gestion de la navigation intrapage.
    DTML Method : page_content_html pour le Folder 'plan'
    <h1><dtml-var "title_or_id()"></h1>
    <a href="&dtml-URL0;?expand_all=1"> Tout développer </a><br>
    <a href="&dtml-URL0;?collapse_all=1"> Tout réduire </a>
    <p>
    <dtml-tree site skip_unauthorized>
        <a href="<dtml-var "absolute_url()">"><dtml-var "title_or_id()"></a>
    </dtml-tree>
    
    Une fonctionnalités les plus époustouflantes ! le plan dynamique se monte avec un seul élément DTML ; comme on n'est pas radin, mais plutôt soigneux, ici encore on va vérifier que le visiteur est bien autorisé avant de lui proposer la navigation : le miracle s'accomplit avec l'invocation du filtre "skip_unauthorized".
    DTML Method : page_content_html pour le Folder 'recherche'
    <h1><dtml-var "title_or_id()"></h1>
    <dtml-var search_param_html>
    <p>
    <dtml-var search_result_html>
    
    Un formulaire pour la saisie des paramètres de recherche, et une fonction d'affichage des résultats.
    DTML Method : search_param_html
    <form action="" method="get">
    <input type="text" name="search_string" 
    size="40" value="<dtml-var search_string missing="">" ></input>
    <input type="submit" value="Rechercher"></input></td>
    </form>
    
    Un formulaire des plus élémentaires, mon cher Watson ! Ici aussi, Zope permet des contrôles de typage, de validité des champs, etc.
    DTML Method : search_result_html
    <dtml-if search_string>
     <dtml-in "catalog.searchResults({'PrincipiaSearchSource':search_string})" 
               sort="bobobase_modification_time">
     <dtml-try>
         <dtml-if "getObject()">
           <dtml-with getObject>
           <dtml-if "_.SecurityGetUser().has_permission('View', this())">
           <a href="<dtml-with aq_parent><dtml-var "absolute_url()"></dtml-with>#<dtml-var "getId()">">
           <dtml-var "title_or_id()"></a>
           <dtml-else><dtml-var "title_or_id()"></dtml-if>
           <br></dtml-with>
         </dtml-if>
     <dtml-except>
     </dtml-try>
     </dtml-in>
    <dtml-else>
        <ul><li>rechercher</li>
        <li>chercher AND trouver</li>
        <li>trouv* OR trouver</li></ul>
    </dtml-if>
    
    On invoque la ZCatalog via les index de type "Plein Texte" ; les paramètres du formulaire sont implicitement transmis et donc immédiatement récupérables dans le code.

    On procède aussi à un test de sécurité, cette fois-ci en testant explicitement une permission de l'utilisateur courant sur l'objet référencé par l'index retourné. Si les droits sont insuffisants, on ne fournira pas la navigation.