Magazine Internet

Intro à Plone 3 : partie 2

Publié le 16 février 2009 par Mikebrant

Seconde partie de l'intro à Plone 3 .
Si vous voulez connaitre Archetypes ou si vous n'avez rien d'autre à faire.

Si vous n'avez pas lu la 1°partie, je vous invite à le faire tout de suite : il y a quelques notions (  c'est quoi un profil, comment ca fonctionne, etc...) à connaitre.
Et son code se trouve toujours là.

À partir de cette partie on va s'occuper du paquet jesuisunconnard.metier .
C'est lui qui va nous permettre réellement de faire de notre site Plone le site jesuisunconnard.
En effet, on va y créer:
- nos Vues (nos nouvelles pages : À propos, Raconte ton histoire, etc...).
- nos contenus ( Histoire et DossierHistoire) via Archetypes.
- etc ...

Mais dans cette partie, on va "seulement" créer nos deux classes.

Bon bah, c'est parti mon kiki .

Création du paquet

cd src/ & paster create -t plone jesuisunconnard.metier


Namespace du paquet : jesuisunconnard
Le nom du paquet : metier
Zope2Product : True

Donc maintenant il faut mettre à jour le buildout.cfg :

[buildout]
parts =
  plone
[...]
eggs =
  elementtree
  plonetheme.keepitsimple
  jesuisunconnard.politique
  jesuisunconnard.metier
develop =
    src/jesuisunconnard.politique
    src/jesuisunconnard.metier


Bon, maintenant que le paquet est créé on peut passer aux choses sérieuses.

Interfaces

On va donc commencer par créer nos interfaces .
Elles vont servir à décrire comment nos classes les implémentant doivent se comporter.

fichier src/jesuisunconnard.metier/jesuisunconnard/metier/interfaces.py :

#-*- coding:Utf-8 -*-
from zope.interface import Interface
from zope import schema
from zope.app.container.constraints import contains
from jesuisunconnard.metier import JesuisunconnardMessageFactory as _
class IHistoire(Interface):
  """ une histoire """
   
  nom = schema.TextLine(title=_(u"Nom"), required=True)
  texte = schema.SourceText(title=_(u"Histoire"),description=_(u""), required=True) 
class IDossierHistoire(Interface):
  """ dossier contenant les histoires """
   
  contains('jesuisunconnard.metier.interfaces.IHistoire')
  titre = schema.TextLine(title=_(u"Titre"), required=True)


(bon ok le bleu et le orange ça ne va pas du tout ensemble...)
le _ va nous servir pour l'i18n, je ne vais pas en parler maintenant(on verra ça dans une des dernières parties je pense), mais il va permettre de marquer nos chaines en tant que chaines à traduire.

Ici, on déclare 2 classes: IHistoire et IDossierHistoire qui deviennent des interfaces en héritant d'Interface.
IHistoire a 2 attributs:
nom : l'auteur de l'histoire,
texte : l'histoire.
 
IDossierHistoire a 1 seul attribut:
titre  : le titre du dossier.
contains() indique que nos dossierHistoire ne devront contenir exclusivement que des objets implémentant IHistoire, cad uniquement des Histoire.

Quand on voudra écrire/éditer une Histoire :
le nom : devra etre une chaine représentée par un "<input type="text">",
le texte : devra etre une chaine représentée par un "<textarea>".

Quand on voudra ajouter un DossierHistoire:
le titre : devra etre une chaine représentée par un "<input type="text">".

Et c'est fini pour nos interfaces.

Contenu

Maintenant on va s'occuper des classes les implémentant : Histoire et DossierHistoire qui vont etre le "contenu" de notre site.

On crée un dossier content dans src/jesuisunconnard.metier/jesuisunconnard/metier, agrémenté d'un fichier __init__.py .

Pour créer nos classes, on va se servir du framework Archetypes, et ca va se passer en deux temps :
- on va d'abord créer le schéma
- puis notre classe.
et Archetypes se chargera du reste ( sauver/supprimer les objets, nous créer les pages d'affichage/édition/suppression, et j'en passe) ! Si c'est pas beau çà ?!

Le schéma : c'est là où on va définir les champs que l'on souhaite avoir pour notre classe .
La classe : on va seulement implémenter notre interface, mettre dans un attribut notre schéma, et ce sera fini (enfin il y aura 2-3 bricoles à faire, mais c'est tout).
Bon, c'est pas très parlant, donc on va passer à la pratique.
 On a notre classe Histoire ; lorsque l'on voudra en créer/éditer une, il nous faudra  logiquement:
- un champs <input type="text"> pour l'auteur de l'histoire
- un champs <textarea>pour le texte de l'histoire 
OK  ? (en même temps vous avez pas le choix)
Et bien on aura juste à définir ces deux champs dans le schéma de notre Histoire.

Ce qui donnera pour le schéma:

from Products.Archetypes import atapi
from Products.ATContentTypes.content import schemata
from Products.ATContentTypes.content.schemata import finalizeATCTSchema
schemaHistoire = schemata.ATContentTypeSchema.copy() + atapi.Schema((
   atapi.StringField('nom',
   required=True,
   searchable=True,
   storage=atapi.AnnotationStorage(),
   widget=atapi.StringWidget(label=_(u"Nom"),
   description=_(u""))
   ),
   atapi.TextField('histoire',
   required=True,
   searchable=True,
   storage=atapi.AnnotationStorage(),
   default_output_type='text/x-html-safe',
   widget=atapi.RichWidget(label=_(u"Histoire"),
   description=_(u""),
   rows=45,
   allow_file_upload=False),
   ),
  
   ))
finalizeATCTSchema(schemaHistoire, folderish=False, moveDiscussion=False)


On doit commence par copier le schéma ATContentTypeSchema.
Il va nous copier le schéma de base(création d'un champs id, title, description, etc...).
Ensuite on rajoute nos 2 champs :
- nom : un simple StringField, vu que l'on souhaite quelquechose du type <input type="text">
- histoire : un TextField, vu que l'on souhaite quelquechose du type <textarea>
Les deux sont obligatoires ( nb : à remplir ) et seront indexés.
storage : à ne pas oublier, il faut mettre AnnotationStorage() (afin d' éviter des problèmes de namespace).
defalut_output_type :  'text/x-html-safe', afin d'échapper les caractères spéciaux, pour éviter le XSS ( et oui ya des cons de partout)
widget : Ce sera le widget que l'on verra sur la page d'ajout par exemple, pour représenter le champs :
- pour le nom : ,ce sera StringWidget, c'est à dire un <input type="text">
- pour l'histoire : ce sera RichWidget, c'est à dire un WYSIWYG ( un textarea amélioré).

Là  quelquechose vous titille l"occiput :
"mais il sert à rien le paramètre widget puisqu'en fonction du champs on sait de suite le widget qui va le représenter !"
Et bien pas tout à fait, puisque un champs peut avoir plusieurs widgets de disponibles, par exempleTextField peut avoir comme widget RichWidget  ou bien TextAreaWidget (un simple textarea).

Enfin,on appelle la fonction finalizeATCTSchema().
Elle va juste servir à finaliser la création de notre nouveau schéma, ya pas réellement besoin d'en savoir plus (elle va réordonner les champs, et en renommer certains).

Et voilà, l'explication du schéma est finie.

Place à la classe :

from jesuisunconnard.metier.config import PROJECTNAME
from jesuisunconnard.metier import JesuisunconnardMessageFactory as _
from jesuisunconnard.metier.interfaces import IHistoire

class Histoire(base.ATCTContent):
""" une histoire """
implements( IHistoire)
portal_type = "Histoire"
_at_rename_after_creation = True
schema = HistoireSchema
nom = atapi.ATFieldProperty("nom")
texte = atapi.ATFieldProperty("histoire")
atapi.registerType(Histoire, PROJECTNAME)


Histoire doit hériter de ATCTContent  puisque c'est un contenu (ça va lui permettre d'accéder à des méthodes&co de contenus).
Ensuite, on implémente IHistoire, normal.
Puis on déclare le type plone de notre classe via portal_type(les différents types plone sont accessibles à partir de portal_types).
( À noter que l'on déclare juste un type, il faut aussi le définir. Cela se fera via un fichier xml dans le futur profil de notre paquet métier )
_at_rename_after_creation : afin de renommer automatiquement notre objet si son id est foireux( doublon, caractère(s) interdit(s), etc...)

Notre schemaHistoire vient se greffer à notre classe via l'attribut schema.

Et puis on déclare nos 2 attributs nom et texte puisqu'ils sont présents dans l'interface
via ATFieldProperty(), nom et texte vont prendre respectivement les valeurs des champs nom et histoire et inversement proportionnel, les champs nom et histoire vont contenir les valeurs des attributs nom et texte.
Et là vous pouvez vous dire (en tout cas moi, ça m'avait choqué):
"mais pourquoi doit-on "re-déclarer" nos 2 attributs alors qu'on les a mis dans le schéma ? nom d'une pipe!"
Bah parce que :
- le schéma peut comporter moult champs que l'on ne souhaite pas forcément sauvegarder dans notre objet (par exemple, on se fout royalement du champs description, présent par défaut dans le schéma).
- on implémente une interface, donc il faut implémenter "explicitement" ses attributs&co (meme si python n'en a rien à foutre qu'un attribut manque à l'appel).
- pour accéder plus facilement aux valeurs
- etc...

 Et on termine par l'incontournable registerType() !
Qui va enregistrer notre classe Histoire, et lui  ajouter 3 méthodes pour chaque champs:
- un accesseur == getter (pour accéder à la valeur)
- un accesseur d'édition == édition ( pour éditer la valeur)
- un mutateur == setter (pour mettre une valeur)

 PROJECTNAME va contenir le nom de notre paquet, que l'on définira ultérieurement dans un futur fichier config.py

 Et voilà on a notre classe toute belle, pas chère, et prete à travailler!

La vérité

Nous on va  etre intelligent pour une fois.
Je vous ai dit, que les schémas de nos classes contiennentt des champs par défaut dont title et description.
Alors pourquoi re-créer des champs, on a qu'à s'en servir, non ?
Pour accéder à un champs d'un schéma c'est comme ca :

monSchema['monChamps'].monAttribut/methode


Pour notre classe DossierHistoire, on a besoin que d'un seul attribut donc d'un seul champs  et il faut que ce soit un simple <input type="text"> donc on va se servir du champs title .
Voici jesuisunconnard.metier/jesuisunconnard/metier/content/DossierHistoire.py :

#-*-coding:Utf-8-*-

from zope.interface import implements
from Products.Archetypes import atapi
from Products.ATContentTypes.content import folder
from Products.ATContentTypes.content import schemata
from Products.ATContentTypes.content.schemata import finalizeATCTSchema

from jesuisunconnard.metier.config import PROJECTNAME
from jesuisunconnard.metier import JesuisunconnardMessageFactory as _
from jesuisunconnard.metier.interfaces import IDossierHistoire


DossierHistoireSchema = folder.ATFolderSchema.copy()

DossierHistoireSchema['title'].storage = atapi.AnnotationStorage()
DossierHistoireSchema['title'].widget.label = _(u"Titre")
DossierHistoireSchema['title'].widget.description = _(u"")


finalizeATCTSchema(DossierHistoireSchema, folderish=True, moveDiscussion=False)

class DossierHistoire(folder.ATFolder):
""" Le Dossier """
implements(IDossierHistoire)
portal_type = "DossierHistoire"
_at_rename_after_creation = True
schema = DossierHistoireSchema
titre = atapi.ATFieldProperty("title")
atapi.registerType(DossierHistoire, PROJECTNAME)


On copie ATFolderSchema et non pas ATContentTypeSchema puisque c'est, certes un contenu, mais aussi un dossier . 
Tout comme la classe DossierHistoire hérite de ATFolder et non pas de ATCTContent puisque c'est un dossier.

Pour notre classe Histoire on va servir du champs title et description ( qui va nous fournir, quant à lui, un WYSIWYG).
Voici jesuisunconnard.metier/jesuisunconnard/metier/content/Histoire.py :

#-*-coding:Utf-8-*-

from zope.interface import implements
from Products.Archetypes import atapi
from Products.ATContentTypes.content import base
from Products.ATContentTypes.content import schemata
from Products.ATContentTypes.content.schemata import finalizeATCTSchema

from jesuisunconnard.metier.config import PROJECTNAME
from jesuisunconnard.metier import JesuisunconnardMessageFactory as _
from jesuisunconnard.metier.interfaces import IHistoire


HistoireSchema = schemata.ATContentTypeSchema.copy()

HistoireSchema['title'].searchable = True
HistoireSchema['title'].storage = atapi.AnnotationStorage()
HistoireSchema['title'].default_output_type = 'text/x-html-safe'
HistoireSchema['title'].widget.label = _(u"Pseudo")
HistoireSchema['title'].widget.description = _(u"")

HistoireSchema['description'].searchable = True
HistoireSchema['description'].required = True
HistoireSchema['description'].storage = atapi.AnnotationStorage()
HistoireSchema['description'].default_output_type = 'text/x-html-safe'
HistoireSchema['description'].widget.label = _(u"Description")
HistoireSchema['description'].widget.description = _(u"")

finalizeATCTSchema(HistoireSchema, folderish=False, moveDiscussion=False)

class Histoire(base.ATCTContent):
""" une histoire """
implements(IHistoire)
portal_type = "Histoire"
_at_rename_after_creation = True
schema = HistoireSchema
nom = atapi.ATFieldProperty("title")
texte = atapi.ATFieldProperty("description")
atapi.registerType(Histoire, PROJECTNAME)


Et voilà, c'est terminé !

Alors dans la 1° partie, j'avais écrit qu'en une semaine, le tuto serait terminé,
et bien je me suis trompé ! 
En une semaine, je viens à peine de terminer cette partie,
et donc, je m'en excuse (enfin aux rares qui suivent mes articles)
Mais si j'ai mis si longtemps, c'est pour diverses raisons :
- coupures de courant
- coupures du net ( vidéotron....)
- je met véritablement beaucoup de temps pour écrire un article, meme trop
- et un grave manque de motivation, j'ai pas vraiment aimé l'écrire sauf une fois fini( faut dire que j'ai passé réellement 3 jours à savoir comment présenter Archetypes)

Alors il me semble que cette partie soit moins bien faite que la première, tantpis, enfin si vous avez des critiques n'hésitez pas
Ce sera une partie par semaine dorénavant.
Donc Rendez-vous à d'ici dimanche pour la 3° partie (configure.zcml de content et fichier config.py en perspective et que sais-je), enfin si l'envie m'endive...


Retour à La Une de Logo Paperblog

A propos de l’auteur


Mikebrant 9 partages Voir son profil
Voir son blog

l'auteur n'a pas encore renseigné son compte l'auteur n'a pas encore renseigné son compte

Dossier Paperblog