Chapter 3: Vue d'ensemble

Vue d'ensemble

Démarrage

Linux
Mac
Windows

web2py est fourni en tant que package binaire pour Windows et Mac OS X. Ils incluent l'interpréteur Python et vous n'avez donc pas besoin de l'installer au préalable. Il y a également une version en code source qui fonctionne sur Windows, Mac, Linux et tout autre système Unix. Le package code source nécessite la pré-installation de Python sur votre système.

web2py ne nécessite aucune installation. Pour débuter, dézippez le fichier zip que vous avez téléchargé en fonction de votre système d'exploitation et exécutez le fichier web2py correspondant.

Sur Unix et Linux (distribution source), exécutez:

python web2py.py

Sur OS X (distribution binaire), exécutez:

open web2py.app

Sur Windows (distribution binaire), exécutez:

web2py.exe

Sur Windows (distribution source), exécutez:

c:/Python27/python.exe web2py.py
Attention, pour exécuter web2py sur Windows depuis les sources vous devez d'abord installer les extensions win32 de Mark Hammond's depuis http://sourceforge.net/projects/pywin32/.

Le programme web2py accepte diverses options sur la ligne de commande expliquées plus loin.

Par défaut, au démarrage, web2py affiche une fenêtre de lancement puis un widget graphique qui vous demandera de définir un mot de passe administrateur unique, l'adresse IP de l'interface réseau à utiliser pour le serveur web, et un numéro de port pour répondre aux requêtes. Par défaut, web2py utilise son serveur web sur 127.0.0.1:8000 (port 8000 sur localhost), mais vous pouvez le démarrer sur n'importe quel IP/port disponibles. Vous pouvez trouver l'adresse IP de votre interface réseau en ouvrant une invite de commande et en tapant ipconfig sur Windows ou ifconfig sur OS X et Linux. A partir de maintenant, nous supposerons que web2py fonctionne sur localhost (127.0.0.1:8000). Utilisez 0.0.0.0:80 pour lancer web2py de manière publique sur n'importe quelle interface réseau.

image

Si vous ne fournissez pas de mot de passe administrateur, l'interface d'administration est désactivée. C'est une mesure de sécurité pour éviter tout risque d'exposer l'interface administrateur publiquement.

L'interface d'administration, admin, est seulement accessible depuis localhost à moins que vous n'exécutiez web2py derrière Apache avec mod_proxy. Si admin détecte un proxy, le cookie de session est défini comme sécurisé et la connexion admin ne fonctionne pas à moins que la communication entre le client et le proxy ait été définie sur HTTPS; c'est une mesure de sécurité. Toutes les communications entre le client et admin doivent toujours être locales et chiffrées; autrement, un attaquant pourrait exécuter une attaque man-in-the-middle ou une attaque de rejeu et exécuter arbitrairement du code sur le serveur.

Une fois que le mot de passe d'administration a été défini, web2py démarre le navigateur web à la page :

http://127.0.0.1:8000/

Si l'ordinateur n'a pas de navigateur par défaut défini, ouvrez un navigateur et entrez l'URL.

image

En cliquant sur "interface d'administration" vous arriverez sur la page d'identification pour l'interface d'administration.

image

Le mot de passe administrateur est le mot de passe que vous avez choisi au démarrage. Notez bien qu'il n'y a qu'un seul administrateur, et de ce fait, un seul mot de passe administrateur. Pour des raisons de sécurité, le développeur se verra demander un nouveau mot de passe à chaque redémarrage de web2py à moins que l'option <recycle> ne soit spécifiée. C'est un mécanisme distinct des authentifications des applications web2py.

Après que l'administrator soit logué sur web2py, le navigateur est redirigé à la page "site".

image

Cette page liste toutes les applications installées et autorise l'administrateur à les gérer. web2py est fourni avec trois applications :

admin
examples
welcome
scaffolding

  • Une application admin, celle que vous utilisez maintenant.
  • Une application examples, avec une documentation en ligne interactive et une copie du site officiel web2py.
  • Une application welcome. C'est le modèle basique pour toute autre application web2py. This is the basic template for any other web2py application. Elle est désignée comme application échafaudage. C'est également l'application qui accueille un utilisateur au démarrage.
appliances

Les applications prêtes à l'emploi sont désignées comme appliances web2py. Vous pouvez télécharger un grand nombre d'appliances disponibles librement depuis [appliances] . Les utilisateurs web2py sont bien entendu encouragés à soumettre leurs propres appliances, que ce soit sous forme open-source ou non (compilées et packagées).

Depuis la page site de l'application admin, vous pouvez exécuter les opérations suivantes :

  • installer une application en remplissant le formulaire en bas à droite de la page. Donnez un nom à l'application, choisissez le fichier contenant une application packagée ou l'URL où l'application est stockée, et cliquez sur "installer".
  • désinstaller une application en cliquant sur le bouton correspondant. Il y a une page de confirmation.
  • créer une nouvelle application en choisissant un nom et en cliquant sur "créer".
  • packager une application pour la distribuer en cliquant sur le bouton correspondant. Une application téléchargée est un fichier tar contenant tout, incluant la base de données. Vous ne devriez pas dézipper cette archive; elle est automatiquement dépackagée par web2py lorsqu'elle est installée avec admin.
  • nettoyer une application de ses fichiers temporaires, telles que les sessions, les erreurs et les fichiers de cache.
  • activer/désactiver chaque application. Lorsqu'une application est désactivée, elle ne peut plus être utilisée de manière distante mais est toujours disponible en localhost. Cela signifie que les applications désactivées peuvent toujours être accessibles derrière un proxy. Une application est désactivée par la création d'un fichier nommé "DISABLED" dans le dossier de l'application. Les utilisateurs qui essaient d'accéder à cette application désactivée recevront une erreur HTTP 503. Vous pouvez utiliser routes_onerror pour personnaliser la page d'erreur.
  • éditer une application.
Lorsque vous créez une nouvelle application en utilisant admin, elle démarre en effectuant un clone de l'application de référence "welcome" avec un "models/db.py" qui créé une base de données SQLite, s'y connecte, instancie Auth, Crud, Service, et les configure. Elle fournit également un "controller/default.py" qui propose les actions "index", "download", "user" pour la gestion des utilisateurs, et "call" pour les services. Par la suite, nous supposons que ces fichiers ont été supprimés; nous créérons les applications sans base.

web2py propose également un assistant, décrit plus tard dans ce chapitre, qui peut écrire un code de référence alternatif pour vous en se basant sur les layouts et les plugins disponibles sur le web et sur la description haut niveau des modèles.

Exemples simples

Dites bonjour

index

Ici, comme exemple, nous créons une simple application web qui affiche le message "Hello from MyApp" à l'utilisateur. Nous appellerons cette application "myapp". Nous ajouterons également un compteur qui compte le nombre de fois où un même utilisateur visite la page.

Vous pouvez créer une nouvelle application simplement en tapant son nom dans le formulaire en haut à droite de la page site dans admin.

image

Après avoir appuyé sur [create], l'application est créée comme copie de l'application pré-construite "welcome".

image

Pour démarrer la nouvelle application, allez sur :

http://127.0.0.1:8000/myapp

Vous avez maintenant une copie de l'application "welcome".

Pour éditer une application, cliquez sur le bouton modifier pour la nouvelle application que vous avez créée.

La page modifier vous dit ce que contient l'application. Toute application web2py possède un ensemble de fichiers, dont la plupart tombe dans l'une de ces six catégories :

  • modèles: décrit la représentation des données.
  • controleurs: décrit la logique de l'application et le workflow.
  • vues: décrit la présentation des données.
  • langues: décrit comment traduire l'application dans d'autres langues.
  • modules: les modules Python qui appartiennent à l'application.
  • fichiers statiques: les images statiques, les fichiers CSS [css-w,css-o,css-school] , les fichiers JavaScript [js-w,js-b] , etc.
  • plugins: groupes de fichiers destinés à fonctionner ensemble.

Tout est soigneusement organisé selon le design pattern Modèle-Vue-Contrôleur. Toute section dans la page modifier correspond à un sous-dossier dans le dossier de l'application.

Notez que le fait de cliquer sur l'entête d'une section bascule l'affichage de son contenu. Les dossiers dans la section des fichiers statiques sont également repliables.

Chaque fichier listé dans la section correspond à un fichier situé physiquement dans le sous-dossier. N'importe quelle opération exécutée sur un fichier via l'interface admin (créer, modifier, supprimer) peut être exécutée directement depuis le shell en utilisant votre éditeur favori.

L'application contient d'autre types de fichiers (base de données, fichiers de session, fichiers d'erreur, etc.), mais ceux-ci ne sont pas listés sur la page modifier car ils ne sont ni créés ni modifiés par l'administrateur; ils sont créés et modifiés par l'application elle-même.

Les contrôleurs contiennent la logique et le workflow de l'application. Toute URL se voit mappée à l'appel d'une fonction dans les contrôleurs (actions). Il y a deux contrôleurs par défaut : "appadmin.py" et "default.py". appadmin fournit l'interface d'administration de la base de données; nous n'en avons pas besoin maintenant. "default.py" est le contrôleur que vous avez besoin de modifier, celui qui est appelé par défaut lorsqu'aucun n'est spécifié dans l'URL. Modifiez la fonction "index" comme suit :

def index():
    return "Hello from MyApp"

Voilà à quoi ressemble l'éditeur en ligne :

image

Enregistrez et retournez à la page edit. Cliquez sur le lien de l'index pour visiter la page juste créée.

Lorsque vous visitez l'URL

http://127.0.0.1:8000/myapp/default/index

l'action index du contrôleur par défaut de l'application myapp est appelée. Elle rnvoie une chaîne de caractères que le navigateur affiche pour nous. Votre page devrait ressembler à cela :

image

Maintenant, modifiez la fonction "index" comme suit :

def index():
    return dict(message="Hello from MyApp")

Depuis la page modifier, éditez également la vue "default/index.html" (le fichier de vue associé à l'action) et remplacez complètement le contenu actuel de ce fichier avec ce qui suit :

<html>
   <head></head>
   <body>
      <h1>{{=message}}</h1>
   </body>
</html>

Désormais, l'action renvoie un dictionnaire définissant un message. Lorsqu'une action retourne un dictionnaire, web2py cherche une vue avec le nom

[controller]/[function].[extension]

et l'exécute. Ici, [extension] est l'extension requise. Si aucune extension n'est spécifiée, ce sera par défaut "html", et c'est ce que nous supposerons ici. Sous cette supposition, la vue est un fichier HTML qui embarque du code Python en utilisant les tags spéciaux {{ }}. En particulier, dans l'exemple, le {{=message}} indique à web2py de remplacer le code tagué avec la valeur de message retournée par l'action. Notez que message ici n'est pas un mot-clé web2py mais est défini dans l'action. Jusqu'ici nous n'avons pas utilisé les mots-clé web2py.

Si web2py ne trouve pas la vue requise, il utilise la vue "generic.html" disponible avec toute application.

Mac Mail
Google Maps
jsonp
Si une extension autre que "html" est spécifiée ("json" par exemple), et que le fichier de vue "[controller]/[function].json" n'est pas trouvé, web2py cherche la vue "generic.json". web2py est fourni avec generic.html, generic.json, generic.jsonp, generic.xml, generic.rss, generic.ics (pour Mac Mail Calendar), generic.map (pourles cartes embarquées Google Maps), et generic.pdf (basé sur fpdf). Ces vues génériques peuvent être modifiées pour chaque application individuellement, et des vues additionnelles peuvent être ajoutées facilement.
Les vues génériques sont un outil de développement. En production, toute action devrait avoir sa propre vue. En fait, par défaut, les vues génériques sont seulement activées depuis localhost.
Vous pouvez également spécifier une vue avec response.view = 'default/something.html'

Vous pourrez en savoir plus sur ce sujet au chapitre 10.

Si vous retournez à "modifier" et que vous cliquez sur index, vous nerrez maintenant la page HTML suivante :

image

Barre d'outil de déboguage

toolbar

Pour des raisons de déboguage vous pouvez insérer

{{=response.toolbar()}}

au code dans une vue et et cela vous affichera les informations utiles, incluant la requête, la réponse et les objets de session, et la liste de toutes les requêtes à la base de données avec leur timing.

C'est parti pour compter

session

Ajoutons maintenant un compteur à cette page qui comptera combien de fois le même utilisateur affiche la page.

web2py suit automatiquement et de manière transparente les visiteurs en utilisant les sessions et les cookies. Pour chaque nouveau visiteur, il créé une session et assigne un unique "session_id". La session est un conteneur de variables stockées côté serveur. L'id unique est envoyé au navigateur via un cookie. Lorsque le visiteur requête une autre page depuis la même application, il renvoie le cookie, qui est récupéré par web2py, et la session correspondante est restaurée.

Pour utiliser la session, modifiez le contrôleur par défaut :

def index():
    if not session.counter:
        session.counter = 1
    else:
        session.counter += 1
    return dict(message="Hello from MyApp", counter=session.counter)

Notez que counter n'est pas un mot-clé web2py mais une session. Nous demandons à web2py de vérifier s'il y a une variable compteur dans la session et, s'il n'y a pas, d'en créer une et de fixer sa valeur à 1. Si le compteur est présent, nous demandons à web2py de l'incrémenter de 1. Finalement nous envoyons la valeur du compteur à la vue.

Un autre moyen plus compact pour coder la même fonction :

def index():
    session.counter = (session.counter or 0) + 1
    return dict(message="Hello from MyApp", counter=session.counter)

Modifiez maintenant la vue pour ajouter une ligne qui affiche la valeur du compteur :

<html>
   <head></head>
   <body>
      <h1>{{=message}}</h1>
      <h2>Number of visits: {{=counter}}</h2>
   </body>
</html>

Lorsque vous visitez la page index à nouveau (et à nouveau) vous devriez voir la page HTML suivante :

image

Le compteur est associé à chaque visiteur, et est incrémenté chaque fois que le visiteur recharge la page. Différents visiteurs voient différents compteurs.

Dites mon nom

form
request.vars

Créez maintenant deux pages (première et deuxième), où la première page créé un formulaire, demande le nom du visiteur, et redirige vers la seconde page, qui salue le visiteur par nom.

yUML diagram

Ecrivez les actions correspondantes dans le contrôleur par défaut :

def first():
    return dict()

def second():
    return dict()

Créez ensuite une vue "default/first.html" pour la première action, et entrez :

{{extend 'layout.html'}}
<h1>What is your name?</h1>
<form action="second">
  <input name="visitor_name" />
  <input type="submit" />
</form>

Finalement, créez une vue "default/second.html" pour la deuxième action :

{{extend 'layout.html'}}
<h1>Hello {{=request.vars.visitor_name}}</h1>
layout

Dans les deux vues nous avons étendu la vue basique "layout.html" fournie avec web2py. La vue layout conserve un look and feel cohérent des deux pages. Le fichier du layout peut être édité et remplacé facilement, vu qu'il contient principalement du code HTML.

Si vous visitez maintenant la première page, entrez votre nom :

image

et envoyez le formulaire, vous recevrez un remerciement :

image

Postbacks

redirect
URL
postback

Le mécanisme de soumission de formulaire que nous avons utilisé auparavant est très commun, mais ne fait pas partie des bonnes pratiques de programmation. Toutes les entrées devraient être vérifiées, et dans l'exemple ci-dessus, la validation échouerait à la seconde action. Ainsi l'action qui exécute la validation est différente de l'action qui génère le formulaire. Ceci tend à causer des redondances dans le code.

Un meilleur patter de soumission de formulaire est d'envoyer le formulaire à la même action que celle qui l'a généré, dans notre exemple le "premier". La "première" action devrait recevoir les variables, les traites, les stocker côté serveur, et rediriger le visiteur vers la "seconde" page, qui récupère les variables. Ce mécanisme est appelé postback.

yUML diagram

Modifiez le contrôleur par défaut pour implémenter l'auto-soumission :

def first():
    if request.vars.visitor_name:
        session.visitor_name = request.vars.visitor_name
        redirect(URL('second'))
    return dict()

def second():
    return dict()

Modifiez ensuite la vue "default/first.html" :

{{extend 'layout.html'}}
What is your name?
<form>
  <input name="visitor_name" />
  <input type="submit" />
</form>

et la vue "default/second.html" a besoin de récupérer les données de la session au lieu de request.vars:

{{extend 'layout.html'}}
<h1>Hello {{=session.visitor_name or "anonymous"}}</h1>

Du point de vue du visiteur, l'auto-soumission se comporte exactement pareil que l'implémentation précédente. Nous n'avons pas ajouté de validation encore, mais il est maintenant clair que la validation devrait être exécutée par la première action.

Cette approche est meilleure également car le nom du visiteur reste dans la session, et peut être accédé par toutes les actions et vues de l'application sans avoir à être passé explicitement.

Notez que si la "seconde" action n'est jamais appelée avant qu'un nom de visiteur ne soit défini, elle affichera "Hello anonymous" car session.visitor_name renvoie None. D'une autre manière, on pourrait ajouter le code suivant dans le contrôleur (à l'intérieur de la second fonction) :

if not request.function=='first' and not session.visitor_name:
    redirect(URL('first'))

C'est un mécanisme ad hoc que vous pouvez utiliser pour renforcer l'autorisation sur les contrôleurs, voir le chapitre 9 pour une méthode encore plus puissante.

FORM
INPUT
requires
IS_NOT_EMPTY
accepts

Avez web2py nous pouvons aller encore plus loin et demander à web2py de générer le formulaire pour nous, incluant la validation. web2py fournit des assistants (FORM, INPUT, TEXTAREA et SELECT/OPTION) avec les mêmes noms que ses tags équivalents HTML. Ils peuvent être utilisés pour construire les formulaires aussi bien dans le contrôleur que dans la vue.

Par exemple, une possibilité pour réécrire la première action :

def first():
    form = FORM(INPUT(_name='visitor_name', requires=IS_NOT_EMPTY()),
              INPUT(_type='submit'))
    if form.process().accepted:
        session.visitor_name = form.vars.visitor_name
        redirect(URL('second'))
    return dict(form=form)

où nous indiquons que le tag FORM contient deux tags INPUT. Les attributs des tags input sont spécifiés par les arguments ayant un nom commençant par un underscore. L'argument requires n'est pas un attribut tag (car il ne démarre pas par un underscore) mais il définit un validateur pour la valeur de visitor_name.

Encore un autre moyen de créer le même formulaire :

def first():
    form = SQLFORM.factory(Field('visitor_name',
                                 label='what is your name?',
                                 requires=IS_NOT_EMPTY()))
    if form.process().accepted:
        session.visitor_name = form.vars.visitor_name
        redirect(URL('second'))
    return dict(form=form)

L'objet form peut être facilement sérialisé en HTML en l'embarquant dans la vue "default/first.html".

{{extend 'layout.html'}}
{{=form}}

La méthode form.process() applique les validateurs et retourne le formulaire. La variable form.accepted est mise à True si le formulaire a été traité et a réussi la validation. Si l'auto-soumission réussit la validation, il stocke les variables dans la session et redirige comme précédemment. Si le formulaire ne réussit pas la validation, les messages d'erreur sont insérés dans le formulaire et affichés à l'utilisateur, comme ci-dessous :

image

Dans la section suivante, nous montrerons comment les formulaires peuvent être générés automatiquement depuis un modèle.

Dans tous nos exemples nous avons utilisé la session pour passer le nom d'utilisateur de la première action à la seconde. Nous aurions pu utiliser un mécanisme différent et passé les données comme partie de l'URL redirigée :

def first():
    form = SQLFORM.factory(Field('visitor_name', requires=IS_NOT_EMPTY()))
    if form.process().accepted:
        name = form.vars.visitor_name
        redirect(URL('second',vars=dict(name=name)))
    return dict(form=form)

def second():
    name = request.vars.visitor_name or redirect(URL('first'))
    return dict(name=name)

Pensez bien qu'en général ce n'est pas une bonne idée de passer des données d'une action à une autre en utilisant l'URL. Ceci rend plus difficile la sécurisation de l'application. Il est plus sûr de stocker les données dans une session.

Internationalisation

Votre code est susceptible d'inclure des chaînes de caractères codées en dur telles que "Quel est votre nom ?". Vous devriez être capable de personnaliser ces chaînes sans éditer le code et en particlulier insérer des traductions pour ces chaînes dans différents langages. De ce fait, si un visiteur a ses préférences de langue du navigateur définies à "Italien", web2py utilisera la traduction italienne pour les chaînes de caractères, si possible. Cette fonctinonalité de web2py est appelée "internationalisation" et est décrite plus précisément dans le chapitre suivant.

Nous observons juste ici que si vous souhaitez utiliser cette fonctionnalité vous devrez marquer les chaînes qui nécessitent une traduction. Ceci est fait grâce aux guillements dans le code tel que

"What is your name?"

avec l'opérateur T :

T("What is your name?")

Vous pouvez aussi marquer des chaînes en dur pour traduction dans les vues. Par exemple

<h1>What is your name?</h1>

devient

<h1>{{=T("What is your name?")}}</h1>

Il est de bonne pratique de faire ceci pour toute chaîne dans le code (labels de champs, messages flashs, etc...) sauf pour les tables et les noms de champs.

Une fois les chaînes identifiées et marquées, web2py s'occupe de tout le reste. L'interface d'administration fournit également une page où vous pouvez traduire chaque chaîne dans la langue que vous souhaitez supporter.

web2py inclut un moteur de pluralisation puissant qui est décrit dans le prochain chapitre. Il est intégré avec le moteur d'internationalisation et celui de rendu markmin.

Un blog d'images

upload

Maintenant, un autre exemple, nous souhaitons créer une application web qui autorise l'administrateur à poster des images et à leur donner un nom, et autorise les visiteur du site web à voir les images nommées et à envoyer des commentaires (posts).

Comme avant, depuis la page site dans admin, créez une nouvelle application appelée images, et naviguez jusqu'à la page modifier :

image

Nous commençons par créer un modèle, une représentation des données persistente dans l'application (les images à uploader, leurs noms, et les commentaires). Premièrement, vous avez besoin de créer/éditer un fichier modèle que nous appellerons, par manque d'imagination, "db.py". Nous supposons que le code suivant remplacera tout code existant dans "db.py". Les modèles et contrôleurs doivent avoir une extension .py puisque c'est du code Python. Si l'extension n'est pas précisée, elle est ajoutée par web2py. Les vues, elles, ont une extension .html puisqu'elles contiennent principalement du code HTML.

Modifiez le fichier "db.py" en cliquant sur le bouton "modifier" correspondant :

image

et saisissez ceci :

IS_EMAIL
IS_NOT_EMPTY
IS_IN_DB

db = DAL("sqlite://storage.sqlite")

db.define_table('image',
   Field('title', unique=True),
   Field('file', 'upload'),
   format = '%(title)s')

db.define_table('post',
   Field('image_id', 'reference image'),
   Field('author'),
   Field('email'),
   Field('body', 'text'))

db.image.title.requires = IS_NOT_IN_DB(db, db.image.title)
db.post.image_id.requires = IS_IN_DB(db, db.image.id, '%(title)s')
db.post.author.requires = IS_NOT_EMPTY()
db.post.email.requires = IS_EMAIL()
db.post.body.requires = IS_NOT_EMPTY()

db.post.image_id.writable = db.post.image_id.readable = False

Analysons ceci ligne par ligne.

La ligne 1 définit une variable globale appelée db qui représente la connexion à la base de données. Dans ce cas, c'est une connexion à une base SQLite qui est stockée dans le fichier "applications/images/databases/storage.sqlite". Lorsque l'on utilise SQLite, si le fichier de base de données n'existe pas, il est créé. Vous pouvez changer le nom de ce fichier aussi bien que le nom de la variable globale db, mais il est pratique de leur donner le même nom pour s'en souvenir plus facilement.

Les lignes 3 à 6 définissent uen table "image". define_table est une méthode de l'objet db. Le premier argument, "image", est le nom de la table que nous définissons. Les autres arguments sont les champs appartenant à cette table. Cette table a un champ appelé "title", un champ appelé "file", et un dernier appelé "id" qui sert de clé primaire ("id" n'est pas déclaré explicitement car les tables ont un champ id par défaut). Le champ "title" est une chaîne, et le champ "file" est de type "upload". "upload" est un type spécial de champ utilisé par la couche d'abstraction à la base de données web2py (DAL) pour stocker les noms des fichiers uploadés. web2py sait comment uploader des fichiers (via streaming s'ils sont gros), les renommer proprement, et les stocker.

Lorsqu'une table est définie, web2py effectue l'une de ces actions possibles :

  • si la table n'existe pas, la table est créée;
  • si la table existe et ne correspond pas à la définitions, la table est modifiée en fonction, et si un champ a un type différent, web2py essaie de convertir le contenu;
  • si la table existe et correspond à la définition, web2py ne change rien.

Ce comportement est appelé "migration". Dans web2py les migrations sont automatiques, mais peuvent être désactivées pour chaque table en passant migrate=False comme dernier argument de define_table.

La ligne 6 définit un format de chaîne pour la table. Il détermine la manière dont un enregistrement devrait être représenté comme chaîne. Notez que l'argument format peut également être une fonction qui prend un enregistrement et renvoie une chaîne. Par exemple :

format=lambda row: row.title

Les lignes 8 à 12 définissent une autre table appelée "post". Un post a un "author", un "email" (nous souhaitons stocker l'adresse mail de l'auteur du post), un "body" de type "texte" (nous souhaitons l'utiliser pour stocker le commentaire actuel posté par l'auteur), et un champ "image_id" de type référence qui pointe sur db.image via le champ "id".

A la ligne 14, db.image.title représente le champ "title" de la table "image". L'attribut requires vous autorise à définir les pré-requis/contrainte qui seront pris en charge par les formulaires web2py. L'unicité de "title" est requise ici :

IS_NOT_IN_DB(db, db.image.title)

Notez que ceci est optionel car il est automatiquement défini par Field('title', unique=True).

Les objets représentant ces contraintes sont appelés validateurs. Plusieurs validateurs peuvent être groupés dans une liste. Les validateurs sont exécutés dans l'ordre où ils apparaissent. IS_NOT_IN_DB(a, b) est un validateur spécial qui vérifie que la valeur du champ b pour un nouvel enregistrement n'est pas déjà dans a.

La ligne 15 requiert que le champ "image_id" de la table "post" soit dans db.image.id. Côté base de données, nous l'avons déjà défini quand nous avons défini la table "post". Maintenant nous allons dire explicitement au modèle que la condition devrait être forcée par web2py, également au niveau du traitement du formulaire lorsqu'un nouveau commentaire est posté, afin que les valeurs invalides ne se propagent pas depuis les entrées de formulaire vers la base de données. Nous allons également demander que l' "image_id" soit représenté par le "title", '%(title)s', de l'enregistrement correspondant.

La ligne 20 indique que le champ "image_id" de la table "post" ne devrait pas apparaître dans les formulaires, writable=False et même dans ceux en lecture seule, readable=False.

La signification des validateurs dans les lignes 17-18 devraient être évidents.

format

Notez que le validateur

db.post.image_id.requires = IS_IN_DB(db, db.image.id, '%(title)s')

peut être omis (et serait automatique) si nous spécifions un format de table référencé :

db.define_table('image', ..., format='%(title)s')

où le format peut être une chaîne de caractères ou une fonction qui prend un enregistrement et renvoie une chaîne.

appadmin

Une fois le modèle défini, s'il n'y a pas d'erreurs, web2py créé une interface d'administration de l'application pour gérer la base de données. Vous y accédez via le lien "administration de base de données" dans la page modifier ou directement :

http://127.0.0.1:8000/images/appadmin

Une capture d'écran de l'interface appadmin :

image

Cette interface est codée dans le contrôleur appelé "appadmin.py" et la vue correspondante "appadmin.html". A partir de maintenant, nous appellerons simplement cette interface appadmin. Elle permet à l'administrateur d'insérer de nouveaux enregistrements dans la base de données, modifier et supprimer des enregistrements existants, parcourir les tables, et effectuer des jointures de base de données.

Au premier accès à appadmin, le modèle est exécuté et les tables sont créées. La DAL web2py traduit le code Python en déclarations SQL qui sont spécifiques à la base de données choisie (SQLite dans cet exemple). Vous pouvez voir le SQL généré depuis la page modifier en cliquant sur le lien "sql.log" sous "modèles". Notez que le lien n'est pas présent tant que les tables n'ont pas été créés.

image

Si vous voulez éditer le modèle et accéder à nouveau à appadmin, web2py regénèrerait le code SQL pour modifier les tables existantes. Le code SQL généré est loggé dans "sql.log".

Revenons maintenant à appadmin et essayez d'insérer l'enregistrement d'une nouvelle image :

image

web2py a traduit le champ db.image.file "upload" en un formulaire d'envoi pour le fichier. Quand le formulaire est soumis et un fichier image envoyé, le fichier est renommé de manière sécurisée qui conserve l'extension, elle est sauvée avec le nouveau nom sous le dossier "uploads" de l'application, et le nouveau nom est stocké dans le champ db.image.file. Ce processus est destiné à prévenir des attaques transversales de dossier.

Notez que chaque type de champ est rendu par un widget. Les widgets par défaut peuvent être réécrits et substitués.

Lorsque vous cliquez sur le nom d'une table dans appadmin, web2py effectue une sélection de tous les enregistrements de la table courante, identifiés par la requête dans la DAL

db.image.id > 0

et effectue le rendu.

image

Vous pouvez choisir un ensemble différent d'enregistrement en éditant la requête à la DAL et en appuyant sur [Submit].

Pour modifier ou supprimer un enregistrement simple, cliquez sur l'id de l'enregistrement.

Grâce au validateur IS_IN_DB validator, le champ de référence "image_id" est rendu par un menu déroulant. Les objets dans la liste déroulante sont stockés comme clés (db.image.id), mais sont représentés par leur db.image.title, comme spécifié par le validateur.

Les validateurs sont des objets très puissants qui savent comment représenter les champs, filtrent les valeurs des champs, génèrent les erreurs, et formatent les valeurs extraites.

La figure suivante montre ce qu'il se passe quand vous envoyez le formulaire qui ne passe pas la validation :

image

Les mêmes formulaires automatiquement générés par appadmin peuvent aussi être programmés par l'assistant SQLFORM et embarqués dans les applications utilisateur. Ces formulaires sont CSS-friendly, et peuvent être personnalisés.

Toute application a son propre appadmin; par conséquent, appadmin peut être modifié lui-même sans affecter d'autres applications.

Jusqu'ici, l'application sait comment stocker les données, et nous avons vu comment accéder à la base de données via appadmin. L'accès à appadmin est restreint à l'administrateur, et il n'est pas prévu d'être utilisé comme interface web de production pour l'application; d'où la partie suivante de cette solution. Spécifiquement, nous voulons créer :

  • Une page "index" qui liste toute les images disponibles triées par titre et les liens aux pages de détail pour les images.
  • Une page "show/[id]" qui montre au visiteur l'image requêtée et l'autorise à voir les commentaires.
  • Une action "download/[name]" pour télécharger les images uploadées.

Ceci peut être représenté de manière schématique :

yUML diagram

Revenez à la page edit et modifier le contrôleur "defaut.py", remplaçant son contenu avec ce qui suit :

select
def index():
    images = db().select(db.image.ALL, orderby=db.image.title)
    return dict(images=images)

Cette action renvoie un dictionnaire. Les clés des objets du dictionnaire sont interprétés commme variables passées à la vue associée à l'action. Lors du développement, s'il n'y a pas de vue, l'action est rendue par la vue "generic.html" qui est fournie avec toute application web2py.

L'action index effectue une sélection de tous les champs (db.image.ALL) depuis la table image, ordonnée par db.image.title. Le résultat de la sélection est un objet Rows contenant les enregistrements. Assignez le à une variable locale appelée images renvoyée par l'action à la vue. images est itérable et ses éléments sont des lignes sélectionnées. Pour chaque ligne, les colonnes peuvent être accédées comme dictionnaires : images[0]['title'] ou l'équivalent à images[0].title.

Si vous n'écrivez pas une vue, le dictionnaire est rendu par "views/generic.html" et un appel à l'action index ressemblerait à :

image

Nous n'avez pas encore créé une vue pour cette action, alors web2py affiche l'ensemble des enregistrements dans un formulaire en tableau plein.

Procédez à la création d'une vue pour l'action index. Retournez à l'administration, modifiez "default/index.html" et remplacez son contenu avec le suivant :

{{extend 'layout.html'}}
<h1>Current Images</h1>
<ul>
{{for image in images:}}
{{=LI(A(image.title, _href=URL("show", args=image.id)))}}
{{pass}}
</ul>

La première chose à noter est qu'une vue est pur HTML avec des tags spéciaux {{...}}. Le code embarqué dans {{...}} est pur Python avec un point : l'indentation est sans importance. Les blocs de code démarrent avec des lignes finissant par deux points (:) et leur fin est marquée par le mot-clé pass. Dans certains cas, la fin d'un bloc est évidente en fonction du contexte et l'utilisation de pass n'est pas obligatoire.

Les lignes 5 à 7 effectuent une boucle sur les lignes d'image et pour chaque image affichent :

LI(A(image.title, _href=URL('show', args=image.id))

C'est un tag <li>...</li> qui contient un tag <a href="...">...</a> qui contient image.title. La valeur de la référence hypertexte (attribut href) est :

URL('show', args=image.id)

i.e., l'URL dans la même application et contrôleur comme la requête courante qui appelle la fonction appelée "show", passant un seul argument à la fonction, args=image.id. LI, A, etc. sont des assistants web2py qui correspondent à des tags HTML. Leur argument non nommés sont interprétés comme des objets pour être sérialisés et insérés dans le tag innerHTML. Les arguments nommés commençant avec un underscore (par exemple _href) sont interprétés comme des attributs mais sans l'underscore. Par exemple _href est l'attribut href, _class est l'attribut class, etc...

Comme exemple, la déclaration suivante :

{{=LI(A('something', _href=URL('show', args=123))}}

est rendue comme :

<li><a href="/images/default/show/123">something</a></li>

Une poignée d'assistants (INPUT, TEXTAREA, OPTION et SELECT) supportent également quelques attributs spéciaux ne commençant pas par un underscore (value, et requires). Ils sont importants pour construire des formulaires personnalisés et seront vus plus tard.

Retournez à la page modifier. Elle indique maintenant que "default.py exposes index". En cliquant sur "index", vous pouvez visiter la page nouvellement créée :

http://127.0.0.1:8000/images/default/index

qui ressemble à :

image

Si vous cliquez sur le lien du nom de l'image, vous êtes redirigé vers :

http://127.0.0.1:8000/images/default/show/1

et ceci résulte en une erreur, tant que vous n'avez pas créé une action appelée "show" dans le contrôleur "default.py".

Modifions le contrôleur "default.py" et remplaçons son contenu avec :

SQLFORM
accepts
response.flash
request.args

response.download
def index():
    images = db().select(db.image.ALL, orderby=db.image.title)
    return dict(images=images)

def show():
    image = db.image(request.args(0,cast=int)) or redirect(URL('index'))
    db.post.image_id.default = image.id
    form = SQLFORM(db.post)
    if form.process().accepted:
        response.flash = 'your comment is posted'
    comments = db(db.post.image_id==image.id).select()
    return dict(image=image, comments=comments, form=form)

def download():
    return response.download(request, db)

Le contrôleur contient deux actions : "show" et "download". L'action "show" sélectionne l'image avec l' id parsé depuis les arguments de la requête et tous les commentaires en relation avec l'image. "show" envoie alors tout à la vue "default/show.html".

L'id de l'image est référencé par :

URL('show', args=image.id)

dans "default/index.html", peut être accédé comme :

request.args(0,cast=int)

depuis l'action "show". L'argument cast=int est optionnel mais très important. Il essaie de caster la valeur de la chaîne passée dans le PATH_INFO à travers un int. En cas d'échec une exception propre est levée au lieu de créer un ticket. Il se peut également qu'une redirection soit spécifiée en cas d'échec lors du cast :

request.args(0,cast=int,otherwise=URL('error'))

De plus db.image(...) est un raccourci pour

db(db.image.id==...).select().first()

L'action "download" attend un nom de fichier dans request.args(0), construit un chemin vers la location où le fichier est supposé être, et le renvoie au client. Si le fichier est trop gros, le fichiers est envoyé via un flux sans endommager la mémoire disponible.

Notez les déclarations suivantes :

  • La ligne 6 définit la valeur pour le champ référence, qui ne fait pas partie des entrées du formulaires car n'est pas dans la liste des champs spécifiés au-dessus.
  • La ligne 7 créé une insertion depuis le SQLFORM pour la table db.post en utilisant seulement les champs spécifiés.
  • La ligne 8 procède avec le formulaire envoyé (les variables du formulaire soumis sont dans request.vars) dans la session courante (la session est utilisée pour prévenir des doubles soumissions, et pour renforcer la navigation). Si les variables soumise par le formulaire sont validées, le nouveau commentaire est inséré dans la table db.post; autrement le formulaire est modifié pour inclure les messages d'erreur (par exemple, si l'adresse email de l'auteur est invalide). Tout est fait ensuite en ligne 9 !.
  • La ligne 9 est seulement exécutée si le formulaire est accepté, après que l'enregistrement soit inséré dans la table de la base de données. response.flash est une variable web2py qui est affichée dans les vues et utilisée pour notifier le visiteur que quelque chose est arrivé.
  • La ligne 10 sélectionne tous les commentaires référençant l'image courante.
L'action "download" est déjà définie dans le contrôleur "default.py" de l'application de base.

L'action "download" ne retourne pas un dictionnaire, donc ne nécessite pas une vue. L'action "show", devrait avoir une vue, donc on retourne à admin et créé une nouvelle vue appelée "default/show.html".

Editons ce nouveau fichier et remplaçons son contenu avec ce qui suit :

{{extend 'layout.html'}}
<h1>Image: {{=image.title}}</h1>
<center>
<img width="200px"
     src="{{=URL('download', args=image.file)}}" />
</center>
{{if len(comments):}}
  <h2>Comments</h2><br /><p>
  {{for post in comments:}}
    <p>{{=post.author}} says <i>{{=post.body}}</i></p>
  {{pass}}</p>
{{else:}}
  <h2>No comments posted yet</h2>
{{pass}}
<h2>Post a comment</h2>
{{=form}}

Cette vue affiche image.file en appelant l'action "download à l'intérieur d'un tag <img ... />. Si il y a des commentaires, il les parcoure et les affiche également.

Voici comment tout apparaîtra pour un visiteur.

image

Lorsqu'un visiteur soumet un commentaire via cette page, le commentaire est stocké dans la base de données et ajouté à la fin de la page.

Ajout de l'authetification

L'API web2py pour les contrôles d'acces basés sur le rôle (Role-Based Access Control) est assez sophistiquée, mais pour l'instant nous nous limiterons à une restriction d'accès à l'action show pour les utilisateurs authentifiés, en se reportant à une discussion plus détaillée en chapitre 9.

Pour limiter l'accès aux utilisateurs authentifiés, nous avons besoin de compléter trois étapes. Dans un modèle, par exemple "db.py", nous avons besoin d'ajouter :

from gluon.tools import Auth
auth = Auth(db)
auth.define_tables(username=True)

Dans notre contrôleur, nous avons besoin d'ajouter une action :

def user():
    return dict(form=auth())

C'est suffisant pour activer les pages de login, l'enregistrement, la déconnexion, etc... Le layout par défaut affichera également les options aux pages correspondantes dans le coin supérieur droit.

image

Nous pouvons maintenant décorer les fonctions que l'on veut restreindre, par exemple :

@auth.requires_login()
def show():
    ...

Quelconque tentative d'accès à

http://127.0.0.1:8000/images/default/show/[image_id]

nécessitera un login. Si l'utilisateur n'est pas logué, l'utilisateur sera redirigé vers

http://127.0.0.1:8000/images/default/user/login

image

La fonction user permet également, parmi d'autres, les actions suivantes :

http://127.0.0.1:8000/images/default/user/logout
http://127.0.0.1:8000/images/default/user/register
http://127.0.0.1:8000/images/default/user/profile
http://127.0.0.1:8000/images/default/user/change_password
http://127.0.0.1:8000/images/default/user/request_reset_password
http://127.0.0.1:8000/images/default/user/retrieve_username
http://127.0.0.1:8000/images/default/user/retrieve_password
http://127.0.0.1:8000/images/default/user/verify_email
http://127.0.0.1:8000/images/default/user/impersonate
http://127.0.0.1:8000/images/default/user/not_authorized

Maintenant, un utilisateur a besoin de s'enregistrer à la première visite pour pouvoir se connecter et lire/poster des commentaires.

L'objet auth et la fonction user sont déjà définis dans l'application de base. L'objet auth est hautement personnalisable et peut être traité avec une vérification email, des approbations d'enregistrement, CAPTCHA, et des méthodes de connexion alternatives via des plugins.

Ajout de grilles

Nous pouvons encore améliorer cela en utilisant les gadgets SQLFORM.grid et SQLFORM.smartgrid pour créer une interface de gestion pour notre application :

@auth.requires_membership('manager')
def manage():
    grid = SQLFORM.smartgrid(db.image,linked_tables=['post'])
    return dict(grid=grid)

avec la vue "views/default/manage.html" associée

{{extend 'layout.html'}}
<h2>Management Interface</h2>
{{=grid}}

Utiliser appadmin créé un groupe "manager" et rend quelques utilisateurs membres du groupe. Ils seront également capables d'accéder à

http://127.0.0.1:8000/images/default/manage

et de parcourir et rechercher :

image

créer, mettre à jour et supprimer les images et leurs commentaires :

image

Configurer le layout

Vous pouvez configurer le layout par défaut en éditant la vue "views/layout.html" mais vous pouvez également le configurer sans éditer l'HTML. En fait, la feuille de style "static/base.css" est bien documentée et décrite dans le chapitre 5. Vous pouvez changer la couleur, les colonnes, la taille, les bordures et le fond sans éditer l'HTML. Si vous voulez éditer le menu, le titre ou le sous-titre, vous pouvez le faire dans n'importe quel fichier modèle. L'application de base, définit les valeurs par défaut de ces paramètres dans le fichier "models/menu.py":

response.title = request.application
response.subtitle = 'customize me!'
response.meta.author = 'you'
response.meta.description = 'describe your app'
response.meta.keywords = 'bla bla bla'
response.menu = [ [ 'Index', False, URL('index') ] ]

Un wiki simple

wiki
RSS
Ajax
XMLRPC

Dans cette section, nous allons construire un simple wiki de rien en utilisant uniquement les APIs bas niveau (à l'opposé de l'utilisation des possibilités pré-construites de web2py démontrées dans la section suivante). Le visiteur sera capable de créer des pages, les chercher (par titre) et de les éditer. Le visiteur sera également capable de poster des commentaires (exactement comme dans les applications précédentes), et également de poster des documents (en pièces jointes aux pages) et les lier depuis les pages. Comme convention, nous adoptons la syntaxe Markmin pour la syntaxe de notre wiki. Nous allons également implémenter une page de recherche avec Ajax, un flux RSS pour les pages, et un gestionnaire pour chercher les pages via XML-RPC[xmlrpx] . Le diagramme suivant liste les actions que nous avons besoin d'implémenter et les liens que nous avons l'intention de construire entre eux.

yUML diagram

Commençons par créer un nouvelle application de base, que nous nommons "mywiki".

Le modèle doit contenir trois tables : page, comment et document. Aussi bien comment que document référence page puisqu'ils lui appartiennent. Un document contient un champ fichier de type upload comme dans l'application précédente pour l'image.

Voici le modèle complet :

db = DAL('sqlite://storage.sqlite')

from gluon.tools import *
auth = Auth(db)
auth.define_tables()
crud = Crud(db)

db.define_table('page',
    Field('title'),
    Field('body', 'text'),
    Field('created_on', 'datetime', default=request.now),
    Field('created_by', 'reference auth_user', default=auth.user_id),
    format='%(title)s')

db.define_table('post',
    Field('page_id', 'reference page'),
    Field('body', 'text'),
    Field('created_on', 'datetime', default=request.now),
    Field('created_by', 'reference auth_user', default=auth.user_id))

db.define_table('document',
    Field('page_id', 'reference page'),
    Field('name'),
    Field('file', 'upload'),
    Field('created_on', 'datetime', default=request.now),
    Field('created_by', 'reference auth_user', default=auth.user_id),
    format='%(name)s')

db.page.title.requires = IS_NOT_IN_DB(db, 'page.title')
db.page.body.requires = IS_NOT_EMPTY()
db.page.created_by.readable = db.page.created_by.writable = False
db.page.created_on.readable = db.page.created_on.writable = False

db.post.body.requires = IS_NOT_EMPTY()
db.post.page_id.readable = db.post.page_id.writable = False
db.post.created_by.readable = db.post.created_by.writable = False
db.post.created_on.readable = db.post.created_on.writable = False

db.document.name.requires = IS_NOT_IN_DB(db, 'document.name')
db.document.page_id.readable = db.document.page_id.writable = False
db.document.created_by.readable = db.document.created_by.writable = False
db.document.created_on.readable = db.document.created_on.writable = False

Editez le contrôleur "default.py" et créez les actions suivantes :

  • index: liste toutes les pages du wiki
  • create: ajouter une nouvelle page au wiki
  • show: montre une page du wiki et ses commentaires, et ajoute de nouveaux commentaires
  • edit: édite une page existante=
  • documents: gère les documents attachés à une page
  • download: télécharge un document (comme dans l'exemple des images)
  • search: affiche une boite de recherche et, via un callback Ajax, renvoie tous les titres correspondant à la requête utilisateur
  • callback: la fonction callback Ajax. Elle renvoir l'HTML qui va être embarqué dans la page de recherche lorsque le visiteur tape.

Voici le contrôleur "default.py" :

def index():
     """ this controller returns a dictionary rendered by the view
         it lists all wiki pages
     >>> index().has_key('pages')
     True
     """
     pages = db().select(db.page.id,db.page.title,orderby=db.page.title)
     return dict(pages=pages)

@auth.requires_login()
def create():
     """creates a new empty wiki page"""
     form = SQLFORM(db.page).process(next=URL('index'))
     return dict(form=form)

def show():
     """shows a wiki page"""
     this_page = db.page(request.args(0,cast=int)) or redirect(URL('index'))
     db.post.page_id.default = this_page.id
     form = SQLFORM(db.post).process() if auth.user else None
     pagecomments = db(db.post.page_id==this_page.id).select()
     return dict(page=this_page, comments=pagecomments, form=form)

@auth.requires_login()
def edit():
     """edit an existing wiki page"""
     this_page = db.page(request.args(0,cast=int)) or redirect(URL('index'))
     form = SQLFORM(db.page, this_page).process(
         next = URL('show',args=request.args))
     return dict(form=form)

@auth.requires_login()
def documents():
     """browser, edit all documents attached to a certain page"""
     page = db.page(request.args(0,cast=int)) or redirect(URL('index'))
     db.document.page_id.default = page.id
     db.document.page_id.writable = False
     grid = SQLFORM.grid(db.document.page_id==page.id,args=[page.id])
     return dict(page=page, grid=grid)

def user():
     return dict(form=auth())

def download():
     """allows downloading of documents"""
     return response.download(request, db)

def search():
     """an ajax wiki search page"""
     return dict(form=FORM(INPUT(_id='keyword',_name='keyword',
              _onkeyup="ajax('callback', ['keyword'], 'target');")),
              target_div=DIV(_id='target'))

def callback():
     """an ajax callback that returns a <ul> of links to wiki pages"""
     query = db.page.title.contains(request.vars.keyword)
     pages = db(query).select(orderby=db.page.title)
     links = [A(p.title, _href=URL('show',args=p.id)) for p in pages]
     return UL(*links)

Les lignes 2 à 6 constituent un commentaire pour l'action index. Les lignes 4 et 5 à l'intérieur de comment sont interprétées par python comme du code de test (doctest)? Les tests peuvent être exécutés via l'interface admin. Dans ce cas les tests vérifient que l'action index fonctionne sans erreur.

Les lignes 18, 27 et 35 essaient de rapporter un enregistrement page avec l'id dans request.args(0).

Les lignes 13, 20 définissent et produisent les formulaires de création pour une nouvelle page et un nouveau commentaire et

la ligne 28 définit et produit un formulaire de mise à jour pour une page de wiki.

La ligne 38 créé un objet grid qui autorise à voir, ajouter et mettre à jour les commentaires liés à une page.

Un peu de magie arrive à la ligne 51. L'attribut onkeyup du tag INPUT "keyword" est défini. A chaque fois qu'un visiteur relâche une touche, le code Javascript à l'intérieur de l'attribut onkeyup est exécuté, côté client. Voici le code JavaScript :

ajax('callback', ['keyword'], 'target');

ajax est une fonction JavaScript définie dans le fichier "web2py.js" qui est inclus par défaut dans le fichier "layout.html". Il prend trois paramètres : l'URL de l'action qu'exécute le callback synchrone, une liste des IDs des variables qui sont envoyées à la callback (["keyword"]), et l'ID où la réponse doit être insérée ("target").

Aussitôt que vous tapez quelque chose dans la boîte de recherche et que vous relâcher une touche, le client appelle le serveur et envoie le contenu du champ 'keyword', et, lorsque le serveur répond, lal réponse est embarquée dans la page comme innerHTML dans le tag 'target'.

Le tag 'target' est un DIC défini à la ligne 52. Il peut avoir été défini dans une vue également.

Voici le code pour la vue "default/create.html" :

{{extend 'layout.html'}}
<h1>Create new wiki page</h1>
{{=form}}

Supposant que vous êtes enregistré et connecté, si vous visitez la page create, vous verrez :

image

Voici le code pour la vue "default/index.html" :

{{extend 'layout.html'}}
<h1>Available wiki pages</h1>
[ {{=A('search', _href=URL('search'))}} ]<br />
<ul>{{for page in pages:}}
     {{=LI(A(page.title, _href=URL('show', args=page.id)))}}
{{pass}}</ul>
[ {{=A('create page', _href=URL('create'))}} ]

Il génère la page suivante :

image

Voici le code pour la vue "default/show.html" :

markdown
MARKMIN

{{extend 'layout.html'}}
<h1>{{=page.title}}</h1>
[ {{=A('edit', _href=URL('edit', args=request.args))}}
| {{=A('documents', _href=URL('documents', args=request.args))}} ]<br />
{{=MARKMIN(page.body)}}
<h2>Comments</h2>
{{for post in comments:}}
  <p>{{=db.auth_user[post.created_by].first_name}} on {{=post.created_on}}
     says <i>{{=post.body}}</i></p>
{{pass}}
<h2>Post a comment</h2>
{{=form}}

Si vous souhaitez utiliser la syntaxe markdown au lieu de la syntaxe markmin :

from gluon.contrib.markdown import WIKI as MARKDOWN

et utilisez les assistants MARKDOWN au lieu des assistants MARKMIN. Alternativement, vous pouvez choisir d'accepter le code HTML brut au lieu de a syntaxe markmin. Dans ce cas vous devez remplacer :

{{=MARKMIN(page.body)}}

avec :

{{=XML(page.body)}}
sanitize

(afin que le code ne soit pas ignoré, ce que web2py fait normalement par défaut pour des raisons de sécurité).

Ceci peut plutôt être fait avec :

{{=XML(page.body, sanitize=True)}}

En définissant sanitize=True, vous dites à web2py d'ignorer tous les tags non sûrs XML tels que "<script>", et du coup d'éviter les vulnérabilités XSS.

Maintenant, si depuis la page index, vous cliquez sur le titre d'une page, vous pouvez voir la page que vous avez créée :

image

Voici le code pour la vue "default/edit.html" :

{{extend 'layout.html'}}
<h1>Edit wiki page</h1>
[ {{=A('show', _href=URL('show', args=request.args))}} ]<br />
{{=form}}

Ceci génère une page qui ressemble très fortement à la page de création.

Voici le code pour la vue "default/documents.html" :

{{extend 'layout.html'}}
<h1>Documents for page: {{=page.title}}</h1>
[ {{=A('show', _href=URL('show', args=request.args))}} ]<br />
<h2>Documents</h2>
{{=grid}}

Si, depuis la page "show", vous cliquez sur documents, vous pouvez maintenant gérer les documents joints à la page.

image

Voici finalement le code pour la vue "default/search.html" :

{{extend 'layout.html'}}
<h1>Search wiki pages</h1>
[ {{=A('listall', _href=URL('index'))}}]<br />
{{=form}}<br />{{=target_div}}

qui génère le formulaire de recherche Ajax :

image

Vous pouvez également essayer d'appeler l'action callback directement en visitant, par exemple, l'URL suivante :

http://127.0.0.1:8000/mywiki/default/callback?keyword=wiki

Si vous regardez le code source de la page, vous verrez l'HTML renvoyé par la callback :

<ul><li><a href="/mywiki/default/show/4">I made a Wiki</a></li></ul>
rss

Générer un flux RSS de vos pages wiki en utilisant web2py est facile puisque web2py inclut gluon.contrib.rss2. Ajoutez simplement l'action suivante à votre contrôleur par défaut :

def news():
    """generates rss feed from the wiki pages"""
    response.generic_patterns = ['.rss']
    pages = db().select(db.page.ALL, orderby=db.page.title)
    return dict(
       title = 'mywiki rss feed',
       link = 'http://127.0.0.1:8000/mywiki/default/index',
       description = 'mywiki news',
       created_on = request.now,
       items = [
          dict(title = row.title,
               link = URL('show', args=row.id, scheme=True, 
	                  host=True, extension=False),
               description = MARKMIN(row.body).xml(),
               created_on = row.created_on
               ) for row in pages])

et lorsque vous visitez la page

http://127.0.0.1:8000/mywiki/default/news.rss

vous voyez le flux (la sortie exacte dépend du lecteur de flux). Notez que le dict est automatiquement converti en RSS, grâce à l'extension .rss dans l'URL.

image

web2py inclut également feedparser pour lire des flux tiers.

Notez que la ligne :

response.generic_patterns = ['.rss']

indique à web2py d'utiliser les vues génériques (dans notre cas "views/generic.rss") lorsque l'URL termine par le pattern ".rss". Par défaut, les vues génériques sont seulement autorisées depuis localhost pour les raisons de développement.

XMLRPC

Ajoutez finalement un gestionnaire XML-RPC qui autorise la recherche dans le wiki par programmation :

service = Service()

@service.xmlrpc
def find_by(keyword):
     """finds pages that contain keyword for XML-RPC"""
     return db(db.page.title.contains(keyword)).select().as_list()

def call():
    """exposes all registered services, including XML-RPC"""
    return service()

Ici, l'action du gestionnaire publie simplement (via XML-RPC), les fonctions spécifiées dans la liste. Dans ce cas, find_by. find_by n'est pas une action (puisqu'il prend un argument). Il requête la base de données avec .select() et ensuite extrait les enregistrement comme liste avec .response et retourne la liste.

Voici un exemple de comment accéder au gestionnaire XML-RPX depuis un programme Python externe.

>>> import xmlrpclib
>>> server = xmlrpclib.ServerProxy(
    'http://127.0.0.1:8000/mywiki/default/call/xmlrpc')
>>> for item in server.find_by('wiki'):
        print item['created_on'], item['title']

Le gestionnaire peut être accessible depuis plusieurs autres langages qui comprennent le XML-RPC, incluant C, C++, C# et Java.

Sur les formats date, datetime et time

Il y a trois représentations différentes pour chaque type de champ date, datetime et time :

  • la représentation base de données
  • la représentation interne web2py
  • la représentation chaîne dans les formulaires et tables

La représentation base de données est interne et n'affecte pas le code. A l'intérieur, au niveau de web2py, ils sont stockés comme objets datetime.date, datetime.datetime et datetime.time respectivement et ils peuvent être manipulés tels que :

for page in db(db.page).select():
    print page.title, page.day, page.month, page.year

Quand les dates sont converties en chaînes dans les formulaires, elles sont converties en utilisant la représentation ISO

%Y-%m-%d %H:%M:%S

maintenant cette représentation est internationalisée et vous pouvez utiliser la traduction admin pour changer le format vers un autre. Par exemple :

%m/%d/%Y %H:%M:%S

Pensez bien que par défaut l'Anglais n'est pas traduit puisque web2py support que les application sont écrites en anglais. Si vous souhaitez que l'internationalisation fonctionne pour l'anglais, vous avez besoin de créer le fichier de traduction (en utilisant admin) et vous avez besoin de déclarer que la langue courante de l'application est autre que l'anglais, par exemple :

T.current_languages = ['null']

Le wiki web2py pré-construit

Vous pouvez maintenant oublier le code que nous avons construit dans la section précédente (pas ce que vous avez appris à propos des APIs web2py, mais juste le code spécifique à l'exemple) puisque nous allons travailler sur un exemple de wiki pré-construit avec web2py.

En fait, web2py propose des possibilités incluant l'attachement de media, tags, tag cloud, les permissions de page, et le support pour oembed [oembed] et les composants (chapitre 14). Ce wiki peut être utilisé avec n'importe quelle application web2py.

Notez que l'API du wiki pré-construit est encore considérée comme expérimentale et de légers changements sont encore possibles.

Nous supposons ici que nous démarrons de zéro depuis un simple clone de l'application "welcome" appelée "wikidemo". Modifiez le contrôleur et remplacez l'action "index" avec :

def index(): return auth.wiki()

C'est fait ! Vous avez un wiki totalement fonctionnel. A ce point, aucune page n'a été créée et afin de créer des pages vous devez vous connecter et être membre du groupe appelé "wiki_editor" ou "wiki_author". Si vous êtes connecté en tant qu'administrateur, le group "wiki_editor" est créé automatiquement et vous en êtes membre. La différente entre les éditeurs et les auteurs est que les éditeurs peuvent créer les pages, les éditer et en supprimer, alors que les auteurs peuvent créer les pages (avec certaines restrictions optionnelles) et peuvent seulement éditer/supprimer les pages qu'ils ont créées.

La fonction auth.wiki() renvoie un dictionnaire avec une clé content qui est interprétée par la vue par défaut "views/default/index.html". Vous pouvez faire votre propre vue pour cette action :

{{extend 'layout.html'}}
{{=content}}

et ajouter de l'extra HTML ou du code comme vous en avez besoin. Nous n'avez pas à utiliser l'action "index" pour exposer le wiki. Vous pouvez utiliser une action avec un nom différent.

Pour essayer le wiki, connectez-vous simplement en tant qu'admin, et visitez la page

http://127.0.0.1:8000/wikidemo/default/index

Choisissez alors un slug (dans le business de l'édition, un slug est un court nom donné à un article en production) et vous serez redirigé vers une page vierge où vous pourrez éditer le contenu en utilisant la syntaxe wiki MARKMIN. Un nouvel objet du menu appelé "[wiki]" vous autorisera à créer, chercher et éditer les pages. Les pages wiki ont des URLs comme :

http://127.0.0.1:8000/wikidemo/default/index/[slug]

Les pages de service ont des noms qui commencent par un underscore :

http://127.0.0.1:8000/wikidemo/default/index/_create
http://127.0.0.1:8000/wikidemo/default/index/_search
http://127.0.0.1:8000/wikidemo/default/index/_could
http://127.0.0.1:8000/wikidemo/default/index/_recent
http://127.0.0.1:8000/wikidemo/default/index/_edit/...
http://127.0.0.1:8000/wikidemo/default/index/_editmedia/...
http://127.0.0.1:8000/wikidemo/default/index/_preview/...

Essayez de créer plus de pages telles que "index", "aboutus" et "contactus". Essayez de les éditer.

La méthode wiki a la signature suivante :

def wiki(self, slug=None, env=None, render='markmin',
         manage_permissions=False, force_prefix='',
         restrict_search=False, resolve=True,
         extra=None, menugroups=None)

Elle prend les arguments suivants :

  • render qui est par défaut à 'markmin' mais qui peut être égal à 'html'. Il détermine la syntaxe utilisée par le wiki. Nous discuterons de la syntaxe markmin plus tard. Si vous le changer à HTML vous pouvez utiliser un éditeur JavaScript WYSIWYG tel que TinyMCE ou NicEdit.
  • manage_permissions. Par défaut à False et reconnaît seulement les permissions pour "wiki_editor" et "wiki_author". Si vous le changez à True la page de création/modification donnera l'option pour spécifier par nom le(s) groupe(s) dont le(s) membre(s) ont la permission de lire et éditer la page. Il y a un group "everybody" qui inclut tous les utilisateurs.
  • force_prefix. Si défini à quelque chose comme '%(id)s-' il restreindra les auteurs (pas les éditeurs) à la création de pages avec un préfixe comme "[user id]-[page name]". Le préfixe peut contenir l'id ("%(id)s") ou le nom d'utilisateur ("%(username)s") ou tout autre champ depuis la table auth_user, tant que la colonne correspondante contient une chaîne valide qui passerait la validation de l'URL.
  • restrict_search. Par défaut à False et n'importe quel utilisateur connecté peut chercher toutes les pages du wiki (mais pas nécessairement les lire ou les éditer). Si mise à True, les auteurs peuvent seulement chercher leurs propres pages, les éditeurs peuvent tout chercher, les autres utilisateur ne peuvent rien chercher.
  • menu_groups. Par défaut à None et indique que le menu de gestion du wiki (chercher, créer, éditer, ...) est toujours affiché. Vous pouvez définir une liste de groupes donc les membres seulement peuvent voir ce menu, par exemple ['wiki_editor','wiki_author']. Notez que même si le menu est affiché à tous, ça ne signifie pas que tout le monde est autorisé à effectuer les actions listées tant qu'elles sont régulées par le système de contrôle d'accès.

La méthode wiki a quelques paramètres additionnels qui seront expliqués plus tard : slug, env, et extra.

Les bases de MARKMIN

La syntaxe MARKMIN vous autorise à marquer du texte en gras en utilisant **gras**, du texte italic avec ''italic'', et du code en le délimitant par des doubles quotes inverses. Les titres doivent être préfixés par un #, les sections par ##, et les sous-sections par ###. Utilisez un moins(-) pour préfixer un object non ordonné et plus(+) pour un objet ordonné dans une liste. Les URLs sont automatiquement converties en liens. Voici un exemple de texte markmin :

# This is a title
## this is a section title
### this is a subsection title

Text can be **bold**, ''italic'', ``code`` etc.
Learn more at:

http://web2py.com

Vous pouvez utiliser le paramètre extra de auth.wiki pour passer des règles de rendu extra à l'assistant MARKMIN.

Vous pouvez trouver plus d'informations à propos de la syntaxe MARKMIN dans le chapitre 5.

auth.wiki est plus puissant que les assistants basiques MARKMIN, supportant oembed et les composants.

Vous pouvez utiliser le paramètre env de auth.wiki pour exposer des fonctions à votre wiki. Par exemple :

auth.wiki(env=dict(join=lambda a,b,c:"%s-%s-%s" % (a,b,c)))

vous autorise à utiliser la syntaxe markup :

@(join:1,2,3)

Ceci appelle la fonction de jointure passée en extra avec les paramètres a,b,c=1,2,3 et sera rendue comme 1-2-3.

Protocole Oembed

Vous pouvez entrer (avec couper/coller) n'importe quelle URL dans une page wiki et qui est rendue comme un lien vers l'URL. Il y a des exceptions :

  • Si l'URL a une extension d'image, le lien sera embarqué comme image, <img/>.
  • Si l'URL a une extension audio, le lien est embarqué comme audio HTML5 <audio/>.
  • Si l'URL a une extension vidéo, le lien est embarqué comme vidéo HTML5 <video/>.
  • Si l'URL a une extension PDF ou MS Office, Google Doc Viewer est embarqué, affichant le contenu du document (fonctionne seulement pour les documents publics).
  • Si l'URL pointe vers une page Youtube, une page Vimeo, ou une page Flickr, web2py contacte le service web correspondant et le requête à propos du meilleur moyen pour embarquer la vidéo. Ceci est fait en utilisant le protocole oembed.

Voici une liste complète des formats supportés :

Image (.PNG, .GIF, .JPG, .JPEG)
Audio (.WAV, .OGG, .MP3)
Video (.MOV, .MPE, .MP4, .MPG, .MPG2, .MPEG, .MPEG4, .MOVIE)

Supportés avec Google Doc Viewer :

Microsoft Excel (.XLS and .XLSX)
Microsoft PowerPoint 2007 / 2010 (.PPTX)
Apple Pages (.PAGES)
Adobe PDF (.PDF)
Adobe Illustrator (.AI)
Adobe Photoshop (.PSD)
Autodesk AutoCad (.DXF)
Scalable Vector Graphics (.SVG)
PostScript (.EPS, .PS)
TrueType (.TTF)
xml Paper Specification (.XPS)

Supportés par oembed :

flickr.com
youtube.com
hulu.com
vimeo.com
slideshare.net
qik.com
polleverywhere.com
wordpress.com
revision3.com
viddler.com

L'implémentation est faite dans le fichier gluon.contrib.autolinks et plus précisément dans la fonction expand_one. Vous pouvez étendre le support oembed en ajoutant d'autres services. Ceci est possible en ajoutant une entrée à la liste EMBED_MAPS :

from gluon.contrib.autolinks import EMBED_MAPS
EMBED_MAPS.append((re.compile('http://vimeo.com/\S*'),
                   'http://vimeo.com/api/oembed.json'))

Référencer le contenu du wiki

Si vous créez une page de wiki avec le slug "contactus" vous pouvez vous référer à cette page par

@////contactus

Ici, @//// correspond à

@/app/controller/function/

mais "app", "controller" et "function" sont omis puisque considérés par défaut.

De la même manière vous pouvez utiliser le menu wiki pour uploader un fichier media (par exemple une image) lié à la page. La page "gestion des médias" montrera tous les fichiers uploadés et montrera également l'expression à utiliser pour lier chaque fichier. Si, par exemple, vous uploadez un fichier nommé "test.jpg" avec le titre "beach", l'expression du lien sera de ce type :

@////15/beach.jpg

@//// est le même préfixe que décrit précédemment. 15 est l'id de l'enregistrement stockant le fichier media. beach est le titre. .jpg est l'extension du fichier original.

Si vous coupez/collez @////15/beach.jpg dans les pages du wiki, vous afficherez l'image.

Pensez bien que les fichiers media sont liés à des pages et récupèrent les droits d'accès de la page en question.

Les menus Wiki

Si vous créez une page avec pour slug "wiki-menu" elle sera interprétée comme description du menu. Voici un exemple :

- Home > @////index
- Info > @////info
- web2py > http://www.web2py.com
- - About us > @////aboutus
- - Contact us > @////contactus

Chaque ligne est un object du menu. Nous avons utilisé le double tiret pour les éléments imbriqués. Le symbole > sépare le titre de l'objet du menu de son lien.

Pensez bien que le menu est ajouté à response.menu. Il ne le remplace pas. Le menu [wiki] avec les fonctions de service est automatiquement ajouté.

Fonctions de service

Si, par exemple, vous voulez utiliser le wiki pour créer une sidebar éditable, vous pouvez créer une page avec slug="sidebar" et ensuite l'embarquer dans votre layout.html avec

{{=auth.wiki(slug='sidebar')}}

Notez qu'il n'y a rien de particulier avec le mot "sidebar". N'importe quelle page wiki peut être récupérée et embarquée à n'importe quel endroit dans votre code. Ceci vous permet de mixer et de combiner les fonctionnalités du wiki avec les fonctionnalités web2py.

Notez aussi que auth.wiki('sidebar') revient au même que auth.wiki(slug='sidebar'), puisque le kwarg slug est le premier dans la signature de la méthode. Une syntaxe un peu plus légère et simple peut donc être donnée.

Vous pouvez également embarquer des fonctions spéciales au wiki telles que la recherche par tags :

{{=auth.wiki('_search')}}

ou le tag cloud :

{{=auth.wiki('_cloud')}}

Etendre les possibilités de auth.wiki

Si votre application wiki prête à l'emploi devient plus compliquée, peut être souhaiteriez-vous personnaliser les enregistrement de la base du wiki par l'interface Auth ou exposer les formulaires personnalisés pour les tâches de wiki CRUD. Par exemple, vous pourriez vouloir personnaliser la représentation d'un enregistrement de la table wiki ou ajouter un nouveau champ de validation. Ce n'est pas autorisé par défaut, puisque le modèle du wiki est défini uniquement après que la méthode auth.wiki() ait requêté l'interface du wiki. Pour autoriser l'accès à la configuration spécifique de la base du wiki à travers le modèle de votre application, vous devez ajouter la phrase suivante dans votre fichier de modèle (i.e. db.py)

# Make sure this is called after the auth instance is created
# and before any change to the wiki tables
auth.wiki(resolve=False)

En utilisant la ligne ci-dessus dans votre modèle, les tables wiki seront accessibles (i.e. wiki_page) pour le CRUB personnalisé ou toute autre tâche de base de données.

Notez bien que vous aurez toujours besoin d'appeler auth.wiki() dans le contrôleur ou la vue afin d'exposer l'interface du wiki, puisque le paramètre resolve=False indique à l'objet auth de juste construire le modèle wiki sans aucune autre interface de configuration.

En définissant également "resolve" à False dans l'appel de la méthode, les tables du wiki seront accessibles à travers l'interface par défaut de l'application à <app>/appadmin pour gérer les enregistrements du wiki.

Une autre personnalisation possible est d'ajouter des champs supplémentaires aux tables standard du wiki (de la même manière que pour la table auth_user, comme défini dans le chapitre 9). Voici comment :

# Place this after auth object initialization
auth.settings.extra_fields["wiki_page"] = [Field("ablob", "blob"),]

La ligne ci-dessus ajoute un champ blob à la table wiki_page. Il n'est pas nécessaire d'appeler le code auth.wiki(resolve=False) pour cette option, à moins que vous ayez besoin d'accéder au modèle du wiki pour d'autres personnalisations.

Composants

L'une des plus puissantes fonctions du nouveau web2py consiste en la possibilité d'embarquer une action à l'intérieur d'une autre. Nous l'appelons un composant.

Considérons le modèle suivant :

db.define_table('thing',Field('name',requires=IS_NOT_EMPTY()))

et l'action suivante :

@auth.requires_login()
def manage_things():
    return SQLFORM.grid(db.thing)

Cette action est spéciale puisqu'elle renvoie un widget/assitant et non un dictionnaire d'objets. Maintenant nous pouvons embarquer cette action manage_things dans n'importe quelle vue, avec

{{=LOAD('default','manage_things',ajax=True)}}

Ceci permet au visiteur d'intéragir avec le composant via Ajax sans avoir à recharger la page hôte qui embarque le widget. L'action est appelée via Ajax, hérite du style de la page hôte, et capture toutes les soumissions du formulaire et les messages flash afin qu'ils soient récupérés et traités à l'intérieur de la page courante. En plus de cela, le widget SQLFORM.grid utilise des URLs signées numériquement pour restreindre l'accès. Plus d'information à propos des composants peuvent être trouvés au chapitre 13).

Les comopsants comme celui ci-dessus peuvent être embarqués dans les pages wiki utilisant la syntaxe MARKMIN :

@{component:default/manage_things}

Ceci indique simplement que nous voulons inclure l'action "manage_things" définie dans le contrôleur "default" comme "composant" Ajax.

La plupart des utilisateur pourront construire des applications relativement complètes en utilisant simplement auth.wiki pour créer des pages, des menus et des composants personnalisables embarqués dans les pages du wiki. Les wikis peuvent être pensés comme un mécanisme autorisant les membres d'un groupe à créer des pages, mais peuvent également être pensés comme un moyen de développer des application de manière modulaire.

Plus sur admin

admin

L'interface d'administration fournir des fonctionnalités additionnelles que nous allons rapidement voir ici.

Site

site

Cette page est l'interface principale d'administration de web2py. Elle liste toutes les applications installées sur la gauche, alors qu'il y a certaines actions spéciales possibles via des formulaires sur la droite.

La première d'entre elles montre la version de web2py et propose la mise à jour si des nouvelles versions sont disponibles. Bien sur, vérifiez que vous ayez une sauvegarde fonctionnelle avant d'effectuer une mise à jour ! Il y a ensuite deux autres formulaires qui permettent la création de nouvelles applications (simple ou en utilisant un assistant de création en ligne) en spécifiant leur nom.

Instant Press
Movuca
Le formulaire suivant permet d'uploader une application existante depuis un fichier local ou un une URL distante. Lorsque vous uploadez une application, vous avez besoin de spécifier un nom pour cela (utiliser différents noms vous permet d'installer plusieurs copies de la même application). Vous pouvez essayer par exemple, d'uploader l'application Movuca Social Networking créée par Bruno Rocha :

https://github.com/rochacbruno/Movuca

ou un CMS Instant Press créé par Martin Mulone :

http://code.google.com/p/instant-press/

ou l'un des nombreux exemples d'applications disponibles sur :

http://web2py.com/appliances
Les fichiers web2py sont packagés comme des fichiers .w2p. Ce sont des archives tar. Web2py utilise l'extension .w2p à la place de .tgz pour éviter que le navigateur n'essaie de décompresser lors du téléchargement. Elles peuvent être décompressées manuellement avec la commande tar zxvf [filename] bien que ne soit jamais nécessaire.

image

Une fois l'upload réussi, web2py affiche la somme MD5 du fichier uploadé. Vous pouvez l'utiliser pour vérifier que le fichier n'a pas été corrompu pendant l'upload. Le nom InstantPress apparaîtra alors dans la liste des applications installées.

Si vous lancez web2py depuis les sources et que vous avez gitpython installé (si nécessaire, installez le avec 'easy_install gitpython'), vous pouvez installer des applications directement depuis les dépôts git en utilisant l'URL .git dans le formulaire d'envoi. Dans ce cas, vous pourrez également utiliser l'interface admin pour repousser les changements dans le dépôt, mais c'est une fonction encore expérimentale.

Par exemple, vous pouvez installer localement l'application qui montre ce livre sur le site web2py avec l'URL :

https://github.com/mdipierro/web2py-book.git
Ce dépôt héberge la version courante et mise à jour de ce livre (qui peut donc différer de la version stable que vous pouvez voir sur ce site web). Vous êtes également invités à l'utiliser pour soumettre vos améliorations, corrections et rectifications sous la forme de "pull requests".

Pour chaque application installée, vous pouvez utiliser la page site pour :

  • Aller directement à l'application en cliquant sur son nom.
  • Désinstaller l'application
  • Se rendre à la page about (voir ci-après).
  • Se rendre à la page edit (voir ci-après).
  • Se rendre à la page erreurs (voir ci-après).
  • Nettoyer les fichiers temporaires (sessions, erreurs, et fichiers cache.disk).
  • Tout packager. Ceci retourne un fichier tar contenant une copie complète de l'application. Nous suggérons que vous fassiez un nettoyage des fichiers temporaires avant de la packager.
  • Compiler l'application. S'il n'y a pas d'erreur, cette option va compiler en bytecode tous les modèles, les contrôleurus et les vues. Etant donné que les vues peuvent étendre et inclure d'autres vues dans un arbre, avant d'effectuer la compilation en bytecode, l'arbre de vue pour tout contrôleur est replié en un seul fichier. L'effet marquant est qu'une application compilée en bytecode est plus rapide, puisqu'il n'y a pas de parsing de templates ou de substitutions de chaînes qui puissent survenir à l'exécution.
  • Paquet compilé. Cette option est seulement présente pour les application compilées. Elle permet de packager l'application sans code source pour la distribution sans code source. Notez que Python (comme de nombreux autres langages de programmation) peut techniquement être décompilé ; donc la compilation ne fournit pas une protection complète du code source. Néanmoins, la décompilation peut être difficile et illégale.
  • Retirer compilé. Ceci supprime tout simplement le bytecode compilé des modèles, vues et contrôleurs de l'application. Si l'application a été packagée avec le code source ou éditée localement, il n'y a aucun souci à supprimer les fichier compilés, et l'application continuera à fonctionner. Si l'application a été installée depuis un fichier compilé et packagé, alors il n'y a aucune sécurité, car il n'y a pas de code source pour revenir , et l'application ne fonctionnera plus.
admin.py
Toutes les fonctionnalités disponibles depuis la page d'administration du site sont également accessibles en programmation via l'API définie dans le module gluon/admin.py. Ouvrez simplement un shell Python et importez ce module.

Si un SDK Google App Engine est installé dans la page d'administration site, la page montre un bouton pour pousser vos application vers GAE. Si python-git est installé, il y a également un bouton pour pousser l'application vers Open Shift. Pour installer des application sur Heroku ou tout autre système d'hébergement, vous devrez regarder dans le répertoire "scripts" pour le script associé.

A propos

about
license

L'onglet à propos permet d'éditer la description de l'application et sa licence. Elles sont écrites respectivement dans les fichiers ABOUT et LICENSE dans le dossier applicaion.

image

Vous pouvez utiliser la syntaxe MARKMIN, ou gluon.contrib.markdown.WIKI poru ces fichier comme décrit en référence [markdown2] .

Design

EDIT

Vous avez déjà utilisé la page modifier dans ce chapitre. Nous allons ici voir quelques fonctionnalités complémentaire sur la page modifier.

  • Si vous cliquez sur n'importe quel nom de fichier, vous pouvez voir les contenus avec la syntaxe surlignée (fonction du code).
  • Si vous cliquez sur modifier, vous pouvez éditer le fichier via une interface web.
  • Si vous cliquez sur supprimer, vous pouvez supprimer le fichier (définitivement).
  • Si vous cliquez sur tester, web2py effectuera les tests. Les tests sont écrits par le développeur en utilisant les doctests PYthon, et chaque fonction devrait avoir ses propres tests.
  • Vous pouvez ajouter des fichiers de langue, scanner l'application pour découvrir toutes les chaînes, et éditer les traductions via l'interface web.
  • Si les fichiers statiques sont organisés en dossiers et sous-dossiers, l'affichage de la hiérarchie de dossier peut être alternée en cliquant sur le nom du dossier.

L'image ci-dessous montre la sortie d'une page de test pour l'application welcome.

image

L'image ci-dessous montre l'onglet des langues pour l'application welcome.

image

L'image ci-dessous montre comment éditer un fichier de langue, dans ce cas, le langage "it" (italien) pour l'application welcome.

image

Débogueur web intégré

(requiert Python 2.6 ou supérieur)

L'administration web2py inclut un débogueur web.

debugger

En utilisant l'éditeur web fourni, vous pouvez ajouter des points d'arrêt au code Python et, depuis la console de débogage associée, vous pouvez inspecter les variables systèmes à ces points d'arrêts et relancer l'exécution. Ceci est illustré dans les captures d'acran suivantes : La console interactive sert également de bloc-notes python.

image

Cette fonctionnalité est basée sur le débogueur Qdb créé par Mariano Reingart. Il utilise multiprocessing.connection pour communiquer entre le backend et le frontend, avec un protocole semblable au JSON-RPC. [qdb]

Définir des points d'arrêt par le code
breakpoints

Incluez ceci :

from gluon.debug import dbg

et pour aller au débogueur, mettez ceci à l'endroit souhaité :

dbg.set_trace() 

L'application de debug à un gestionnaire de point d'arrêt.

Notes : web2py ne sait pas si vous avez une fenêtre de debug ouverte dans votre navigateur; l'exécution s'arrête quoi qu'il en soit. Les IDEs ont généralement leur propre débogueur inter-process, e.g. PyCharm ou PyDev. Ils peuvent se plaindre si vous incluez la librairie gluon.

Le Shell Python web

Si vous cliquez sur le lien "shell" en dessous de l'ongler des contrôleurs dans modifier, web2py ouvrira un shell Python web et exécutera les modèles pour l'application courante. Ceci vous permet de parler de manière interactive avec votre application.

image

Faites attention en utilisant le shell web - car différentes requêtes shell peuvent être exécutées dans différents threads. Ceci peut facilement mener à des erreurs, notamment si vous jouez sur la création et les connexions aux bases de données. Pour ce type d'activités (i.e. si vous avez besoin de persistence), il est mieux d'utiliser la ligne de commande python.

Crontab

2galement sous l'onglet contrôleurs dans modifier il y a un lien "crontab". En cliquant sur ce lien, vous aurez la possibilité d'éditer le fichier crontab de web2py. Celui-ci respecte la même syntaxe que le crontab Unix mais ne dépend pas d'Unix. En fait, il nécessite uniquement web2py, et fonctionne sous Windows. Il permet d'enregistrer des actions qui ont besoin d'être exécutées en arrière-plan aux heures planifiées. Pour plus d'information, vous pouvez vous référer au chapitre suivant.

Erreurs

errors

Lorsque vous programmez avec web2py, vous ferez inévitablement des erreurs et introduirez des bugs. web2py vous aide en deux points : 1) il vous permet de créer des tests pour toute fonction qui peut être exécutée dans le navigateur depuis la page modifier; et 2) lorsqu'une erreur se manifeste, un ticket est émis au visiteur et l'erreur est loggée.

Nous allons introduire intentionnellement une erreur dans l'application des images comme montré ci-dessous :

def index():
    images = db().select(db.image.ALL,orderby=db.image.title)
    1/0
    return dict(images=images)

Lorsque vous accédez à l'action index, vous obtenez le ticket suivant :

image

Seuls les administrateurs peuvent accéder au ticket :

image

Le ticket montre la trace (traceback), et le contenu du fichier qui a causé le problème, et l'état complet du système (variables, requête, session, etc...). Si l'erreur survient dans une vue, web2py montre la vue convertie depuis l'HTML en code python. Ceci permet de facilement identifier la structure logique du fichier.

Par défaut, les tickets sont stockés sur le système de fichiers et regroupés par trace. L'interface d'administration fournit des vues agrégées (type de trace et le nombre d'occurence) et une vue détaillée (tous les tickets sont listés par id de ticket). L'administrateur peut basculer entre les deux vues.

Notez que admin montre partout le code syntaxiquement surligné (par exemple, dans les rapports d'erreur, les mots-clés web2py sont montrés en orange). Si vous cliquez sur un mot-clé web2py, vous êtes redirigé à une page de documentation à propos du mot-clé.

Si vous corrigez le bug de division par zéro dans l'action index et que vous en introduisez un dans la vue index :

{{extend 'layout.html'}}

<h1>Current Images</h1>
<ul>
{{for image in images:}}
{{1/0}}
{{=LI(A(image.title, _href=URL("show", args=image.id)))}}
{{pass}}
</ul>

vous obtenez le ticket suivant :

image

Notez que web2py a converti la vue depuis l'HTML en fichier Python, et l'erreur décrite dans le ticket se referre au code Python généré et non au fichier de vue original :

image

Ceci peut sembler perturbant au début, mais en pratique il rend le débogage plus facile, car l'indentation Python souligne la structure logique du code que vous avez défini dans les vues.

Le code est montré à la fin de la même page.

Tous les tickets sont listés sous admin dans la page errors pour chaque application :

image

Mercurial

Mercurial

Si vous exécutez depuis les sources, l'interface d'administration montre un menu supplémentaire appelé "Versioning".

images

Entrer un commentaire et appuyer sur le bouton "commit" dans la page de résultat va effectuer un commit sur l'application courante. Lors du premier commit, un dépôt local Mercurial spécifique à l'application va être créé. En interne, Mercurial stocke les informations à propos des changement que vous faites dans votre code dans un dossier caché ".hg" dans votre sous-dossier de l'application. Toute application a son propre dossier ".hg" et son propre fichier ".hgignore" (disant à Mercurial quels fichiers ignorer). Afin d'utiliser cette fonctionnalité, vous devez avoir les librairies de contrôle de version Mercurial installées (au moins la version 1.9) :

easy_install mercurial

L'interface web Mercurial vous permet de parcourir les précédents commit et différer les fichiers mais nous vous recommandons d'utiliser directement Mercurial depuis le shell ou l'un des nombreux clients graphiques Mercurial puisqu'ils sont plus puissants. Par exemple, ils vont permettre de synchroniser votre application avec un dépôt source distant.

Vous pouvez en lire plus sur Mercurial ici :

http://mercurial.selenic.com/

Integration GIT

git

L'application admin inclut également l'intégration git. Les librairies git Python sont requises :

pip install gitpython

et ensuite, par application, vous devez cloner ou configurer un dépôt git.

Après ces étapes, le menu de gestion pour chaque application gérée par git montrera les actions push/pull de git. Les application qui ne sont pas gérées par git sont ignorées. Vous pouvez effectuer des actions pull/push depuis le dépôt distant par défaut.

Assistant d'Application (expérimental)

L'interface admin inclut un wizard qui peut vous aider à créer une nouvelle application. Vous pouvez accéder au wizard depuis la page "sites" comme montré ci-dessous.

image

Le wizard va vous guider à travers les différentes étapes nécessaires pour la création d'une nouvelle application.

  • Choisissez un nom pour l'application
  • Configurez l'application et choisissez les plugins requis
  • Construisez les modèles requis (ceci crééra les pages CRUD pour chaque modèle)
  • Vous permettre d'éditer les vues de ces pages en utilisant la syntaxe MARKMIN

L'image ci-dessous montre la seconde étape du process.

image

Vous pouvez voir un menu déroulant pour choisir un plugin layout (depuis web2py.com/layouts), un menu déroulant à choix multiple pour vérifier les autres plugins (depuis web2py.com/plugins) et un champ "login config" où vous pouvez mettre l'ensemble "domain:key".

Les autres étapes sont plutôt assez explicites.

Le wizard fonctionne bien pour ce qu'il doit mais est considéré comme une fonctionnalité expérimentale pour deux raisons :

  • Les application créées avec le wizard et éditées manuellement, ne peuvent pas être modifiées plus tard par le wizard.
  • L'interface du wizard va changer dans le temps en incluant le support de plus de fonctionnalités et un développement visuel aisé.

Quel que soit le cas, le wizard est un outil clé en main pour le prototypage rapide et peut être utilisé pour amorcer une nouvelle application avec un layout alternatif et des plugins optionnels.

Configurer admin

Normalement il n'y a pas d'utilité à effectuer quelconque configuration sur admin mais quelques personnalisations sont possibles. Après vous être identifié dans l'administration, vous pouvez éditer le fichier de configuration admin via l'URL :

http://127.0.0.1:8000/admin/default/edit/admin/models/0.py

Notez que admin peut être édité lui-même. En fait, admin est une application comme une autre.

Le fichier "0.py" est plus ou moins auto-documenté, peu importe, voici la plupart des personnalisations possibles :

GAE_APPCFG = os.path.abspath(os.path.join('/usr/local/bin/appcfg.py'))

Ceci devrait pointer vers le fichier "appcfg.py" fourni avec le SDK Google App Engine. Si vous avez le SDK, vous pouvez vouloir change ces paramètres de configuration à la bonne valeur. Ceci vous permettra de déployer vers GAE depuis l'interface d'administration.

DEMO_MODE

Vous pouvez également mettre l'admin web2py en mode démo :

DEMO_MODE = True
FILTER_APPS = ['welcome']

Et seules les applications listées dans FILTER_APPS seront accessibles et uniquement en mode lecture seule.

MULTI_USER_MODE
virtual laboratory

Si vous êtes un professeur et que vous souhaitez présenter l'interface d'administration aux élèves afin qu'ils puissent partager une interface d'administration pour leurs projets (un labo virtuel par exemple), vous pouvez le faire en définissant :

MULTI_USER_MODE = True

Par ce moyen, les étudiants auront besoin de se connecter et auront uniquement accès à leurs propres applications via admin. Vous, en tant que premier utilisateur/professeur pourrez accéder à l'ensemble des applications.

En mode multi-utilisateur, vous pouvez enregistrer les étudiants en utilisant le lien "enregistrement en vrac" et les gérer en utilisant le lien "gérer étudiants". Le système enregistre également les connexions des étudiants et le nombre de lignes de code qu'ils ajoutent/suppriment depuis/vers leur code. Les données sont présentées à l'administrateur en tant que graphes sous la page "à propos" de l'application.

Pensez bien que ce mécanisme suppose encore que tous les utilisateurs sont vérifiés. Toutes les applications créées sous admin s'exécutent avec les mêmes identifiants sur le même système de fichier. Il est possible pour une application créée par un étudiant d'accéder aux données et aux sources d'une application créée par un autre étudiant. Il est également possible pour un étudiant de créer une application qui bloque le serveur.

admin mobile

Notez que l'application admin inclut le plugin "plugin_jqmobile" qui contient jQuery Mobile. Si l'administration est accédée par un périphérique mobile, la détection est automatique par web2py et l'interface est affichée en utilisant un layout mobile :

image

En savoir plus sur appadmin

appadmin

appadmin n'est pas prévu pour être présentée au public. Il n'est prévu que pour vous aider en fournissant un moyen simple d'accéder à la base de données. Ceci consiste en deux fichiers uniquement : un contrôleur "appadmin.py" et une vue "appadmin.html" qui sont utilisés par toutes les actions dans le contrôleur.

Le contrôleur appadmin est relativement petit et facile à lire ; il fournit un exemple pour définir une interface de base de données.

appadmin montre quelles bases de données sont disponibles et quelles tables existent dans chaque base. Vous pouvez insérer des enregistrement et lister tous les enregistrement pour chaque table individuellement. appadmin pagine jusqu'à 100 enregistrements en sortie à la fois.

Une fois qu'un ensemble d'enregistrements est sélectionné, l'en-tête des pages change, vous autorisant à mettre à jour ou supprimer les enregistrement sélectionnés.

Pour mettre à jour les enregistrement, entrez une description SQL dans le champ Query string :

title = 'test'

où les valeurs de la chaîne peuvent être encapsulées avec de simples quotes. Plusieurs champs peuvent être séparés par des virgules.

Pour supprimer un enregistrement, cliquez sur la checkbox correspondante pour confirmer que vous êtes sûr.

appadmin peut également effectuer des jointures si le SQL FILTER contient une condition SQL qui entraîne deux tables ou plus. Par exemple, essayez :

db.image.id == db.post.image_id

web2py passe ceci à la couche de BDD (DAL), et comprend que les liens de la requête lient deux tables; d'où, les deux tables sont sélectionnées avec un INNER JOIN. Voici la sortie :

image

Si vous cliquez sur le numéro d'un champ id, vous obtenez une page d'édition pour l'enregistrement avec l'id correspondant.

Si vous cliquez sur le numéro d'un champ référence, vous obtenez une page d'édition pour l'enregistrement référencé.

Vous ne pouvez pas mettre à jour ou supprimer des lignes sélectionnées par une jointure, car elles entraînent des enregistrements depuis plusieurs tables et ceci serait donc ambigu.

En plus des possibilités d'administration de sa base de données, appadmin vous permet également de voir les détails à propos des contenus du cache de l'application (via /yourapp/appadmin/cache) aussi bien que les contenus des objets courants request, response, et session (via /yourapp/appadmin/state).

appadmin remplace response.menu avec son propre menu, qui fournit les liens vers la page modifier de l'application, la page db (administration de la base de données), la page state, et la page cache. Si le layout de votre application ne génère pas de menu en utilisant response.menu, alros vous ne verrez pas le menu appadmin. Dans ce cas, vous pouvez modifier manuellement le fichier appadmin.html et ajouter {{=MENU(response.menu)}} pour afficher le menu.

 top