Plone pour les développeurs

publicité
Plone pour les développeurs
Version 1.0.0
plone-fr
17-April-2017
Table des matières
1
2
3
Rappel sur Python
1.1 Définition . . . . . . . . . . . . .
1.2 Savoir . . . . . . . . . . . . . . .
1.3 Aide intégrée à Python . . . . . .
1.4 Attributs par défaut d’une classe .
1.5 set . . . . . . . . . . . . . . . . .
1.6 Le __call__ d’une classe . . . . .
1.7 Manipulation de chaines . . . . .
1.8 Différence entre unicode et string
1.9 Comprehensive list et generator .
1.10 Decorateur . . . . . . . . . . . .
1.11 Methode de classe . . . . . . . .
1.12 Property . . . . . . . . . . . . .
1.13 Template . . . . . . . . . . . . .
1.14 Debogueur pdb . . . . . . . . . .
1.15 Profiler . . . . . . . . . . . . . .
1.16 Ressources . . . . . . . . . . . .
1.17 Exercice . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
3
3
3
3
4
4
4
5
5
5
6
7
7
7
8
8
8
9
Presentation de Python
2.1 Introduction . . . . . . . . . . . . . . . . . . . .
2.2 Installation . . . . . . . . . . . . . . . . . . . . .
2.3 La console Python . . . . . . . . . . . . . . . . .
2.4 Le contrôle du flux d’instructions . . . . . . . . .
2.5 Les conteneurs standard . . . . . . . . . . . . . .
2.6 Fonctions et espaces de noms . . . . . . . . . . .
2.7 Modules et packages . . . . . . . . . . . . . . . .
2.8 La programmation Orientée Objet . . . . . . . . .
2.9 Quelques Techniques avancées de programmation
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
11
11
15
15
30
36
48
54
59
67
ZODB - Une Base de données objet native pour python
3.1 Avertissement . . . . . . . . . . . . . . . . . . . .
3.2 Licence . . . . . . . . . . . . . . . . . . . . . . . .
3.3 Résumé . . . . . . . . . . . . . . . . . . . . . . . .
3.4 Documentation . . . . . . . . . . . . . . . . . . . .
3.5 Téléchargements . . . . . . . . . . . . . . . . . . .
3.6 Communauté et contribution . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
77
77
77
77
78
87
88
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
i
4
5
6
7
8
9
Le guide complet de l’Architecture de Composants de Zope
4.1 Mise en route . . . . . . . . . . . . . . . . . . . . . . .
4.2 Un exemple . . . . . . . . . . . . . . . . . . . . . . . .
4.3 Interfaces . . . . . . . . . . . . . . . . . . . . . . . . .
4.4 Adaptateurs . . . . . . . . . . . . . . . . . . . . . . . .
4.5 Utilitaires . . . . . . . . . . . . . . . . . . . . . . . . .
4.6 Adaptateurs avancés . . . . . . . . . . . . . . . . . . .
4.7 Usage de la ZCA dans Zope . . . . . . . . . . . . . . .
4.8 Étude de cas . . . . . . . . . . . . . . . . . . . . . . .
4.9 Reference . . . . . . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
89
89
93
96
100
104
106
110
114
125
Le langage ZCML
5.1 Introduction au ZCML . . . .
5.2 Traitement des fichiers ZCML
5.3 Les directives ZCML . . . . .
5.4 Pour aller plus loin . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
163
163
164
166
168
Utiliser la ZCA avec Grok
6.1 Introduction . . . . . .
6.2 Installation de five.grok
6.3 Utiliser Grok . . . . . .
6.4 Ressources . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
173
173
173
174
175
Modélisation UML
7.1 Objectifs . . . . . . . . . . . . . . .
7.2 Savoir . . . . . . . . . . . . . . . . .
7.3 Qu’est ce qu’UML . . . . . . . . . .
7.4 Les éléments . . . . . . . . . . . . .
7.5 Les relations . . . . . . . . . . . . .
7.6 Les diagrammes que nous utiliserons
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
177
177
177
177
178
183
184
Génerer un composant Plone avec ArgoUML et ArchgenXML
8.1 Définition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
8.2 Savoir . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
8.3 Installation d’ArgoUML . . . . . . . . . . . . . . . . . . . . . .
8.4 Installation de ArchGenXML . . . . . . . . . . . . . . . . . . .
8.5 Configuration d’ArgoUML pour utiliser le profil d’ArchGenXML
8.6 Création du buildout de notre module . . . . . . . . . . . . . . .
8.7 Création du plan documentaire . . . . . . . . . . . . . . . . . . .
8.8 Création des workflows . . . . . . . . . . . . . . . . . . . . . .
8.9 exercice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
189
189
189
189
189
190
192
192
199
212
Nouvelles fonctionnalités de ArchGenXML
9.1 Définition . . . . . . . . . . . . . . . .
9.2 Savoir . . . . . . . . . . . . . . . . . .
9.3 Modélisation d’un portlet . . . . . . .
9.4 Modélisation de vue zope 3 . . . . . .
9.5 Exercice . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
10 Création d’un thème Plone
10.1 Installation de Gloworm . . . . . .
10.2 Création d’un theme . . . . . . . .
10.3 Exercice 1 . . . . . . . . . . . . .
10.4 Exercice 2 : Création d’une viewlet
10.5 Portlets . . . . . . . . . . . . . . .
10.6 GenericSetup . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
213
213
213
213
213
214
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
215
215
215
216
216
217
219
11 Les viewlets
221
11.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 221
ii
11.2
11.3
11.4
11.5
11.6
Gestion interactive des viewlets
L’enregistrement en ZCML . .
Le step GenericSetup . . . . . .
Exemple . . . . . . . . . . . .
Pour aller plus loin . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
222
223
226
227
228
12 Les portlets
12.1 Personnalisation . . . . . . . . . . . . . . . . . . . . .
12.2 Portlets disponibles en standard . . . . . . . . . . . . .
12.3 Retour sur le paramétrage d’une portlet . . . . . . . . .
12.4 Habillage graphique . . . . . . . . . . . . . . . . . . .
12.5 Export/Import Generic setup du paramétrage des portlets
12.6 Création d’une portlet personnelle . . . . . . . . . . . .
12.7 Et le résultat... . . . . . . . . . . . . . . . . . . . . . .
12.8 Créer votre propre portlet . . . . . . . . . . . . . . . .
12.9 Pour aller plus loin . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
231
231
232
234
234
234
234
237
239
239
13 Les vues standard
13.1 Qu’est-ce que c’est ? . . . . . . .
13.2 Pourquoi utiliser ces vues ? . . .
13.3 Quelles vues sont proposées ? . .
13.4 Accéder aux ressources des vues .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
241
241
241
241
242
14 Internationalisation d’un composant
14.1 Exemples de la formation . . . . .
14.2 Internationaliser un composant . . .
14.3 Localiser un composant . . . . . .
14.4 La syntaxe des po en quelques mots
14.5 Modifier les traductions de Plone .
14.6 Mise à jour des traductions . . . . .
14.7 Ressources . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
249
249
250
253
254
254
255
255
15 Gestion des utilisateurs avec PlonePAS
15.1 Présentation . . . . . . . . . . . .
15.2 Architecture fonctionnelle . . . . .
15.3 PlonePAS en ZMI . . . . . . . . .
15.4 Les plugins standard . . . . . . . .
15.5 Les plugins de tierce partie . . . . .
15.6 Votre plugin . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
257
257
258
259
264
264
264
16 L’environnement de développement
16.1 Installation de Python 2.4 . . . . . . . . . . .
16.2 Configuration de l’éditeur vim . . . . . . . . .
16.3 Configuration de l’autocomplétion dans Python
16.4 Activation de la recherche dans l’historique . .
16.5 Installation de ArgoUML et ArchGenXML . .
16.6 Checkout svn.zope.org . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
265
265
265
265
266
266
267
17 La gestion des sources avec subversion
17.1 Savoir . . . . . . . . . . . . . . . . . .
17.2 Création d’un dépôt via Apache . . . .
17.3 Installation du client subversion . . . .
17.4 Administration d’un dépôt subversion .
17.5 Mettre à jour la branche de production .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
269
269
269
270
270
270
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
18 Migration des composants développés pour Plone 2.5
273
18.1 Définition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 273
18.2 Savoir . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 273
18.3 Outils . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 273
iii
18.4
18.5
18.6
18.7
18.8
18.9
18.10
18.11
Outils pour la preview des documents . . . .
Installation du produit AttachmentField . . .
Génération du produit AgileKnowledgeBase
Configuration du calendrier . . . . . . . . .
Création d’un nouveau Plone Site . . . . . .
Migration du contenu . . . . . . . . . . . .
Ajout nouvelles fonctionnalités . . . . . . .
Exercice . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
273
274
274
275
275
275
276
277
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
279
279
279
279
280
20 Programmation dirigée par la documentation
20.1 Définition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
20.2 Savoir . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
20.3 Exercice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
281
281
281
281
21 Sensibilisation aux méthodes agiles
21.1 Savoir . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
21.2 Ressources . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
21.3 Exercice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
283
283
283
283
22 Glossaire
285
23 Indexes et tables
291
24 Contributeurs
293
25 Evolutions planifiées de ce document
295
Index
297
19 Programmation dirigée par les tests
19.1 Définition . . . . . . . . . . . .
19.2 Savoir . . . . . . . . . . . . . .
19.3 Test unitaire . . . . . . . . . .
19.4 Exercice . . . . . . . . . . . .
iv
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Plone pour les développeurs, Version 1.0.0
Astuce : Pour voir cette documentation dans son site original et accéder à son moteur de recherche, veuillez vous
rendre ici.
Lire la dernière version de ce document au format PDF
Ce document constitué à partir de contributions de la communauté Plone francophone, notamment :
– Le manuel de formation intégrateur Plone par Vincent Fretin (Ecréall)
– Le cours sur le développement pour Plone par Michael Launay (Ecréall)
– La traduction par Christophe Combelles du livre de Baiju Muthukadan sur la Zope Component Architecture
– Les travaux, les relectures et les très nombreuses contributions de Gilles Lenfant et Thomas Desvenain
– Le cours sur Python 3 de Robert Cordeau
Cette création est mise à disposition selon le Contrat Paternité-Partage des Conditions Initiales à l’Identique 2.0
France disponible en ligne http ://creativecommons.org/licenses/by-sa/2.0/fr/ ou par courrier postal à Creative
Commons, 171 Second Street, Suite 300, San Francisco, California 94105, USA.
Table des matières
1
Plone pour les développeurs, Version 1.0.0
2
Table des matières
CHAPITRE 1
Rappel sur Python
1.1 Définition
Le but est de maîtriser le langage python, de comprendre et respecter les standards utilisés par la communauté
Plone.
1.2 Savoir
–
–
–
–
–
–
–
–
–
–
Installation de python
Votre premier programme Python
Types prédéfinis
Le pouvoir de l’introspection
Les objets et l’orienté objet
Traitement des exceptions et utilisation de fichiers
Expressions régulières
Traitement du HTML
Traitement de données XML
Des scripts et des flots de données (streams)
1.3 Aide intégrée à Python
Dans un Python interactif, vous pouvez lire la documentation d’un module avec la commande help, par exemple
si vous voulez la documentation sur le module os.
>>> help("os")
ou bien
>>> import os
>>> help(os)
Il existe aussi une commande spéciale pour avoir de la documentation sur un sujet donné.
>>> help(’topics’)
3
Plone pour les développeurs, Version 1.0.0
1.4 Attributs par défaut d’une classe
Dans un shell Python :
>>> class MyClass(object):
...
myattr = 2
...
>>> a = MyClass()
>>> a.myattr
2
>>> a.__dict__
{}
>>> a.myattr = 3
>>> a.myattr
3
>>> a.__dict__
{’myattr’: 3}
>>> b = MyClass()
>>> b.myattr
2
>>> MyClass.myattr = 4
>>> b.myattr
4
>>> a.myattr
3
1.5 set
C’est une liste à éléments uniques :
>>> s set([1,2,3,1,5])
>>> s = set([1,2,3,1,5])
>>> s
set([1, 2, 3, 5])
>>> s.add(6)
>>> s
set([1, 2, 3, 5, 6])
>>> s2 = frozenset([1,2])
>>> s2.add(3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: ’frozenset’ object has no attribute ’add’
>>>
1.6 Le __call__ d’une classe
Une classe peut être appelé :
>>> class A(object):
...
def __call__(self):
...
print "bonjour"
...
>>> a = A()
>>> a()
bonjour
4
Chapitre 1. Rappel sur Python
Plone pour les développeurs, Version 1.0.0
1.7 Manipulation de chaines
>>> "bonJour".capitalize()
’Bonjour’
>>> "bonjour"[0]
’b’
>>> "bonjour"[-1]
’r’
>>> "bonjour"[0:3]
’bon’
>>> "bonjour"[:3]
’bon’
>>> "bonjour"[1:3]
’on’
>>> "bonjour"[-2:-1]
’u’
>>> "bonjour"[-2:]
’ur’
>>> "bonjour"[1:5:2]
’oj’
1.8 Différence entre unicode et string
– http ://www.stereoplex.com/two-voices/python-unicode-and-unicodedecodeerror
1.9 Comprehensive list et generator
>>> [i*2 for i in range(10)]
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
>>> (i*2 for i in range(10))
<generator object at 0x1745ef0>
>>> g = (i*2 for i in range(10))
>>> g.next()
0
>>> g.next()
2
>>> g.next()
4
>>> g.next()
6
>>> g.next()
8
>>> for i in g:
...
print i
...
10
12
14
16
18
>>> g.next()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
1.7. Manipulation de chaines
5
Plone pour les développeurs, Version 1.0.0
1.10 Decorateur
Copiez cet exemple dans un fichier test.py :
import functools
def log_calls_decorator(f):
"""My decorator.
"""
@functools.wraps(f)
def wrapped(*args, **kwargs):
"""My wrapped function.
"""
call_string = "%s called with *args: %r, **kwargs: %r " % (f.__name__, args, kwargs)
try:
retval = f(*args, **kwargs)
call_string += " --> " + repr(retval)
return retval
finally:
print call_string
return wrapped
@log_calls_decorator
def add(operand1, operand2):
"""Return the sum of operand1 and operand2.
"""
return operand1 + operand2
La construction est équivalente à :
add = log_calls_decorator(add)
Lancez le :
python -i test.py
>>> print add.__name__
add
>>> print add.__doc__
Return the sum of operand1 and operand2.
Le @functools.wraps(f) permet de garder le __name__ et __doc__ de la fonction add d’origine. Enlevez le et
recommencez le test.
Pour plus d’informations :
>>> help(functools.wraps)
>>> help(functools.update_wrapper)
>>> functools.WRAPPER_ASSIGNMENTS
(’__module__’, ’__name__’, ’__doc__’)
>>> functools.WRAPPER_UPDATES
(’__dict__’,)
Source
6
Chapitre 1. Rappel sur Python
Plone pour les développeurs, Version 1.0.0
1.11 Methode de classe
>>> class A(object):
...
@classmethod
...
def m(cls):
...
print "classmethod"
...
def n(self):
...
print "dans n"
...
>>> a = A()
>>> a.n()
dans n
>>> A.m()
classmethod
>>> a.m()
classmethod
>>> A.n()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unbound method n() must be called with A instance as first argument (got nothing instea
1.12 Property
Exemple :
>>>
...
...
>>>
>>>
>>>
5
>>>
...
...
...
...
...
...
>>>
>>>
>>>
6
class A(object):
myattr = 3
a = A()
a.myattr = 5
a.myattr
class A(object):
def setMyAttr(self, value):
self._myattr = value * 2
def getMyAttr(self):
return self._myattr
myattr = property(getMyAttr, setMyAttr)
b = A()
b.myattr = 3
b.myattr
1.13 Template
En Python 2.4-2.6 :
>>> "Bonjour %(name)s" % {’name’: ’Vincent’}
’Bonjour Vincent’
En Python 2.6/3.x :
>>> "Bonjour {name}".format(name=’Vincent’)
’Bonjour Vincent’
1.11. Methode de classe
7
Plone pour les développeurs, Version 1.0.0
1.14 Debogueur pdb
À insérer dans le code source à l’endroit où vous voulez vous arrêter :
import pdb; pdb.set_trace ()
(Ne mettez pas l’espace avant les parenthèses)
À partir de là, vous avez les commandes suivantes :
– s/step : entre dans la fonction
– n/next : instruction suivante
– c/continue : continuer jusqu’au prochain point d’arrêt
– l/list : affiche le contexte
– u/up : monter dans la pile d’appel
– d/down : descendre dans la pile d’appel
– b/break num_line : ajouter un point d’arrêt à la ligne num_line
– a : affiche la liste des paramètres passés à la fonction
– r : continue jusqu’au return de la fonction
– q : quitter la session de debogage
– Taper juste sur la touche entrée exécute la dernière commande réalisée.
– Préfixer par ” !” si vous voulez afficher la variable “a” ou “b” plutôt que d’exécuter la commande pdb associé.
Lire un exemple concret d’une session de debogage (en anglais)
1.15 Profiler
Installez le package Ubuntu pour avoir les modules profile et pstats :
$ sudo apt-get install python-profiler
Dans votre code, pour obtenir des statistiques sur l’appel de la methode :
self.method(arg1, arg2)
remplacez la ligne par :
import profile
p = profile.Profile()
p.runcall(self.method, arg1, arg2)
p.dump_stats(’/tmp/stats’)
Les résultats seront sauvegardés dans le fichier /tmp/stats.
Pour ensuite afficher les résultats, ouvrez un Python shell et exécutez :
import pstats
p = pstats.Stats(’/tmp/stats’)
p.strip_dirs().sort_stats(-1).print_stats()
Plus d’informations sur le module profile.
1.16 Ressources
– livre Plongez au coeur de Python
8
Chapitre 1. Rappel sur Python
Plone pour les développeurs, Version 1.0.0
1.17 Exercice
Lecture d’un fichier, traitement avec une expression régulière et écriture des résultats dans un fichier.
1.17. Exercice
9
Plone pour les développeurs, Version 1.0.0
10
Chapitre 1. Rappel sur Python
CHAPITRE 2
Presentation de Python
Le but est de connaître le langage python, de comprendre les standards utilisés par la communauté Plone.
Nous avons pris le parti de présenter Python 3 bien que Plone 4 soit développé avec Python 2.6, les différences
entre les deux versions seront présentées.
Ce
cours
est
issu
du
cours
de
Robert
Cordeau
http ://www.afpy.org/Members/bcordeau/Python3v1-1.pdf/download
disponible
à
l’adresse
Afin de respecter la licence initiale définie par Robert Cordeau, cette présentation de Python est sous les termes
de la licence Paternité-Pas d’Utilisation Commerciale-Partage des Conditions Initiales à l’Identique 2.0 France
accessible à http ://creativecommons.org/licenses/by-nc-sa/2.0/fr
2.1 Introduction
Ce premier chapitre introduit les grandes caractéristiques du langage Python, replace Python dans l’histoire des
langages, donne les particularités de production des scripts, défini la notion si importante d’algorithme et conclut
sur les divers implémentations disponibles.
2.1.1 Principales caractéristiques du langage Python
Historique
– 1991 : Guido van Rossum publie Python au CWI (Pays-Bas) à partir du langage ABC et du projet AMOEBA
(système d’exploitation distribué)
– 1996 : sortie de Numerical Python
– 2001 : naissance de de la PSF (Python Software Fundation)
– Les versions se succèdent... Un grand choix de modules disponibles, des colloques annuels sont organisés,
Python est enseigné dans plusieurs universités et est utilisé en entreprise...
– Fin 2008 : sorties simultanées de Python 2.6 et de Python 3
Langage Open Source
– Licence Open Source CNRI, compatible GPL, mais sans la restriction copyleft. Python est libre et gratuit même
pour les usages commerciaux
– GvR (Guido van Rossum) est le “BDFL” (dictateur bénévole à vie !)
– Importante communauté de développeurs
– Nombreux outils standard disponibles : Batteries included
11
Plone pour les développeurs, Version 1.0.0
Travail interactif
–
–
–
–
–
Nombreux interpréteurs interactifs disponibles
Importantes documentations en ligne
Développement rapide et incrémentiel
Tests et débogage faciles
Analyse interactive de données
Langage interprété rapide
– Interprétation du bytecode compilé
– De nombreux modules sont disponibles à partir de bibliothèques optimisées écrites en C, C++ ou FORTRAN
Simplicité du langage (cf. Zen of Python p. 101)
–
–
–
–
Syntaxe claire et cohérente
Indentation significative
Gestion automatique de la mémoire (garbage collecteur)
Typage dynamique fort : pas de déclaration
Orientation objet
– Modèle objet puissant mais pas obligatoire
– Structuration multifichier très facile des applications : facilite les modifications et les extensions
– Les classes, les fonctions et les méthodes sont des objets dits de première classe. Ces objets sont traités comme
tous les autres (on peut les affecter, les passer en paramètre)
Ouverture au monde
– Interfaçable avec C/C++/FORTRAN
– Langage de script de plusieurs applications importantes
– Excellente portabilité
Disponibilité de bibliothèques
– Plusieurs milliers de packages sont disponibles dans tous les domaines
2.1.2 Environnements matériel et logiciel
L’ordinateur
On peut simplifier la définition de l’ordinateur de la façon suivante :
L’ordinateur comprend entre autres :
– un microprocesseur avec une UC (Unité de Contrôle), une UAL (Unité Arithmétique et Logique), une horloge,
une mémoire cache rapide ;
– de la mémoire volatile (dite vive ou RAM), contenant les instructions et les données nécessaires à l’exécution
des programmes. La RAM est formée de cellules binaires (bits) organisées en mots de 8 bits (octets) ;
– des périphériques : entrées/sorties, mémoires permanentes (dite mortes : disque dur, clé USB, CD-ROM...),
réseau...
12
Chapitre 2. Presentation de Python
Plone pour les développeurs, Version 1.0.0
Deux sortes de programmes
On distingue, pour faire rapide :
– Le système d’exploitation : ensemble des programmes qui gèrent les ressources matérielles et logicielles. Il
propose une aide au dialogue entre l’utilisateur et l’ordinateur : l’interface textuelle (interprèteur de commande)
ou graphique (gestionnaire de fenêtres). Il est souvent multitâche et parfois multiutilisateur ;
– les programmes applicatifs sont dédiés à des tâches particulières. Ils sont formés d’une série de commandes
contenues dans un programme source qui est transformé pour être exécuté par l’ordinateur.
2.1.3 Les langages
Des langages de différents niveaux
– Chaque processeur possède un langage propre, directement exécutable : le langage machine. Il est formé de 0
et de 1 et n’est pas portable, mais c’est le seul que l’ordinateur puisse utiliser ;
– le langage d’assemblage est un codage alphanumérique du langage machine. Il est plus lisible que le langage
machine, mais n’est toujours pas portable. On le traduit en langage machine par un assembleur ;
– les langages de haut niveau. Souvent normalisés, ils permettent le portage d’une machine à l’autre. Ils sont
traduits en langage machine par un compilateur ou un interpréteur.
Bref historique des langages
–
–
–
–
–
–
Années 50 (approches expérimentales) : FORTRAN, LISP, COBOL, ALGOL...
Années 60 (langages universels) : PL/1, Simula, Smalltalk, Basic...
Années 70 (génie logiciel) : C, PASCAL, ADA, MODULA-2...
Années 80 (programmation objet) : C++, LabView, Eiffel, Perl, VisualBasic...
Années 90 (langages interprétés objet) : Java, tcl/Tk, Ruby, Python...
Années 2000 (langages commerciaux propriétaires) : C#, VB.NET...
Des centaines de langages ont été créés, mais l’industrie n’en utilise qu’une minorité.
2.1.4 Production des programmes
Deux techniques de production des programmes
La compilation est la traduction du source en langage objet. Elle comprend au moins quatre phases (trois phases
d’analyse | lexicale, syntaxique et sémantique | et une phase de production de code objet). Pour générer le langage
machine il faut encore une phase particulière : l’édition de liens. La compilation est contraignante mais offre au
final une grande vitesse d’exécution.
F IGURE 2.1 – Chaîne de compilation
Dans la technique de l’interprétation chaque ligne du source analysé est traduite au fur et à mesure en instructions directement exécutées. Aucun programme objet n’est généré. Cette technique est très souple mais les codes
générés sont peu performants : l’interpréteur doit être utilisé à chaque exécution...
F IGURE 2.2 – Technique de l’interprétation
2.1. Introduction
13
Plone pour les développeurs, Version 1.0.0
Technique de production de Python
– Technique mixte : l’interprétation du bytecode compilé. Bon compromis entre la facilité de développement et
la rapidité d’exécution ;
– le bytecode (forme intermédiaire) est portable sur tout ordinateur muni de la machine virtuelle Python.
F IGURE 2.3 – Interprétation du bytecode compilé
La construction des programmes
Le génie logiciel étudie les méthodes de construction des programmes. Plusieurs modèles sont envisageables,
entre autres :
– la méthodologie procédurale. On emploie l’analyse descendante (division des problèmes) et remontante (réutilisation d’un maximum de sous algorithmes). On s’efforce ainsi de décomposer un problème complexe en sousprogrammes plus simples. Ce modèle structure d’abord les actions ;
– la méthodologie objet. On conçoit des fabriques (classes) qui servent à produire des composants (objets) qui
contiennent des données (attributs) et des actions (méthodes). Les classes dérivent (héritage et polymorphisme)
de classes de base dans une construction hiérarchique.
Python offre les deux techniques.
2.1.5 Algorithme et programme
Définitions
Algorithme Ensemble des étapes permettant d’atteindre un but en répétant un nombre fini de fois un nombre fini
d’instructions. Un algorithme se termine en un temps fini.
Programme un programme est la traduction d’un algorithme en un langage compilable ou interprétable par un
ordinateur. Il est souvent écrit en plusieurs parties dont une qui pilote les autres : le programme principal.
La présentation des programmes
Un programme source est destiné à l’être humain. Pour en faciliter la lecture, il doit être judicieusement commenté.
La signification de parties non triviales (et uniquement celles-là) doit être expliquée par un commentaire.
Un commentaire commence par le caractère # et s’étend jusqu’à la fin de la ligne :
#--------------------# Voici un commentaire
#--------------------x = 9 + 2 # En voici un autre
2.1.6 Les implémentations de Python
–
–
–
–
–
–
–
CPython : Classic Python, codé en C, portable sur différents systèmes
Python3000 : Python 3, la nouvelle implémentation de CPython
Jython : ciblé pour la JVM (utilise le bytecode de JAVA)
IronPython : Python.NET, écrit en C#, utilise le MSIL (MicroSoft Intermediate Language)
Stackless Python : élimine l’utilisation de la pile du langage C (permet de récurser tant que l’on veut)
Pypy : projet de recherche européen d’un interpréteur Python écrit en Python
Unladen Swallow est un nouveau projet de Google utilisant les techniques de compilation JIT de Java. Ce projet,
encore au stade de développement multipliera par 5 la vitesse d’exécution de Python.
14
Chapitre 2. Presentation de Python
Plone pour les développeurs, Version 1.0.0
2.2 Installation
L’installation est simple :
Si vous êtes sur Linux ou MacOSX, votre système a probablement une version de python déjà installée. Pour
connaître sa version il vous suffit de faire python -V. Pour installer une autre version vous devez utiliser le gestionnaire de paquet de votre distribution.
MacOSX n’intègre pas en natif de gestionnaire de paquets. Nous vous recommandons d’installer le gestionnaire
de paquets MacPorts.
Pour Windows et autres Unix, il vous suffit d’ouvrir votre navigateur sur la page http ://www.python.org/download
et de choisir la version correspondant à votre choix.
2.3 La console Python
Comme tout langage, Python permet de manipuler des données grâce à un vocabulaire de mots réservés et grâce à
des types de données - approximation des ensembles définis en mathématique.
Ce chapitre présente les règles de construction des identifiants, les types de données simples (les conteneurs seront
examinés au chapitre Les conteneurs standard) ainsi que les types chaîne de caractères (Unicode et binaires).
Enfin, last but not the least, ce chapitre s’étend sur les notions non triviales de variables, de références d’objet et
d’affectation.
2.3.1 Les modes d’exécution
Les deux modes d’exécution d’un code Python
– Soit on enregistre un ensemble de commandes Python dans un fichier grâce à un éditeur (on parle alors d’un
script Python) que l’on exécute par une touche du menu de l’éditeur ;
– soit on utilise un interpréteur (par exemple IDLE) pour obtenir un résultat immédiat grâce à l’interpréteur
Python embarqué dans IDLE qui exécute la boucle d’évaluation (cf. Fig. 2.1)
F IGURE 2.4 – La boucle d’évaluation de IDLE
2.3.2 Identifiants et mots clés
Identifiants
Comme tout langage, Python utilise un identifiant pour nommer chaque objet.
Sachant que :
– un caractère de début peut être n’importe quelle lettre UNICODE.
– un caractère de continuation est une lettre UNICODE, un chiffre, un point ou un tiret bas _.
2.2. Installation
15
Plone pour les développeurs, Version 1.0.0
Note : En python 2.6, les caractères ne peuvent être que des lettres ASCII, c’est à dire sans accent
Warning : Les identifiants sont sensibles à la casse et ne doivent pas être un mot clé.
Astuce : Pour être certain de ne pas empiéter sur le vocabulaire (mot clés et bibliothèque standard de Python),
essayez ceci en mode interactif :
>>> # Un symbole valable doit produire les erreurs suivantes
>>> symbole
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name ’symbole’ is not defined
>>> import symbole
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ImportError: No module named symbole
>>> # Un symbole non valable doit produire Une erreur de syntaxe
>>> with
File "<stdin>", line 1
with
^
SyntaxError: invalid syntax
>>> # Un import de symbole non valable ne produit pas d’erreur
>>> import calendar
Styles de nommage
Il est important d’utiliser une politique cohérente de nommage des identifiants.
Voici les styles préconisés :
– UPPERCASE ou UPPER_CASE pour les constantes ;
– TitleCase pour les classes ;
– camelCase pour les fonctions, les méthodes et les interfaces graphiques ;
– lowercase ou lower_case pour tous les autres identifiants.
Exemples :
NB_ITEMS = 12 # UPPER_CASE
class MaClasse: pass # TitleCase
def maFonction(): pass # camelCase
mon_id = 5 # lower_case
Les mots réservés de Python 3
La version 3.1.1 de Python compte 33 mots clés :
– False
– None
– True
– and
– as
– assert
– break
– class
– continue
– def
– del
16
Chapitre 2. Presentation de Python
Plone pour les développeurs, Version 1.0.0
–
–
–
–
–
–
–
–
–
–
–
–
–
–
–
–
–
–
–
–
–
–
–
elif
else
except
finally
for
from
global
if
import
in
is
lambda
nonlocal 1
not
or
pass
print 2
raise
return
try
while
with
yield
2.3.3 Notion d’expression
Exemples de deux expressions simples et d’une expression complexe :
id1 = 15.3
id2 = maFonction(id1)
if id2 > 0:
id3 = math.sqrt(id2)
else:
id4 = id1 - 5.5*id2
2.3.4 Les types de données entiers
Python 3 offre deux types entiers standard : int et bool.
Le type int
Le type int n’est limité en taille que par la mémoire de la machine.
Les entiers littéraux sont décimaux par défaut, mais on peut aussi utiliser les bases suivantes :
>>> 2009 # décimal
2009
>>> 0b11111011001 # binaire
2009
>>> 0o3731 # octal
2009
>>> 0x7d9 # hexadecimal
2009
1. N’est pas supporté par Python 2.x.
2. N’est supporté que par Python 2.x en tant que mot clé.
2.3. La console Python
17
Plone pour les développeurs, Version 1.0.0
Opérations arithmétiques
Les principales opérations :
>>> 20 + 3
23
>>> 20 - 3
17
>>> 20 * 3
60
>>> 20 ** 3
8000
>>> 20 / 3
6.666666666666667
>>> 20 // 3 # division entière
6
>>> 20 % 3 # modulo
2
>>> abs(3 - 20) # valeur absolue
17
Bien remarquer le rôle des deux opérateurs de division :
– / : produit une division flottante ;
– // : produit une division entière.
Note : En python 2.6, les nombres dépassants 2 puissance 63 - 1 étaient des long. La notation des longs est
marquée par un L ex : 9223372036854775808L
Le type bool
– Deux valeurs possibles : False, True.
– Opérateurs de comparaison : ==, !=, >, >=, < et <= :
>>> 2 > 8
False
>>> 2 <= 8 < 15
True
– Opérateurs logiques (concept de shortcut) : not, or et and.
En observant les tables de vérité des opérateurs and et or (cf. p. 108), on remarque que :
– dès qu’un premier membre à la valeur False, l’expression False and expression2 vaudra False.
On n’a donc pas besoin de l’évaluer ;
– de même dès qu’un premier membre à la valeur True, l’expression True or expression2 vaudra
True.
Cette optimisation est appelée “principe du shortcut” :
>>> (3 == 3) or (9 > 24) # True (dès le premier membre)
True
>>> (9 > 24) and (3 == 3) # False (dès le premier membre)
False
– Les opérations logiques et de comparaisons sont évaluées afin de donner des résultats booléens dans False,
True.
Les expressions booléennes
Une expression booléenne a deux valeurs possibles : False ou True.
Python attribut à une expression booléennes la valeur False si c’est :
– la constante False ;
– la constante None ;
– une séquence ou une collection vide ;
18
Chapitre 2. Presentation de Python
Plone pour les développeurs, Version 1.0.0
– une donnée numérique de valeur 0.
Tout le reste vaut True.
Exemple :
>>> l = ( None, "N’est pas False", "", (), (1), [], [1] , {}, {’a’:’A’},
...
True, False, 0, 1)
>>> for e in l:
...
print("{!r} est {}".format(e, e and "VRAI" or "FAUX"))
...
None est FAUX
"N’est pas False" est VRAI
’’ est FAUX
() est FAUX
1 est VRAI
[] est FAUX
[1] est VRAI
{} est FAUX
{’a’: ’A’} est VRAI
True est VRAI
False est FAUX
0 est FAUX
1 est VRAI
2.3.5 Les types de données flottants
Le type float
– Un float est noté avec un point décimal ou en notation exponentielle :
>>> float()
0.0
>>> float(1) # Construit avec un entier = conversion
1.0
>>> 2.718
2.718
>>> .02
0.02
>>> 3e8
300000000.0
>>> 6.023e23
6.0229999999999998e+23
– Les flottants (float) supportent les mêmes opérations que les entiers.
– Les float ont une précision finie indiquée dans sys.float_info.epsilon.
– L’import du module math autorise toutes les opérations mathématiques usuelles :
>>> import math
>>> math.sin(math.pi/4)
0.70710678118654746
>>> math.degrees(math.pi)
180.0
>>> math.factorial(9)
362880
>>> math.log(1024, 2)
10.0
2.3.6 Le type complex
– Les complexes sont écrits en notation cartésienne formée de deux flottants.
2.3. La console Python
19
Plone pour les développeurs, Version 1.0.0
– La partie imaginaire est suffixée par j :
>>> 1j
1j
>>> (2+3j) + (4-7j)
(6-4j)
>>> (9+5j).real
9.0
>>> (9+5j).imag
5.0
>>> abs(3+4j)
5.0
– Un module mathématique spécifique (cmath) leur est réservé :
>>> import cmath
>>> cmath.phase(-1 + 0j)
3.1415926535897931
>>> cmath.polar(3 + 4j)
(5.0, 0.92729521800161219)
>>> cmath.rect(1., cmath.pi/4)
(0.70710678118654757+0.70710678118654746j)
2.3.7 Variables et affectation
Les variables
Dès que l’on possède des types de données, on a besoin des variables pour stocker les données.
En réalité, Python n’offre pas la notion de variable, mais plutôt celle de référence d’objet. Tant que l’objet n’est
pas modifiable (comme les entiers, les flottants, etc.), il n’y a pas de différence notable. On verra que la situation
change dans le cas des objets modifiables...
L’affectation
La valeur d’une variable, comme son nom l’indique, peut évoluer au cours de chaque affectation (la valeur antérieure est perdue) :
>>> a = a + 1 # 3 (incrémentation)
>>> a = a - 1 # 2 (décrémentation)
Affecter n’est pas comparer !
Affectation
L’ affectation a un effet (elle modifie l’état interne du programme en cours d’exécution) mais n’a pas de valeur
(on ne peut pas l’utiliser dans une expression) :
>>> a = 2
>>> x = (a = 3) + 2
SyntaxError: invalid syntax
20
Chapitre 2. Presentation de Python
Plone pour les développeurs, Version 1.0.0
Comparaison
La comparaison a une valeur utilisable dans une expression mais n’a pas d’effet (l’automate interne représentant
l’évolution du programme n’est pas modifié) :
>>> x = (a == 3) + 2
>>> x
2
Les variantes de l’affectation
Outre l’affectation simple, on peut aussi utiliser les formes suivantes :
# affectation simple
v = 4
# affectation augmentée
v += 2 # idem à : v = v + 2 si v est déjà référencé
# affectation de droite à gauche
c = d = 8 # cibles multiples
# affectations parallèles d’une séquence
e, f = 2.7, 5.1 # tuple
g, h, i = [’G’, ’H’, ’I’] # liste
x, y = coordonneesSouris() # retour multiple d’une fonction
Les affectations (explications graphiques)
Dans les schémas des figures ci-dessous, les cercles représentent les identificateurs alors que les rectangles représentent les données.
Les affectations relient les identificateurs aux données : si une donnée en mémoire n’est plus reliée, le ramassemiettes (garbage collector) de Python la supprime automatiquement :
F IGURE 2.5 – Exemple (a) : Trois affectations
2.3. La console Python
21
Plone pour les développeurs, Version 1.0.0
F IGURE 2.6 – Exemple (b) : La donnée ‘c’ est supprimée
F IGURE 2.7 – Exemple (c) : La donnée ‘a’ est supprimée
22
Chapitre 2. Presentation de Python
Plone pour les développeurs, Version 1.0.0
2.3.8 Les chaînes de caractères
Les chaînes de caractères : présentation
Chaîne de caractères Le type de données non modifiable str représente une séquence de caractères Unicode.
Non modifiable signifie qu’une donnée, une fois crée en mémoire, ne pourra plus être changée.
Trois syntaxes de chaînes sont disponibles.
Remarquez que l’on peut aussi utiliser le ‘ à la place de ”, ce qui permet d’inclure une notation dans l’autre :
syntaxe1 = "Première forme avec un retour à la ligne \n"
syntaxe2 = r"Deuxième forme sans retour à la ligne \n"
syntaxe3 = """
Troisième forme multi-ligne
"""
guillemets = "L’eau vive"
apostrophes = ’Forme "avec des apostrophes"’
Note : En python 2.6 “une chaine” est en faite une suite d’octets, contrairement à la chaîne unicode u”Avec un é”
qui correspond à la chaine de caractères de python 3.
Il faut passer de la chaine d’octets vers l’unicode en utilisant la méthode decode et en précisant l’encodage
exemple :
>>> print "Le \xc3\xa9 en utf8 est cod\xc3\xa9 sur 2 octets".decode("utf-8")
Le é en utf8 est codé sur 2 octets
>>> print "Le \xe9 en iso 8859-15 est cod\xe9 sur 1 octet".decode("iso 8859-15")
Le é en iso 8859-15 est codé sur 1 octet
Pour passer d’un encodage à un autre directement depuis une chaine d’octets, on utilise la méthode encode en lui
précisant en paramètre l’encodage de destination et l’encodage source :
>>> ’le symbole euro \xe2\x82\xac’.encode("iso 8859-15", "utf8")
’le symbole euro \xa4’
Attention : Un caractère unicode occupe 4 octets en mémoire, il s’agit d’un chiffre dont la valeur numérique
correspond au symbole a représenter. Ainsi le é a pour valeur 233 (e9) soit en mémoire “00 00 00 E9”, or en
iso 8859-15 le é a aussi pour valeur 233 (e9), mais en mémoire il prendra simplement un octet soit “E9”.
Beaucoup d’erreurs de compréhension viennent de cette intersection dans la représentation des unicodes et des
iso 8859-15.
Les chaînes de caractères : opérations
– Longueur :
>>> s = "abcde"
>>> len(s)
5
– Concaténation :
>>> s1 = "abc"
>>> s2 = "defg"
>>> s3 = s1 + s2
>>> s3
abcdefg
– Répétition :
2.3. La console Python
23
Plone pour les développeurs, Version 1.0.0
>>>
>>>
>>>
Fi!
s4 = "Fi! "
s5 = s4 * 3
s5
Fi! Fi!
Les chaînes de caractères : fonctions vs méthodes
On peut agir sur une chaîne (et plus généralement sur une séquence) en utilisant des fonctions (notion procédurale)
ou des méthodes (notion objet).
– Pour appliquer une fonction, on utilise l’opérateur () appliqué à la fonction :
>>> ch1 = "abc"
>>> long = len(ch1)
3
– On applique une méthode à un objet en utilisant la notation pointée entre la donnée/variable à laquelle on
applique la méthode, et le nom de la méthode suivi de l’opérateur () appliqué à la méthode :
>>> ch2 = "abracadabra"
>>> ch3 = ch2.upper()
ABRACADABRA
Méthodes de test de l’état d’une chaîne ch
Les méthodes suivantes sont à valeur booléennes, c’est-à-dire qu’elles retournent la valeur True ou False.
La notation [xxx] indique un élément optionnel que l’on peut donc omettre lors de l’utilisation de la méthode.
– isupper() et islower() : retournent True si ch ne contient respectivement que des majuscules/minuscules :
>>> print("cHAise basSe".isupper())
False
– istitle() : retourne True si seule la première lettre de chaque mot de ch est en majuscule :
>>> print("Chaise Basse".istitle())
True
– isalnum(), isalpha(), isdigit() et isspace() : retournent True si ch ne contient respectivement
que des caractères alphanumériques, alphabétiques, numériques ou des espaces :
>>> print("3 chaises basses".isalpha())
False
>>> print("54762".isdigit())
True
– startswith(prefix[, start[, stop]]) et endswith(suffix[, start[, stop]])
testent si la sous-chaîne définie par start et stop commence respectivement par prefix ou finit par
suffix :
>>> print("abracadabra".startswith(’ab’))
True
>>> print("abracadabra".endswith(’ara’))
False
24
Chapitre 2. Presentation de Python
Plone pour les développeurs, Version 1.0.0
Méthodes retournant une nouvelle chaîne
– lower(), upper(), capitalize() et swapcase() : retournent respectivement une chaîne en minuscule, en majuscule, en minuscule commençant par une majuscule, ou en casse inversée :
>>> # s sera notre chaîne de test pour toutes les méthodes
>>> s = "cHAise basSe"
>>> print(s.lower())
chaise basse
>>> print(s.upper())
CHAISE BASSE
>>> print(s.capitalize())
Chaise basse
>>> print(s.swapcase())
ChaISE BASsE
– expandtabs([tabsize]) : remplace les tabulations par tabsize espaces (8 par défaut).
– center(width[, fillchar]),
ljust(width[, fillchar])
et
rjust(width[,
fillchar]) : retournent respectivement une chaîne centrée, justifiée à gauche ou à droite, de largeur
maximale fixée par le paramètre width complétée par le caractère fillchar (ou par l’espace par défaut) :
>>> print(s.center(20, ’-’))
----cHAise basSe--->>> print(s.rjust(20, ’@’))
@@@@@@@@cHAise basSe
– zfill(width) : complète ch à gauche avec des 0 jusqu’à une longueur maximale de width :
>>> print(s.zfill(20))
00000000cHAise basSe
– strip([chars]), lstrip([chars]) et rstrip([chars]) : suppriment toute les combinaisons de
chars (ou l’espace par défaut) respectivement au début et en fin, au début, ou en fin d’une chaîne :
>>> print(s.strip(’ce’))
HAise basS
– find(sub[, start[, stop]]) : renvoie l’indice de la chaîne sub dans la sous-chaîne start à stop,
sinon renvoie -1. rfind() effectue le même travail en commençant par la fin. index() et rindex() font
de même mais produisent une erreur (exception) si la chaîne n’est pas trouvée :
>>> print(s.find(’se b’))
4
– replace(old[, new[, count]]) : remplace count instances (toutes pas défaut) de old par new :
>>> print(s.replace(’HA’, ’ha’))
chaise basSe
– split(seps[, maxsplit]) : découpe la chaîne en maxsplit morceaux (tous par défaut). rsplit()
effectue la même chose en commençant par la fin et striplines() effectue ce travail avec les caractères de
fin de ligne :
>>> print(s.split())
[’cHAise’, ’basSe’]
– join(seq) : concatène les chaînes du conteneur seq en intercalant la chaîne sur laquelle la méthode est
appliquée :
>>> print("**".join([’cHAise’, ’basSe’]))
cHAise**basSe
2.3. La console Python
25
Plone pour les développeurs, Version 1.0.0
Les chaînes de caractères : indiçage simple
Pour indicer une chaîne, on utilise l’opérateur [x] dans lequel l’indice x, un entier signé qui commence à 0
indique la position d’un caractère :
>>> s = "Rayon X"
>>> len(s)
7
>>> print(s[0])
R
>>>>> print(s[2])
y
>>> print(s[-1])
X
>>> print(s[-3])
n
F IGURE 2.8 – L’indiçage d’une chaîne
Extraction de sous-chaînes
L’opérateur [ ] avec 2 ou 3 indices séparés par le caractère permet d’extraire des sous-chaînes (ou tranches)
d’une chaîne. La littérrature anglophone appelle cette notation le “slice” :
>>> s = "Rayon X"
>>> len(s)
7
>>> s[1:4] # (de l’indice 1 compris à 4 non compris)
’ayo’
>>> s[-2:] # (de l’indice -2 compris à la fin)
’ X’
>>> s[:3] # (du début à 3 non compris)
’Ray’
>>> s[3:] # (de l’indice 3 compris à la fin)
’on X’
>>> s[::2] # (du début à la fin, de 2 en 2)
’RynX’
26
Chapitre 2. Presentation de Python
Plone pour les développeurs, Version 1.0.0
F IGURE 2.9 – L’extraction de sous-chaînes.
2.3.9 Les données binaires
Les types binaires Python 3 propose deux types de données binaires : byte (non modifiable) et bytearray
(modifiable).
Une donnée binaire contient une suite de zéro ou plusieurs octets, c’est-à-dire d’entiers non-signés sur 8 bits
(compris dans l’intervalle [0...255]). Ces types à la C sont bien adaptés pour stocker de grandes quantités de
données. De plus Python fournit des moyens de manipulation efficaces de ces types.
Les deux types sont assez semblables au type str et possèdent la plupart de ses méthodes. Le type modifiable
bytearray possède des méthodes communes au type list.
Exemples de données binaires et de méthodes :
>>> b_mot = b"Animal" # chaîne préfixée par b : type byte
>>> print(b_mot)
b’Animal’
>>> for b in b_mot:
...
print(b, end=" ") # (cf. table ASCII)
...
65 110 105 109 97 108
>>> bMot = bytearray(b_mot) # retourne un nouveau tableau de bytes...
>>> bMot.pop() # ...qui possède les méthodes usuelles
108
>>> bMot
bytearray(b’Anima’)
>>> data = b"5 Hills \x35\x20\x48\x69\x6C\x6C\x73"
>>> data.upper()
b’5 HILLS 5 HILLS’
>>> data.replace(b"ill", b"at")
b’5 Hats 5 Hats’
Bien différencier les codes, glyphes, caractères et octets !
2.3. La console Python
27
Plone pour les développeurs, Version 1.0.0
F IGURE 2.10 – Codes, glyphes, caractères et octets.
28
Chapitre 2. Presentation de Python
Plone pour les développeurs, Version 1.0.0
2.3.10 Les entrées-sorties
L’utilisateur à besoin d’interagir avec le programme. En mode “console”, on doit pouvoir saisir ou entrer des
informations, ce qui est généralement fait depuis une lecture au clavier. Inversement, on doit pouvoir afficher ou
sortir des informations, ce qui correspond généralement à une écriture sur l’écran.
F IGURE 2.11 – Les entrées-sorties.
Les entrées
Il s’agit de réaliser une saisie à l’écran : la fonction standard input() interrompt le programme, afficher une
éventuelle invite et attend que l’utilisateur entre une donnée et la valide par Entrée. La fonction standard input()
effectue toujours une saisie en mode texte (la saisie est une chaîne) dont on peut ensuite changer le type (on dit
aussi transtyper) :
>>> nb_etudiant = input("Entrez le nombre d’étudiants : ")
>>> type(nb_etudiant) # (c’est une chaîne)
<class ’str’>
>>> f1 = input("\nEntrez un flottant : ")
>>> f1 = float(f1) # transtypage en flottant
>>> # ou plus brièvement :
>>> f2 = float(input("Entrez un autre flottant : "))
>>> type(f2))
<class ’float’>
Les sorties
En mode “Console”, Python lit-évalue-affiche, mais la fonction print() reste indispensable aux affichages dans les
scripts :
2.3. La console Python
29
Plone pour les développeurs, Version 1.0.0
>>> import sys
>>> a, b = 2, 5
>>> print(a, b)
2 5
>>> print("Somme :", a + b)
Somme : 7
>>> print(a - b, "est la différence")
-3 est la différence
>>> print("Le produit de", a, "par", b, "vaut :", a * b)
Le produit de 2 par 5 vaut : 10
>>> print() # affiche une nouvelle ligne
>>> # pour afficher un espace à la place de la nouvelle ligne:
>>> print(a, end=" ")
2 # (et ne va pas à la ligne)
>>> print("\nErreur fatale !", file=sys.stderr) # dans un fichier
>>> print("On a <", 2**32, "> cas !", sep="###")
On a <###4294967296###> cas !
Attention : La fonction print n’est pas active par défaut en Python 2.6. Voir la note à cette adresse :
http ://docs.python.org/library/functions.html#print
Les séquences d’échappement
À l’intérieur d’une chaîne, le caractère antislash \ permet de donner une signification spéciale à certaines séquences :
Séquence
\
\\
\’
\"
\a
\b
\f
\n
\r
\t
\v
\Nnom
\uhhhh
\Uhhhhhhhh
\ooo
\xhh
Signification
saut_ligne saut de ligne ignoré
affiche un antislash
apostrophe
guillemet
sonnerie (bip)
retour arrière
saut de page
saut de ligne
retour en début de ligne
tabulation horizontale
tabulation verticale
caractère sous forme de code Unicode nommé
caractère sous forme de code Unicode 16 bits
caractère sous forme de code Unicode 32 bits
caractère sous forme de code octal
caractère sous forme de code hexadécimal
Exemples :
>>> print("\N{pound sign} \u00A3 \U000000A3")
£ £ £
>>> print("d \144 \x64")
d d d
>>> print("d \144 \x64") #séquence d’échapement inactive dans les chaînes brutes
d \144 \x64
2.4 Le contrôle du flux d’instructions
Un script Python est formé d’une suite d’instructions exécutées en séquence de haut en bas.
30
Chapitre 2. Presentation de Python
Plone pour les développeurs, Version 1.0.0
Chaque ligne d’instructions est formée d’une ou plusieurs lignes physiques qui peuvent être continuées par un
antislash \\ ou un caractère ouvrant [ ou ( ou { pas encore fermé.
Cette exécution en séquence peut être modifiée pour choisir ou répéter des portions de code. C’est l’objet principal
de ce chapitre.
2.4.1 Les instructions composées
Instruction composée
Une instruction composée est constituée :
– d’une ligne d’en-tête terminée par deux-points ;
– d’un bloc d’instructions indenté par rapport à la ligne d’en-tête.
Important : Toutes les instructions au même niveau d’indentation appartiennent au même bloc
Exemple :
somme = 0.0
nb_valeurs = 0
for v in valeurs:
nb_valeurs = nb_valeurs + 1
somme = somme + valeurs
moyenne = somme / nb_valeurs
Astuce : N’indentez pas avec des tabulations. Bien que Python admette les tabulations pour indenter le code
source, il est vivement recommandé d’utiliser 4 espaces à cette fin.
On a souvent besoin d’imbriquer les instructions composées :
if a == 0:
if b != 0:
print("\nx = {:.2f}".format(-c/b))
else:
print("\nPas de solution.")
else:
delta = b**2 - 4*a*c
if delta > 0.0:
rac_delta = sqrt(delta)
print("\nx1 = {:.2f} \t x2 = {:.2f}"
.format((-b-rac_delta)/(2*a), (-b+rac_delta)/(2*a)))
elif delta < 0.0:
print("\nPas de racine réelle.")
else:
print("\nx = {:.2f}".format(-b/(2*a)))
2.4.2 Choisir
Choisir : if - [elif] - [else]
Contrôler une alternative :
if x < 0:
print("x est négatif")
elif x % 2:
print("x est positif et impair")
else:
print("x n’est pas négatif et est pair")
2.4. Le contrôle du flux d’instructions
31
Plone pour les développeurs, Version 1.0.0
F IGURE 2.12 – Les instructions composées.
32
Chapitre 2. Presentation de Python
Plone pour les développeurs, Version 1.0.0
Test d’une valeur booléenne :
if x: # mieux que (if x is True:) ou que (if x == True:)
pass
Syntaxe compacte d’une alternative
Pour trouver, par exemple, le minimum de deux nombres, on peut utiliser l’opérateur ternaire (repris du C) :
x, y = 4, 3
# Ecriture classique :
if x < y:
plus_petit = x
else:
plus_petit = y
# Utilisation de l’opérateur ternaire :
plus_petit = x if x < y else y
print("Plus petit : ", plus_petit) # 3
2.4.3 Boucler
Boucler : while
Répéter une portion de code :
x, cpt = 257, 0
print("L’approximation de log2 de", x, "est", end=" ")
while x > 1:
x //= 2 # division avec troncature
cpt += 1 # incrémentation
print(cpt, "\n") # 8
Utilisation classique : la saisie filtrée d’une valeur numérique (on doit préciser le type car input() saisit une
chaîne) :
n = int(input(’Entrez un entier [1 .. 10] : ’))
while not(1 <= n <= 10):
n = int(input(’Entrez un entier [1 .. 10], S.V.P. : ’))
Parcourir : for
Parcourir un itérable (tout conteneur que l’on peut parcourir élément par élément, dans l’ordre ou non, suivant son
type) :
for lettre in "ciao":
print(lettre, end=" ") # c i a o
for x in [2, ’a’, 3.14]:
print(x, end=" ") # 2 a 3.14
for i in range(5):
print(i, end=" ") # 0 1 2 3 4
2.4. Le contrôle du flux d’instructions
33
Plone pour les développeurs, Version 1.0.0
2.4.4 Ruptures de séquences
Interrompre une boucle : break
Sort immédiatement de la boucle for ou while en cours contenant l’instruction :
for x in range(1, 11): # 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
if x == 5:
break
print(x, end=" ")
print("\nBoucle interrompue pour x =", x)
# affiche :
# 1 2 3 4
# Boucle interrompue pour x = 5
Court-circuiter une boucle : continue
Passe immédiatement à l’itération suivante de la boucle for ou while en cours contenant l’instruction ; reprend à
la ligne de l’en-tête de la boucle :
for x in range(1, 11): # 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
if x == 5:
continue
print(x, end=" ")
print("\nLa boucle a sauté la valeur 5")
# affiche :
# 1 2 3 4 6 7 8 9 10
# La boucle a sauté la valeur 5
Syntaxe complète des boucles
while - else
Les boucles while et for peuvent posséder une clause else qui ne s’exécute que si la boucle se termine normalement, c’est-à-dire sans interruption :
y = int(input("Entrez un entier positif : "))
while not(y > 0):
y = int(input(’Entrez un entier positif, S.V.P. : ’))
x = y // 2
while x > 1:
if y % x == 0:
print("{} a pour facteur {}".format(y, x))
break # voici l’interruption !
x -= 1
else:
print(y, "est premier.")
for - else
Un exemple avec le parcours d’une liste :
une_sequence = [2, 5, 9, 7, 11]
cible = int(input("Entrez un entier : "))
for i in une_sequence:
if i == cible:
sauve = i
break # voici l’interruption !
else:
print(cible, "n’est pas dans", une_sequence)
sauve = None
34
Chapitre 2. Presentation de Python
Plone pour les développeurs, Version 1.0.0
# sauve vaut donc cible ou None :
print("On obtient sauve =", sauve)
Exceptions
Afin de rendre les applications plus robustes, il est nécessaire de gérer les erreurs d’exécution des parties sensibles
du code.
Le mécanisme des exceptions sépare d’un côté la séquence d’instructions à exécuter lorsque tout se passe bien, et
d’un autre côté, une ou plusieurs séquences d’instructions à exécuter en cas d’erreur.
Lorsqu’une erreur survient, un objet exception est passé au mécanisme de propagation des exceptions, et l’exécution est transférée à la séquence de traitement ad doc.
Le mécanisme s’effectue en deux phases :
– la levée d’exception lors de la détection d’erreur ;
– le traitement approprié.
Syntaxe
La séquence normale d’instructions est placée dans un bloc try.
Si une erreur est détectée (levée d’exception), elle est traitée dans le bloc except approprié (le gestionnaire d’exception).
from math import sin
for x in range(-4, 5): # -4, -3, -2, -1, 0, 1, 2, 3, 4
try:
print(’{:.3f}’.format(sin(x)/x), end=" ")
except ZeroDivisionError: # toujours fournir une exception
print(1.0, end=" ") # gère l’exception en 0
# -0.189 0.047 0.455 0.841 1.0 0.841 0.455 0.047 -0.189
Toutes les exceptions levées par Python sont des instances de sous-classe de la classe Exception.
La hiérarchie des sous-classes offre une vingtaine d’exceptions standard.
L’instruction raise permet de lever volontairement une exception :
x = 2
if not(0 <= x <= 1):
raise ValueError("x n’est pas dans [0 .. 1]")
Syntaxe complète d’une exception :
try:
... # séquence normale d’exécution
except <exception_1>:
... # traitement de l’exception 1
except <exception_2>:
... # traitement de l’exception 2
...
else:
... # clause exécutée en l’absence d’erreur
finally:
... # clause toujours exécutée
Note : En python 2.6, il est encore possible de lever une exception avec une simple chaîne
2.4. Le contrôle du flux d’instructions
35
Plone pour les développeurs, Version 1.0.0
>>> raise "Une exception"
Traceback (most recent call last):
File ...
Une exception
Contrairement à Python 3, où il faut impérativement une instance dérivée de BaseException.
2.5 Les conteneurs standard
Le chapitre “La console Python” a présenté les types de données simples, mais Python offre beaucoup plus : les
conteneurs.
De façon générale, un conteneur est un objet composite destiné à contenir d’autres objets. Ce chapitre détaille les
séquences, les tableaux associatifs, les ensembles et les fichiers textuels.
2.5.1 Les séquences
Qu’est-ce qu’une séquence ?
Sequence Une séquence est un conteneur ordonné d’éléments indicés par des entiers.
Python dispose de trois types prédéfinis de séquences :
– les chaînes (vues précédemment) ;
– les listes ;
– les tuples.
2.5.2 Les listes
Définition, syntaxe et exemples
Collection Collection ordonnée et modifiable d’éléments éventuellement hétérogènes.
Syntaxe
Éléments séparés par des virgules, et entourés de crochets.
>>> couleurs = [’trèfle’, ’carreau’, ’coeur’, ’pique’]
>>> print(couleurs)
[’trèfle’, ’carreau’, ’coeur’, ’pique’]
>>> couleurs[1] = 14
>>> print(couleurs)
[’trèfle’, 14, ’coeur’, ’pique’]
>>> list1 = [’a’, ’b’]
>>> list2 = [4, 2.718]
>>> list3 = [list1, list2] # liste de listes
>>> print(list3)
[[’a’, ’b’], [4, 2.718]]
Initialisations et tests
Utilisation de la répétition, de l’opérateur d’appartenance in et de l’itérateur range() :
36
Chapitre 2. Presentation de Python
Plone pour les développeurs, Version 1.0.0
>>> truc, machin = [], [0.0] * 3
>>> print(truc) # (liste vide)
[]
>>> print(machin)
[0.0, 0.0, 0.0]
>>> l1 = list(range(4))
>>> print("l1 =", l1)
l1 = [0, 1, 2, 3]
>>> l2 = list(range(4, 8))
>>> print("l2 =", l2)
l2 = [4, 5, 6, 7]
>>> l3 = list(range(2, 9, 2))
>>> print("l3 =", l3)
l3 = [2, 4, 6, 8]
>>> print(2 in l1, 8 in l2, 6 in l3)
True False True
>>> for i in range(len(l3)):
...
print(i, l3[i], sep="-", end=" ")
0-2 1-4 2-6 3-8
Méthodes
Quelques méthodes de modification des listes :
>>> nombres = [17, 38, 10, 25, 72]
>>> nombres.sort()
>>> print(nombres)
[10, 17, 25, 38, 72]
>>> nombres.append(12)
>>> nombres.reverse()
>>> nombres.remove(38)
>>> print(nombres)
[12, 72, 25, 17, 10]
>>> print(nombres.index(17))
3
>>> nombres[0] = 11
>>> nombres[1:3] = [14, 17, 2]
>>> print(nombres.pop())
10
>>> print(nombres)
[11, 14, 17, 2, 17]
>>> print(nombres.count(17))
2
>>> nombres.extend([1, 2, 3])
>>> print(nombres)
[11, 14, 17, 2, 17, 1, 2, 3]
Manipulation des “tranches”
Syntaxe
Si on veut supprimer, remplacer ou insérer plusieurs éléments d’une liste, il faut indiquer une tranche dans le
membre de gauche d’une affectation et fournir une liste dans le membre de droite.
>>> mots = [’jambon’, ’sel’, ’miel’, ’confiture’, ’beurre’]
>>> mots[2:4] = [] # effacement par affectation d’une liste vide
>>> print(mots)
[’jambon’, ’sel’, ’beurre’]
>>> mots[1:3] = [’salade’]
2.5. Les conteneurs standard
37
Plone pour les développeurs, Version 1.0.0
>>> print(mots)
[’jambon’, ’salade’]
>>> mots[1:] = [’mayonnaise’, ’poulet’, ’tomate’]
>>> print(mots)
[’jambon’, ’mayonnaise’, ’poulet’, ’tomate’]
>>> mots[2:2] = [’miel’] # insertion en 3è position
>>> print(mots)
[’jambon’, ’mayonnaise’, ’miel’, ’poulet’, ’tomate’]
2.5.3 Les listes en intension
Exemple
Soit la fonction f(x) retournant l’ensemble des entiers qui à x associe x au carré pour tout x compris entre 2
et 10 inclus.
En Python on écrit :
>>> [x**2 for x in range(2, 11)]
[4, 9, 16, 25, 36, 49, 64, 81, 100]
Une liste en intension est une expression qui permet de générer une liste de manière très compacte. Cette notation
reprend la définition mathématique d’un ensemble en intension.
Liste en intension Une liste en intension est équivalente à une boucle for qui construirait la même liste en
utilisant la méthode append().
Les listes en intension sont utilisables sous trois formes.
Première forme expression d’une liste simple de valeurs :
result1 = [x+1 for x in une_seq]
# a le même effet que :
result2 = []
for x in une_seq:
result2.append(x+1)
Deuxième forme expression d’une liste de valeurs avec filtrage :
result3 = [x+1 for x in une_seq if x > 23]
# a le même effet que :
result4 = []
for x in une_seq:
if x > 23:
result4.append(x+1)
Troisième forme expression d’une combinaison de listes de valeurs :
result5 = [x+y for x in une_seq for y in une_autre]
# a le même effet que :
result6 = []
for x in une_seq:
for y in une_autre:
result6.append(x+y)
Des utilisations très pythoniques :
>>>
>>>
>>>
>>>
>>>
38
valeurs_s = ["12", "78", "671"]
# conversion d’une liste de chaînes en liste d’entier
valeurs_i = [int(i) for i in valeurs_s] # [12, 78, 671]
# calcul de la somme de la liste avec la fonction intégrée sum
print(sum([int(i) for i in valeurs_s]))
Chapitre 2. Presentation de Python
Plone pour les développeurs, Version 1.0.0
761
>>> # a le même effet que :
>>> s = 0
>>> for i in valeurs_s:
...
s = s + int(i)
>>> print(s)
761
>>> # Initialisation d’une liste 2D
>>> multi_liste = [[0]*2 for ligne in range(3)]
>>> print(multi_liste)
[[0, 0], [0, 0], [0, 0]]
2.5.4 Les tuples
Tuple Collection ordonnée et non modifiable d’éléments éventuellement hétérogènes.
Syntaxe
Éléments séparés par des virgules, et entourés de parenthèses.
>>> mon_tuple = (’a’, 2, [1, 3])
– Les tuples s’utilisent comme les listes mais leur parcours est plus rapide ;
– Les tuples consomment moins de mémoire ;
– Ils sont utiles pour définir des constantes.
Attention : Comme les chaînes de caractères, les tuples ne sont pas modifiables !
2.5.5 Retour sur les références
Nous avons déjà vu que l’opération d’affectation, apparemment innocente, est une réelle difficulté de Python.
>>> i = 1
>>> msg = "Quoi de neuf ?"
>>> e = 2.718
Dans l’exemple ci-dessus, les affectations réalisent plusieurs opérations :
– crée en mémoire un objet du type ad hoc (membre de droite) ;
– stocke la donnée dans l’objet créé ;
– crée un nom de variable (membre de gauche) ;
– associe ce nom de variable à l’objet contenant la valeur.
Une conséquence de ce mécanisme est que, si un objet modifiable est affecté, tout changement sur un objet
modifiera l’autre :
>>> fable = ["Je", "plie", "mais", "ne", "romps", "point"]
>>> phrase = fable
>>> phrase[4] = "casse"
>>> print(fable)
[’Je’, ’plie’, ’mais’, ’ne’, ’casse’, ’point’]
Si l’on désire réaliser une vraie copie d’un objet, on doit utiliser le module copy :
>>>
>>>
>>>
>>>
>>>
import copy
a = [1, 2, 3]
b = a # une référence
b.append(4)
print(a)
2.5. Les conteneurs standard
39
Plone pour les développeurs, Version 1.0.0
[1,
>>>
>>>
>>>
[1,
>>>
[1,
2, 3, 4]
c = copy.copy(a) # une copie de "surface"
c.append(5)
print(c)
2, 3, 4, 5]
print(a)
2, 3, 4]
Dans les rares occasions où l’on veut aussi que chaque élément et attribut de l’objet soit copié séparément et de
façon récursive, on emploie la fonction copy.deepcopy().
Complément graphique sur l’assignation
Assignation augmentée d’un objet non modifiable (cas d’un entier).
On a représenté en gris clair l’addition intermédiaire :
F IGURE 2.13 – Exemple (a) Assignation d’un entier
F IGURE 2.14 – Exemple (b) Addition intermédiaire
Assignation augmentée d’un objet modifiable (cas d’une liste).
On a représenté en gris clair la création de la liste intermédiaire :
40
Chapitre 2. Presentation de Python
Plone pour les développeurs, Version 1.0.0
F IGURE 2.15 – Exemple (c) Assignation augmentée
F IGURE 2.16 – Exemple (a) Assignation d’une liste
F IGURE 2.17 – Exemple (b) Création intermédiaire en mémoire
2.5. Les conteneurs standard
41
Plone pour les développeurs, Version 1.0.0
F IGURE 2.18 – Exemple (c) Assignation augmentée
2.5.6 Les tableaux associatifs
Les types tableaux associatifs
Tableau associatif Un tableau associatif est un type de données permettant de stocker des couples cle : valeur,
avec un accès très rapide à la valeur à partir de la clé, la clé ne pouvant être présente qu’une seule fois dans
le tableau.
Il possède les caractéristiques suivantes :
– l’opérateur d’appartenance d’une clé in ;
– la fonction taille len() donnant le nombre de couples stockés ;
– il est itérable (on peut le parcourir) mais n’est pas ordonné.
Python propose le type standard dict.
Les dictionnaires (dict)
Syntaxe
Collection de couples cle : valeur entourée d’accolades.
Les dictionnaires constituent un type composite mais ils n’appartiennent pas aux séquences.
Comme les listes, les dictionnaires sont modifiables, mais les couples enregistrés n’occupent pas un ordre immuable, leur emplacement est géré par un algorithme spécifique (Cf. les fonctions de hachage).
Une clé pourra être alphabétique, numérique...en fait tout type hachable. Les valeurs pourront être des valeurs
numériques, des séquences, des dictionnaires, mais aussi des fonctions, des classes ou des instances.
Exemples de création :
>>> # insertion de clés/valeurs une à une
>>> d1 = {} # dictionnaire vide
>>> d1["nom"] = 3
>>> d1["taille"] = 176
>>> print(d1)
{’nom’: 3, ’taille’: 176}
>>> # définition en extension
>>> d2 = {"nom": 3, "taille": 176}
>>> print(d2)
{’nom’: 3, ’taille’: 176}
42
Chapitre 2. Presentation de Python
Plone pour les développeurs, Version 1.0.0
>>> # définition en intension
>>> d3 = {x: x**2 for x in (2, 4, 6)}
>>> print(d3)
{2: 4, 4: 16, 6: 36}
>>> # utilisation de paramètres nommés
>>> d4 = dict(nom=3, taille=176)
>>> print(d4)
{’taille’: 176, ’nom’: 3}
>>> # utilisation d’une liste de couples clés/valeurs
>>> d5 = dict([("nom", 3), ("taille", 176)])
>>> print(d5)
{’nom’: 3, ’taille’: 176}
Méthodes
Quelques méthodes applicables aux dictionnaires :
>>> tel = {’jack’: 4098, ’sape’: 4139}
>>> tel[’guido’] = 4127
>>> print(tel)
{’sape’: 4139, ’jack’: 4098, ’guido’: 4127}
>>> print(tel[’jack’])
4098
>>> del tel[’sape’]
>>> tel[’irv’] = 4127
>>> print(tel)
{’jack’: 4098, ’irv’: 4127, ’guido’: 4127}
>>> print(list(tel.keys()))
[’jack’, ’irv’, ’guido’]
>>> print(sorted(tel.keys()))
[’guido’, ’irv’, ’jack’]
>>> print(sorted(tel.values()))
[4098, 4127, 4127]
>>> print(’guido’ in tel, ’jack’ not in tel)
True False
2.5.7 Les ensembles (set)
set Collection itérable non ordonnée d’éléments hachables distincts.
>>> X, Y = set(’abcd’), set(’sbds’)
>>> print("X =", X)
X = {’a’, ’c’, ’b’, ’d’}
>>> print("Y =", Y)
Y = {’s’, ’b’, ’d’} : un seul élément ’s’
>>> print(’c’ in X)
True
>>> print(’a’ in Y)
False
>>> print(X - Y)
{’a’, ’c’}
>>> print(Y - X)
{’s’}
>>> print(X | Y)
{’a’, ’c’, ’b’, ’d’, ’s’}
>>> print(X & Y)
{’b’, ’d’}
Note : La notation d’ensembles avec des accolades est une nouveauté de Python 3. en Python 2, l’affichage serait :
2.5. Les conteneurs standard
43
Plone pour les développeurs, Version 1.0.0
F IGURE 2.19 – Opérations sur les ensembles
>>> X = set(’abcd’)
>>> print X
set([’a’, ’c’, ’b’, ’d’])
2.5.8 Les fichiers textuels
Les fichiers : introduction
On rappelle que l’ordinateur n’exécute que les programmes présents dans sa mémoire volatile (la RAM).
Mais, pour conserver durablement des informations, il faut utiliser une mémoire permanente comme par exemple
le dique dur, la clé USB, le DVD...
Comme la plupart des langages, Python utilise classiquement la notion de fichier.
C’est un type pré-défini en Python, qui ne nessécite donc pas d’importer de module externe.
Nous nous limiterons aux fichiers textuels (portables, lisible par un éditeur), mais signalons que les fichiers stockés
en codage binaire sont plus compacts et plus rapides à gérer.
Gestion des fichiers
Ouverture et fermeture des fichiers
Principaux modes d’ouverture des fichiers textuels :
>>> f1 = open("monFichier_1", "r") # en lecture
>>> f2 = open("monFichier_2", "w") # en écriture
>>> f3 = open("monFichier_3", "a") # en ajout
Python utilise les fichiers en mode texte par défaut (noté t) (pour les fichiers binaires, il faut préciser le mode b).
Tant que le fichier n’est pas fermé, son contenu n’est pas garanti sur le disque.
Une seule méthode de fermeture :
44
Chapitre 2. Presentation de Python
Plone pour les développeurs, Version 1.0.0
>>> f1.close()
Écriture séquentielle
Méthodes d’écriture :
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
f = open("truc.txt", "w")
s = ’toto\n’
f.write(s) # écrit la chaîne s dans f
l = [’a’, ’b’, ’c’]
f.writelines(l) # écrit les chaînes de la liste l dans f
f.close()
# utilisation de l’option file de print
f2 = open("truc2.txt", "w")
print("abcd", file=f2)
f2.close()
Regardez maintenant votre fichier truc.txt avec votre éditeur de texte favori :
toto
abc
Lecture séquentielle
Méthodes de lecture :
>>> f = open("truc.txt", "r")
>>> s = f.read() # lit tout le fichier --> string
>>> s = f.read(3) # lit au plus n octets --> string
>>> s = f.readline() # lit la ligne suivante --> string
>>> s = f.readlines() # lit tout le fichier --> liste de strings
>>> f.close()
>>> # Affichage des lignes d’un fichier une à une
>>> f = open("truc.txt") # mode "r" par défaut
>>> for ligne in f:
...
print(ligne[:-1]) # pour sauter le retour à la ligne
toto
abc
>>> f.close()
2.5.9 Itérer sur les conteneurs
Les techniques suivantes sont classiques et très utiles.
Obtenir clés et valeurs en bouclant sur un dictionnaire :
>>> knights = {"Gallahad": "the pure", "Robin": "the brave"}
>>> for k, v in knights.items():
...
print(k, v)
Gallahad the pure
Robin the brave
Obtenir clés et valeurs en bouclant sur une liste :
>>> for i, v in enumerate(["tic", "tac", "toe"]):
...
print(i, v, end=" ", sep="->")
0->tic 1->tac 2->toe
Boucler sur deux séquences (ou plus) appariées :
2.5. Les conteneurs standard
45
Plone pour les développeurs, Version 1.0.0
>>> question = ["name", "quest", "favorite color"]
>>> answers = ["Lancelot", "the Holy Grail", "blue"]
>>> for q, a in zip(question, answers):
...
print("What is your {}? It is {}.".format(q, a))
What is your name? It is Lancelot.
What is your quest? It is the Holy Grail.
What is your favorite color? It is blue.
Boucler sur une séquence inversée (la séquence initiale est inchangée) :
>>> print()
>>> for i in reversed(range(1, 10, 2)):
...
print(i, end=" ")
9 7 5 3 1
Boucler sur une séquence triée à éléments uniques (la séquence initiale est inchangée) :
>>> print()
>>> basket = ["apple", "orange", "apple", "pear", "orange", "banana"]
>>> for f in sorted(set(basket)):
...
print(f, end=" ")
apple banana orange pear
2.5.10 L’affichage formaté
Attention : Python 2.6 : Contrairement à Python 3, les arguments positionnels d’une chaîne de formatage doit
être explicite. En d’autres termes l’utilisation du slot de formatage {} est sanctionné d’un message d’erreur.
>>> # Python 3
>>> print("Je m’appelle {}".format("Bob"))
Je m’appelle Bob
>>> # Python 2.6
>>> print("Je m’appelle {0}".format("Bob"))
Je m’appelle Bob
La méthode format permet de contrôler finement toutes sortes d’affichages.
Remplacements simples :
>>> print("Je m’appelle {}".format("Bob"))
Je m’appelle Bob
>>> print("Je m’appelle {{{}}}".format("Bob"))
Je m’appelle {Bob}
>>> print("{}".format("-"*10))
----------
Remplacements avec champs nommés :
>>> a, b = 5, 3
>>> print("The story of {c} and {d}".format(c=a+b, d=a-b))
The story of 8 and 2
Formatages à l’aide de liste :
>>> stock = [’papier’, ’enveloppe’, ’chemise’, ’encre’, ’buvard’]
>>> print("Nous avons de l’{0[3]} et du {0[0]} en stock\n".format(stock))
Nous avons de l’encre et du papier en stock
Formatages à l’aide de dictionnaire :
46
Chapitre 2. Presentation de Python
Plone pour les développeurs, Version 1.0.0
>>>
>>>
>>>
>>>
>>>
print("My name is {0[name]}".format(dict(name=’Fred’)))
# My name is Fred
d = dict(animal = ’éléphant’, poids = 12000)
print("L’{0[animal]} pèse {0[poids]} kg\n".format(d))
# L’éléphant pèse 12000 kg
Remplacement avec attributs nommés :
>>> import math
>>> import sys
>>> print("math.pi = {.pi}, epsilon = {.float_info.epsilon}".format(math, sys))
math.pi = 3.14159265359, epsilon = 2.22044604925e-16
Conversions str() et repr() :
>>> print("{0!s} {0!r}".format("Une chaîne"))
Une chaîne ’Une chaîne’
Formatages numériques :
>>> n = 100
>>> pi = 3.1415926535897931
>>> print("{0}, et {1}".format(n, pi))
100, et 3.14159265359
>>> print("{}, et {}".format(n, pi))
100, et 3.14159265359
>>> print("{0}, {1} et {0}".format(n, pi))
100, 3.14159265359 et 100
>>> print("{:.4e}".format(pi))
3.1416e+00
>>> print("{:g}".format(pi))
3.14159
>>> msg = "Résultat sur {:d} échantillons : {:.2f}".format(n, pi)
>>> print(msg)
Résultat sur 100 échantillons : 3.14
Formatages divers :
>>> s = "The sword of truth"
>>> print("[{}]".format(s))
[The sword of truth]
>>> print("[{:25}]".format(s))
[The sword of truth ]
>>> print("[{:>25}]".format(s))
[ The sword of truth]
>>> print("[{:^25}]".format(s))
[ The sword of truth ]
>>> print("[{:-^25}]".format(s))
[---The sword of truth----]
>>> print("[{:.<25}]".format(s))
[The sword of truth.......]
>>> long = 12
>>> print("[{}]".format(s[:long]))
[The sword of]
>>> m = 123456789
>>> print("{:0=12}".format(m))
000123456789
>>> print("{:#=12}".format(m))
###123456789
2.5. Les conteneurs standard
47
Plone pour les développeurs, Version 1.0.0
2.6 Fonctions et espaces de noms
Note : Les fonctions sont les éléments structurants de base de tout langage procédural.
Elles offrent différents avantages :
– Évite la répétition : on peut < factoriser > une portion de code qui se répète lors de l’exécution en séquence
d’un script ;
– Met en relief les données et les résultats : entrées et sorties de la fonction ;
– Permet la réutilisation : mécanisme de l’import ;
– Décompose une tâche complexe en tâches plus simples : conception de l’application.
Ces avantages sont illustrés ci-dessous :
F IGURE 2.20 – Exemple (a) Évite la duplication de code.
F IGURE 2.21 – Exemple (b) Met en relief entrées et sorties.
48
Chapitre 2. Presentation de Python
Plone pour les développeurs, Version 1.0.0
F IGURE 2.22 – Exemple (c) L’import permet la réutilisation.
F IGURE 2.23 – Exemple (d) Améliore la conception.
2.6.1 Définition et syntaxe
Fonction Groupe d’instructions regroupé sous un nom (définition) et s’exécutant à la demande (appel).
Syntaxe
C’est une instruction composée :
def nomFonction(parametre_1, ...):
"""Documentation de la fonction."""
# <bloc_instructions>*
Le bloc d’instructions est obligatoire. S’il est vide, on emploie l’instruction pass.
La documentation (facultative) est fortement conseillée.
2.6.2 Passage des arguments
Mécanisme général
Note : Passage par affectation : chaque argument de la définition de la fonction correspond, dans l’ordre, à un
paramètre de l’appel. La correspondance se fait par affectation.
Un ou plusieurs paramètres, pas de retour
Exemple sans l’instruction return, ce qu’on appelle souvent une procédure.
Dans ce cas la fonction renvoie implicitement la valeur None :
def table(base, debut, fin):
"""Affiche la table des <base> de <debut> à <fin>."""
n = debut
while n <= fin:
2.6. Fonctions et espaces de noms
49
Plone pour les développeurs, Version 1.0.0
F IGURE 2.24 – Passage des arguments par affectation.
print(n, ’x’, base, ’=’, n * base, end=" ")
n += 1
# exemple d’appel :
table(7, 2, 11)
# 2 x 7 = 14 3 x 7 = 21 4 x 7 = 28 5 x 7 = 35 6 x 7 = 42
# 7 x 7 = 49 8 x 7 = 56 9 x 7 = 63 10 x 7 = 70 11 x 7 = 77
Un ou plusieurs paramètres, utilisation du retour
Exemple avec utilisation d’un return unique :
from math import pi
def cube(x):
return x**3
def volumeSphere(r):
return 4.0 * pi * cube(r) / 3.0
# Saisie du rayon et affichage du volume
rayon = float(input(’Rayon : ’))
print("Volume de la sphère =", volumeSphere(rayon))
Exemple avec utilisation d’un return multiple :
import math
def surfaceVolumeSphere(r):
surf = 4.0 * math.pi * r**2
vol = surf * r/3
return surf, vol
# programme principal
50
Chapitre 2. Presentation de Python
Plone pour les développeurs, Version 1.0.0
rayon = float(input(’Rayon : ’))
s, v = surfaceVolumeSphere(rayon)
print("Sphère de surface {:g} et de volume {:g}".format(s, v))
Passage d’une fonction en paramètre
def tabuler(fonction, borneInf, borneSup, nbPas):
"""Affichage des valeurs de <fonction>.
On doit avoir (borneInf < borneSup) et (nbPas > 0)"""
h, x = (borneSup - borneInf) / float(nbPas), borneInf
while x <= borneSup:
y = fonction(x)
print("f({:.2f}) = {:.2f}".format(x, y))
x += h
# ...
def maFonction(x):
return 2*x**3 + x - 5
tabuler(maFonction, -5, 5, 10)
# f(-5.00) = -260.00
# f(-4.00) = -137.00
# ...
# f(5.00) = 250.00
Paramètres avec valeur par défaut
On utilise de préférence des valeurs par défaut non modifiables car la modification d’un paramètre par un premier
appel est visible les fois suivantes :
def initPort(speed=9600, parity="paire", data=8, stops=1):
print("Init. à", speed, "bits/s", "parité :", parity)
print(data, "bits de données", stops, "bits d’arrêt")
# Appels possibles :
initPort()
# Init. à 9600 bits/s parité : paire
# 8 bits de données 1 bits d’arrêt
initPort(parity="nulle")
# Init. à 9600 bits/s parité : nulle
# 8 bits de données 1 bits d’arrêt
initPort(2400, "paire", 7, 2)
# Init. à 2400 bits/s parité : paire
# 7 bits de données 2 bits d’arrêt
Nombre d’arguments arbitraire : passage d’un tuple
def somme(*args):
"""Renvoie la somme de <tuple>."""
resultat = 0
for nombre in args:
resultat += nombre
return resultat
# Exemples d’appel :
print(somme(23)) # 23
print(somme(23, 42, 13)) # 78
Note : Si la fonction possède plusieurs arguments, le tuple est en dernière position.
2.6. Fonctions et espaces de noms
51
Plone pour les développeurs, Version 1.0.0
Il est aussi possible de passer un tuple (en fait une séquence) à l’appel qui sera décompressé en une liste de
paramètres d’une fonction < classique > :
def somme(a, b, c):
return a+b+c
# Exemple d’appel :
elements = (2, 4, 6)
print(somme(*elements)) # 12
Nombre d’arguments arbitraire : passage d’un dictionnaire
def unDict(**kargs):
return kargs
# Exemples d’appels
## par des paramètres nommés :
print(unDict(a=23, b=42)) # {’a’: 23, ’b’: 42}
## en fournissant un dictionnaire :
mots = {’d’: 85, ’e’: 14, ’f’:9}
print(unDict(**mots)) # {’e’: 14, ’d’: 85, ’f’: 9}
Note : Si la fonction possède plusieurs arguments, le dictionnaire est en toute dernière position (après un éventuel
tuple).
2.6.3 Espaces de noms
Portée des objets
Note : Portée : les noms des objets sont créés lors de leur première affectation, mais ne sont visibles que dans
certaines régions de la mémoire.
On distingue :
– La portée globale : celle du module __main__. Un dictionnaire gère les objets globaux : l’instruction globals()
fournit les couples variable :valeur ;
– La portée locale : les objets internes aux fonctions (et aux classes) sont locaux. Les objets globaux ne sont pas
modifiables dans les portées locales. L’instruction locals() fournit les couples variable :valeur.
Résolution des noms : règle LGI
La recherche des noms est d’abord locale (L), puis globale (G), enfin interne (I) (cf. Figure ci-dessous) :
Exemples de portée :
# x et fonc sont affectés dans le module : globaux
def fonc(y): # y et z sont affectés dans fonc : locaux
global x # permet de modifier x ligne suivante
x += 2
z = x + y
return z
x = 99
print(fonc(1)) # 102
# x et fonc sont affectés dans le module : globaux
def fonc(y): # y et z sont affectés dans fonc : locaux
# dans fonc : portée locale
z = x + y
return z
52
Chapitre 2. Presentation de Python
Plone pour les développeurs, Version 1.0.0
F IGURE 2.25 – Règle LGI
2.6. Fonctions et espaces de noms
53
Plone pour les développeurs, Version 1.0.0
x = 99
print(fonc(1)) # 100
# x et fonc sont affectés dans le module : globaux
def fonc(y): # y, x et z sont affectés dans fonc : locaux
x = 3 # ce nouvel x est local et masque le x global
z = x + y
return z
x = 99
print(fonc(1)) # 4
2.7 Modules et packages
Un programme Python est généralement composé de plusieurs fichiers sources, appelés modules. Leur nom est
suffixé :samp‘.py‘.
S’ils sont correctement codés les modules doivent être indépendants les uns des autres pour être réutilisés à la
demande dans d’autres programmes.
Ce chapitre explique comment coder et importer des modules dans un autre.
Nous verrons également la notion de package qui permet de grouper plusieurs modules.
2.7.1 Modules
Module Fichier indépendant permettant de scinder un programme en plusieurs scripts. Ce mécanisme permet
d’élaborer efficacement des bibliothèques de fonctions ou de classes.
Avantages des modules :
– réutilisation du code ;
– la documentation et les tests peuvent être intégrés au module ;
– réalisation de services ou de données partagés ;
– partition de l’espace de noms du système.
Import d’un module
Deux syntaxes possibles :
– la commande import nom_module importe la totalité des objets du module :
import tkinter
– la commande from nom_module import obj1, obj2... n’importe que les objets obj1, obj2... du
module :
from math import pi, sin, log
Il est conseillé d’importer dans l’ordre :
– les modules de la bibliothèque standard ;
– les modules des bibliothèques tierces ;
– Les modules personnels.
Exemples
Notion d’auto-test
Un module cube_m.py. Remarquez l’utilisation de l’auto-test qui permet de tester le module seul :
54
Chapitre 2. Presentation de Python
Plone pour les développeurs, Version 1.0.0
def cube(y):
"""Calcule le cube du paramètre <y>."""
return y**3
# Auto-test ---------------------------------------------------if __name__ == "__main__": # False lors d’un import ==> ignoré
help(cube) # affiche le docstring de la fonction
print("cube de 9 :", cube(9)) # cube de 9 : 729
Utilisation de ce module. On importe la fonction cube() incluse dans le fichier cube_m.py :
from cube_m import cube
for i in range(1, 4):
print("cube de", i, "=", cube(i), end=" ")
# cube de 1 = 1 cube de 2 = 8 cube de 3 = 27
Une interface à gnuplot
L’application libre gnuplot permet d’afficher des courbes. La fonction suivante est une interface d’appel qui permet
d’afficher des données issues de fichiers :
import os
def plotFic(courbes):
dem = open("_.dem", "w") # fichier réutilisé à chaque tracé
dem.write("set grid\n")
plot_data = ["’%s’ with %s" % (c[0], c[1]) for c in courbes]
dem.write("plot " + ’,’.join(plot_data))
dem.write(’\npause -1 "\’Entrée\’ pour continuer"\n’)
dem.write("reset")
dem.close()
os.system("wgnuplot _.dem")
L’auto-test suivant illustre son utilisation :
if __name__ == ’__main__’:
f, g, h = open("d1.dat", "w"), open("d2.dat", "w"), open("d3.dat", "w")
for i in range(201):
x = 0.1*i - 5.0
y = x**3 - 20*x**2
f.write("%g %g\n" %(x, y))
y = x**3 - 30*x**2
g.write("%g %g\n" %(x, y))
y = x**3 - 40*x**2
h.write("%g %g\n" %(x, y))
h.close(); g.close(); f.close()
plotFic([(’d1.dat’, ’points’)])
plotFic([(’d1.dat’, ’lines’), (’d2.dat’, ’points’), (’d3.dat’, ’lines’)])
2.7.2 Bibliothèque standard
La bibliothèque standard
On dit souvent que Python est livré piles comprises (batteries included) tant sa bibliothèque standard, riche de
plus de 200 packages et modules, répond aux problèmes courants les plus variés.
Ce survol présente quelques fonctionnalités utiles.
2.7. Modules et packages
55
Plone pour les développeurs, Version 1.0.0
La gestion des chaînes
Le module string fournit des constantes comme ascii_lowercase, digits...et la classe Formatter
qui peut être spécialisée en sous-classes spécialisées de formateurs de chaînes.
Le module textwrap est utilisé pour formater un texte : longueur de chaque ligne, contrôle de l’indentation.
Le module struct permet de convertir des nombres, booléens et des chaînes en leur représentation binaire afin
de communiquer avec des bibliothèques de bas niveau (souvent en C).
Le module difflib permet la comparaison de séquences et fournit des sorties au format standard diff ou en
HTML.
Enfin on ne peut oublier le module re qui offre à Python la puissance des expressions régulières.
Exemple : le module io.StringIO
Ce module fournit des objets compatibles avec l’interface des objets fichiers.
Exemple de gestion ligne à ligne d’un fichier ou d’une chaîne avec la même fonction scanner utilisant le même
traitement :
def scanner(objet_fichier, gestionnaire_ligne):
for ligne in objet_fichier:
gestionnaire_ligne(ligne)
if __name__==’__main__’:
def premierMot(ligne): print(ligne.split()[0])
fic = open("data.dat")
scanner(fic, premierMot)
import io
chaine = io.StringIO("un\ndeux xxx\ntrois\n")
scanner(chaine, premierMot)
La gestion de la ligne de commande
La gestion est assurée par deux modules : getopt, le module historique hérité du C et optparse, un module
récent beaucoup plus puissant :
from optparse import OptionParser
parser = OptionParser()
parser.add_option("-f", "--file", dest="filename",
help="write report to FILE", metavar="FILE")
parser.add_option("-q", "--quiet",
action="store_false", dest="verbose", default=True,
help="don’t print status messages to stdout")
(options, args) = parser.parse_args()
Les lignes de commande :
$ python 6_025.py -h
ou :
$ python 6_025.py --help
produisent la même documentation :
Usage: 6_025.py [options]
Options:
-h, --help show this help message and exit
-f FILE, --file=FILE write report to FILE
-q, --quiet don’t print status messages to stdout
56
Chapitre 2. Presentation de Python
Plone pour les développeurs, Version 1.0.0
Bibliothèques mathématiques et types numériques
En standard, Python propose les modules fraction et decimal :
>>> from fractions import Fraction
>>> import decimal as d
>>> print(Fraction(16, -10))
-8/5
>>> print(Fraction(123))
123
>>> print(Fraction(’ -3/7 ’))
-3/7
>>> print(Fraction(’-.125’))
-1/8
>>> print(Fraction(’7e-6’))
7/1000000
>>> d.getcontext().prec = 6
>>> print(d.Decimal(1) / d.Decimal(7))
0.142857
>>> d.getcontext().prec = 18
>>> print(d.Decimal(1) / d.Decimal(7))
0.142857142857142857
En plus des bibliothèques math et cmath déjà vues, la bibliothèque random propose plusieurs fonctions de
nombres aléatoires.
La gestion du temps et des dates
Les modules calendar, time et datetime fournissent les fonctions courantes de gestion du temps et des
durées :
>>> import calendar, datetime, time
>>> moon_apollo11 = datetime.datetime(1969, 7, 20, 20, 17, 40)
>>> print(moon_apollo11)
>>> print(time.asctime(time.gmtime(0)))
Thu Jan 01 00:00:00 1970 ("epoch" UNIX)
>>> vendredi_precedent = datetime.date.today()
>>> un_jour = datetime.timedelta(days=1)
>>> while vendredi_precedent.weekday() != calendar.FRIDAY:
...
vendredi_precedent -= un_jour
>>> print(vendredi_precedent.strftime("%A, %d-%b-%Y"))
Friday, 09-Oct-2009
Algorithmes et types de données collection
Le module bisect fournit des fonctions de recherche de séquences triées. Le module array propose un type
semblable à la liste, mais plus rapide car de contenu homogène. Le module heapq gère des tas dans lesquels
l’élément d’indice 0 est toujours le plus petit :
>>>
>>>
>>>
>>>
...
>>>
[2,
import heapq
import random
heap = []
for i in range(10):
heapq.heappush(heap, random.randint(2, 9))
print(heap)
3, 5, 4, 6, 6, 7, 8, 7, 8]
À l’instar des structures C, Python propose désormais, via le module collections, la notion de type tuple
nommé :
2.7. Modules et packages
57
Plone pour les développeurs, Version 1.0.0
>>> import collections
>>> # description du type :
>>> Point = collections.namedtuple("Point", "x y z")
>>> # on instancie un point :
>>> point = Point(1.2, 2.3, 3.4)
>>> # on l’affiche :
>>> print("point : [{}, {}, {}]".format(point.x, point.y, point.z))
point : [1.2, 2.3, 3.4]
Il est bien sûr possible d’avoir des tuples nommés emboîtés. Le type defaultdict permet des utilisations
avancées :
>>> from collections import defaultdict
>>> s = [(’y’, 1), (’b’, 2), (’y’, 3), (’b’, 4), (’r’, 1)]
>>> d = defaultdict(list)
>>> for k, v in s:
...
d[k].append(v)
>>> print(d.items())
dict_items([(’y’, [1, 3]), (’r’, [1]), (’b’, [2, 4])])
>>> s = ’mississippi’
>>> d = defaultdict(int)
>>> for k in s:
...
d[k] += 1
>>> print(d.items())
dict_items([(’i’, 4), (’p’, 2), (’s’, 4), (’m’, 1)])
Et tant d’autres domaines...
Beaucoup d’autres domaines pourraient être explorés :
– accès au système ;
– utilitaires fichiers ;
– programmation réseau ;
– persistance ;
– les fichiers XML ;
– la compression ;
– ...
2.7.3 Bibliothèques tierces
Une grande diversité
Outre les modules intégrés à la distribution standard de Python, on trouve des bibliothèques dans tous les domaines :
– scientifique ;
– bases de données ;
– tests fonctionnels et contrôle de qualité ;
– 3D ;
– ...
Le site http ://pypi.python.org/pypi (The Python Package Index) recense des milliers de modules et de packages !
Un exemple : la bibliothèque Unum
Elle permet de calculer en tenant compte des unités du système S.I. Voici un exemple de session interactive :
58
Chapitre 2. Presentation de Python
Plone pour les développeurs, Version 1.0.0
-- Welcome in Unum Calculator (ver 04.00) ->>> d = 1609 * M
>>> t = 11.7 * S
>>> v = d/t
>>> v
137.521367521 [m/s]
>>> a = v/t
>>> a
11.753963036 [m/s2]
2.7.4 Packages
Package Un package est un module contenant d’autres modules. Les modules d’un package peuvent être des
sous-packages, ce qui donne une structure arborescente.
En résumé, un package est simplement un répertoire qui contient des modules et un fichier __init__.py décrivant l’arborescence du package.
2.8 La programmation Orientée Objet
La Programmation Orientée Objet :
– la POO permet de mieux modéliser la réalité en concevant des ensembles d’objets, les classes.
– Ces classes permettent de construire des objets interactifs entre eux et avec le monde extérieur.
– Les objets sont créés indépendamment les uns des autres, grâce à l’encapsulation, mécanisme qui permet d’embarquer leurs propriétés.
– Les classes permettent d’éviter au maximum l’emploi des variables globales.
– Enfin les classes offrent un moyen économique et puissant de construire de nouveaux objets à partir d’objets
préexistants.
2.8.1 Insuffisance de l’approche procédurale
Un exemple : On veut représenter un cercle, ce qui nécessite au minimum trois informations, les coordonnées du
centre et le rayon :
cercle = (11, 60, 8)
Mais comment interpréter ces trois données ?
cercle = (x, y, rayon)
ou bien
cercle = (rayon, x, y)
Pour résoudre ce problème et améliorer la lisibilité, on peut utiliser des tuples nommés :
from collection import namedtuple
Cercle = namedtuple("Cercle", "x y rayon")
cercle = Cercle(11, 60, 8)
# exemple d’utilisation :
distance = distance_origine(cercle.x, cercle.y)
Par contre, il reste le problème des données invalides, ici un rayon négatif :
cercle = Cercle(11, 60, -8)
2.8. La programmation Orientée Objet
59
Plone pour les développeurs, Version 1.0.0
Si les cercles doivent changer de caractéristiques, il faut opter pour un type modifiable, liste ou dictionnaire ce qui
ne règle toujours pas le problème des données invalides...
On a donc besoin d’un mécanisme pour empaqueter les données nécessaires pour représenter un cercle et pour empaqueter les méthodes applicables à ce nouveau type de données (la classe), de telle sorte que seules les opérations
valides soient utilisables.
2.8.2 Terminologie
Le vocabulaire de la POO
Une classe est donc équivalente à un nouveau type de données. On connaît déjà par exemple int ou str.
Un objet ou une instance est un exemplaire particulier d’une classe. Par exemple "truc" est une instance de la
classe str.
La plupart des classes encapsulent à la fois les données et les méthodes applicables aux objets. Par exemple un
objet str contient une chaîne de caractères Unicode (les données) et de nombreuses méthodes comme upper.
On pourrait définir un objet comme une capsule, à savoir un “paquet” contenant des attributs et des méthodes :
objet = [attributs + méthodes]
Beaucoup de classes offrent des caractéristiques supplémentaires comme par exemple la concaténation des chaînes
en utilisant simplement l’opérateur +. Ceci est obtenu grâce aux méthodes spéciales. Par exemple l’opérateur +
est utilisable car on a redéfini la méthode __add__.
Les objets ont généralement deux sortes d’attributs : les données nommées simplement attributs et les fonctions
applicables appelées méthodes. Par exemple un objet de la classe complex possède :
– imag et real, ses attributs ;
– beaucoup de méthodes, comme conjugate ;
– des méthodes spéciales pour le support des opérateurs : +, -, / ...
Les attributs sont normalement implémentés comme des variables d’instance, particulières à chaque instance
d’objet.
Le mécanisme de property permet un accès contrôlé aux données, ce qui permet de les valider et de les sécuriser.
Un avantage décisif de la POO est qu’une classe Python peut toujours être spécialisée en une classe fille qui
hérite alors de tous les attributs (données et méthodes) de sa supper classe. Comme tous les attributs peuvent
être redéfinis, une méthode de la classe fille et de la classe mère peut posséder le même nom mais effectuer des
traitements différents (surcharge) et Python s’adaptera dynamiquement, dès l’affectation.
En proposant d’utiliser un même nom de méthode pour plusieurs types d’objets différents, le polymorphisme
permet une programmation beaucoup plus générique.
Le développeur n’a pas à savoir, lorsqu’il programme une méthode, le type précis de l’objet sur lequel la méthode
va s’appliquer. Il lui suffit de savoir que cet objet implémentera la méthode.
Enfin Python supporte également le duck typing : “s’il marche comme un canard et cancane comme un canard,
alors c’est un canard !”. Ce qui signifie que Python ne s’intéresse qu’au comportement des objets.
Par exemple un objet fichier peut être créé par open ou par une instance de io.StringIO.
Les deux approches offrent la même API (interface de programmation), c’est-à-dire les mêmes méthodes.
60
Chapitre 2. Presentation de Python
Plone pour les développeurs, Version 1.0.0
2.8.3 Classes et instanciation d’objets
L’instruction class
Syntaxe
Instruction composée : en-tête (avec docstring) + corps indenté :
class C:
"""Documentation de la classe."""
x = 23
Dans cet exemple, C est le nom de la classe (qui commence conventionnellement par une majuscule), et x est un
attribut de classe, local à C.
L’instanciation et ses attributs
– Les classes sont des fabriques d’objets : on construit d’abord l’usine avant de produire des objets !
– On instancie un objet (i.e. création, production depuis l’usine) en appelant le nom de sa classe :
>>> a = C() # a est un objet de la classe C
>>> print(dir(a)) # affiche les attributs de l’objet a
[’__class__’, ’__delattr__’, ’__dict__’, ’__doc__’, ’__format__’, ..., ’x’]
>>> print(a.x) # x est un attribut de classe
23
>>> a.x = 12 # modifie l’attribut d’instance (attention...)
>>> print(C.x) # l’attribut de classe est inchangé
23
>>> a.y = 44 # nouvel attribut d’instance
>>> b = C() # b est un autre objet de la classe C
>>> print(b.x) # b connaît son attribut de classe, mais...
23
>>> print(b.y)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: ’C’ object has no attribute ’y’
L’introspection.
Plusieurs commandes magiques :
– dir fonction d’affichage des membres d’un objet,
– is opérateur testant si les deux membres sont la même instance,
– isinstance fonction testant si une instance est bien d’un type donné,
– help fonction d’affichage de l’aide sur un objet,
– globals permet d’afficher les variables globales,
– locals permet d’afficher les variables locales d’une fonction ou methode.
Plusieurs attributs :
– __class__ pointe vers la classe de l’objet.
– __dict__ si l’objet est un mutable, ce dictionnaire contient la liste des membres de l’instance.
Retour sur les espaces de noms
Tout comme les fonctions, les classes possèdent leurs espaces de noms :
– Chaque classe possède son propre espace de noms. Les variables qui en font partie sont appelées attributs de
classe.
– Chaque objet instance (créé à partir d’une classe) obtient son propre espace de noms. Les variables qui en font
partie sont appelées attributs d’instance.
2.8. La programmation Orientée Objet
61
Plone pour les développeurs, Version 1.0.0
– Les classes peuvent utiliser (mais pas modifier) les variables définies au niveau principal.
– Les instances peuvent utiliser (mais pas modifier) les variables définies au niveau de la classe et les variables
définies au niveau principal. Les espaces de noms sont implémentés par des dictionnaires pour les modules, les
classes et les instances.
– Noms non qualifiés (exemple dimension) l’affectation crée ou change le nom dans la portée locale courante.
Ils sont cherchés suivant la règle LGI.
– Noms qualifiés (exemple dimension.hauteur) l’affectation crée ou modifie l’attribut dans l’espace de
noms de l’objet. Un attribut est cherché dans l’objet, puis dans toutes les classes dont l’objet dépend (mais pas
dans les modules).
L’exemple suivant affiche le dictionnaire lié à la classe C puis la liste des attributs liés à une instance de C :
>>> class C:
...
x = 20
>>> print(C.__dict__)
{’__dict__’: <attribute ’__dict__’ of ’C’ objects>, ’x’: 20,
’__module__’: ’__main__’, ’__weakref__’: <attribute ’__weakref__’ of ’C’ objects>,
’__doc__’: None}
>>> a = C()
>>> print(dir(a))
[’__class__’, ’__delattr__’, ’__dict__’, ’__doc__’, ’
__getattribute__’, ’__hash__’, ’__init__’, ’__module__’, ’__new__’, ’
__reduce__’, ’__reduce_ex__’, ’__repr__’, ’__setattr__’, ’__str__’, ’
__weakref__’, ’x’]
2.8.4 Méthodes
Syntaxe
Une méthode s’écrit comme une fonction du corps de la classe avec un premier paramètre self obligatoire, où
self représente l’objet sur lequel la méthode sera appliquée. 3
Autrement dit self est la référence d’instance.
class C:
x = 23 # x et y : attributs de classe
y = x + 5
def affiche(self): # méthode affiche()
self.z = 42 # attribut d’instance
print(C.y) # dans une méthode, on qualifie un attribut de classe,
print(self.z) # mais pas un attribut d’instance
ob = C() # instanciation de l’objet ob
ob.affiche() # 28 42 (à l’appel, ob affecte self)
2.8.5 Méthodes spéciales
Les méthodes spéciales
Ces méthodes portent des noms pré-définis, précédés et suivis de deux caractères de soulignement.
Elles servent :
– à initialiser l’objet instancié ;
– à modifier son affichage ;
– à surcharger ses opérateurs ;
– ...
3. L’utilisation du terme self est une convention, contrairement à Javascript qui impose le terme this.
62
Chapitre 2. Presentation de Python
Plone pour les développeurs, Version 1.0.0
L’initialisateur
Lors de l’instanciation d’un objet, la méthode __init__ est automatiquement invoquée. Elle permet d’effectuer
toutes les initialisations nécessaires :
>>> class C:
...
def __init__(self, n):
...
self.x = n # initialisation de l’attribut d’instance x
>>> une_instance = C(42) # paramètre obligatoire, affecté à n
>>> print(une_instance.x)
42
C’est une procédure automatiquement invoquée lors de l’instanciation : elle ne contient jamais l’instruction
return. Le cas échéant, celle-ci est ignorée.
Surcharge des opérateurs
La surcharge permet à un opérateur de posséder un sens différent suivant le type de leurs opérandes. Par exemple,
l’opérateur + permet :
x = 7 + 9 # addition entière
s = ’ab’ + ’cd’ # concaténation
Python possède des méthodes de surcharge pour :
– tous les types (__call__, __str__, ...) ;
– les nombres (__add__, __div__, ...) ;
– les séquences (__len__, __iter__, ...).
Soient deux instances, obj1 et obj2, les méthodes spéciales suivantes permettent d’effectuer les opérations
arithmétiques courantes :
Nom
opposé
addition
soustraction
multiplication
division
Méthode spéciale
__neg__
__add__
__sub__
__mul__
__div__
Utilisation
-obj1
obj1 + obj2
obj1 - obj2
obj1 * obj2
obj1 / obj2
Exemple de surcharge
class Vecteur2D:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, autre): # addition vectorielle
return Vecteur2D(self.x + autre.x, self.y + autre.y)
def __str__(self): # affichage d’un Vecteur2D
return "Vecteur({:g}, {:g})" % (self.x, self.y)
v1 = Vecteur2D(1.2, 2.3)
v2 = Vecteur2D(3.4, 4.5)
print(v1 + v2) # Vecteur(4.6, 6.8)
2.8. La programmation Orientée Objet
63
Plone pour les développeurs, Version 1.0.0
2.8.6 Héritage et polymorphisme
Héritage et polymorphisme
Héritage L’héritage est le mécanisme qui permet de se servir d’une classe préexistante pour en créer une nouvelle qui possédera des fonctionnalités différentes ou supplémentaires.
Polymorphisme Le polymorphisme est la faculté pour une méthode portant le même nom mais appartenant à
des classes distinctes héritées d’effectuer un travail différent. Cette propriété est acquise par la technique de
la surcharge.
Exemple d’héritage et de polymorphisme
Dans l’exemple suivant, la classe Carre hérite de la classe Rectangle, et la méthode __init__ est polymorphe :
class Rectangle:
def __init__(self, longueur=30, largeur=15):
self.L, self.l, self.nom = longueur, largeur, "rectangle"
class Carre(Rectangle):
def __init__(self, cote=10):
Rectangle.__init__(self, cote, cote)
self.nom = "carré"
r = Rectangle()
print(r.nom) # ’rectangle’
c = Carre()
print(c.nom) # ’carré’
2.8.7 Retour sur l’exemple initial
La classe Cercle : conception
Nous allons tout d’abord concevoir une classe Point héritant de la classe mère object.
Puis nous pourrons l’utiliser comme classe de base de la classe Cercle.
Dans les schémas UML (Unified Modeling Language ) ci-dessous, les attributs en italiques sont hérités, ceux en
casse normale sont nouveaux et ceux en gras sont redéfinis (surchargés).
La classe Cercle
Voici le code de la classe Point :
class Point:
def __init__(self, x=0, y=0):
self.x, self.y = x, y
@property
def distance_origine(self):
return math.hypot(self.x, self.y)
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def __str__(self):
return "({0.x!s}, {0.y!s})".format(self)
64
Chapitre 2. Presentation de Python
Plone pour les développeurs, Version 1.0.0
F IGURE 2.26 – Conception UML de la classe Cercle.
L’utilisation du décorateur property permet un accès en lecture seule au résultat de la méthode
distance_origine considérée alors comme un simple attribut (car il n’y a pas de parenthèse) :
if __name__ == "__main__":
p1, p2 = Point(), Point(3, 4)
print(p1 == p2) # False
print(p2, p2.distance_origine) # (3, 4) 5.0
De nouveau, les méthodes renvoyant un simple flottant seront utilisées comme des attributs grâce à property :
class Cercle(Point):
def __init__(self, rayon, x=0, y=0):
super().__init__(x, y)
self.rayon = rayon
@property
def aire(self):
return math.pi * (self.rayon ** 2)
@property
def circonference(self):
return 2 * math.pi * self.rayon
@property
def distance_bord_origine(self):
return abs(self.distance_origine - self.rayon)
Voici la syntaxe permettant d’utiliser la méthode rayon comme un attribut en lecture-écriture.
Remarquez que la méthode rayon retourne l’attribut protégé : __rayon qui sera modifié par le setter (la
méthode modificatrice) :
class Cercle(Cercle):
@property
def rayon(self):
return self.__rayon
2.8. La programmation Orientée Objet
65
Plone pour les développeurs, Version 1.0.0
@rayon.setter
def rayon(self, rayon):
assert rayon > 0, "rayon strictement positif"
self.__rayon = rayon
Exemple d’utilisation des instances de Cercle :
class Cercle(Cercle):
def __eq__(self, other):
return (self.rayon == other.rayon
and super().__eq__(other))
def __str__(self):
return ("{0.__class__.__name__}({0.rayon!s}, {0.x!s}, "
"{0.y!s})".format(self))
if __name__ == "__main__":
c1 = Cercle(2, 3, 4)
print(c1, c1.aire, c1.circonference)
# Cercle(2, 3, 4) 12.5663706144 12.5663706144
print(c1.distance_bord_origine, c1.rayon) # 3.0 2
c1.rayon = 1 # modification du rayon
print(c1.distance_bord_origine, c1.rayon) # 4.0 1
2.8.8 Notion de Conception Orientée Objet
Suivant les relations que l’on va établir entre les objets de notre application, on peut concevoir nos classes de deux
façons possibles :
– la composition qui repose sur la relation a-un ou sur la relation utilise-un ;
– la dérivation qui repose sur la relation est-un.
Bien sûr, ces deux conceptions peuvent cohabiter, et c’est souvent le cas !
Composition
Composition La composition est la collaboration de plusieurs classes distinctes via une association (utilise-un)
ou une aggrégation (a-un).
La classe composite bénéficie de l’ajout de fonctionnalités d’autres classes qui n’ont rien en commun.
L’implémentation Python utilisée est généralement l’instanciation de classes dans le constructeur de la classe
composite.
Exemple :
class Point:
def __init__(self, x, y):
self.px, self.py = x, y
class Segment:
"""Classe composite utilisant la classe distincte Point."""
def __init__(self, x1, y1, x2, y2):
self.orig = Point(x1, y1) # Segment "a-un" Point origine,
self.extrem = Point(x2, y2) # et "a-un" Point extrémité
def __str__(self):
return ("Segment : [({:g}, {:g}), ({:g}, {:g})]"
.format(self.orig.px, self.orig.py,
self.extrem.px, self.extrem.py))
66
Chapitre 2. Presentation de Python
Plone pour les développeurs, Version 1.0.0
s = Segment(1.0, 2.0, 3.0, 4.0)
print(s) # Segment : [(1, 2), (3, 4)]
2.8.9 Dérivation
Dérivation La dérivation décrit la création de sous-classes par spécialisation.
On utilise dans ce cas le mécanisme de l’héritage.
L’implémentation Python utilisée est généralement l’appel dans le constructeur de la classe dérivée du constructeur
de la classe parente, soit nommément, soit grâce à l’instruction super.
Exemple :
class Rectangle:
def __init__(self, longueur=30, largeur=15):
self.L, self.l, self.nom = longueur, largeur, "rectangle"
class Carre(Rectangle): # héritage simple
"""Sous-classe spécialisée de la super-classe Rectangle."""
def __init__(self, cote=20):
# appel au constructeur de la super-classe de Carre :
super().__init__(cote, cote)
self.nom = "carré" # surcharge d’attribut
2.9 Quelques Techniques avancées de programmation
Ce chapitre présente quelques exemples de techniques avancées dans les trois paradigmes que supporte Python,
les programmations procédurale, objet et fonctionnelle.
2.9.1 Techniques procédurales
Améliorer la documentation
La fonction utilitaire printApi filtre les méthodes disponibles de element, et affiche les docstrings associés sous
une forme plus lisible que help :
def printApi(element):
methods = [el for el in dir(element) if not el.startswith(’_’)]
for meth in methods:
print(getattr(element, meth).__doc__)
if __name__ == "__main__":
printApi([])
Son exécution produit :
L.append(object) -- append object to end
L.count(value) -> integer -- return number of occurrences of value
L.extend(iterable) -- extend list by appending elements from the iterable
L.index(value, [start, [stop]]) -> integer -- return first index of value.
Raises ValueError if the value is not present.
L.insert(index, object) -- insert object before index
L.pop([index]) -> item -- remove and return item at index (default last).
Raises IndexError if list is empty or index is out of range.
L.remove(value) -- remove first occurrence of value.
Raises ValueError if the value is not present.
L.reverse() -- reverse *IN PLACE*
2.9. Quelques Techniques avancées de programmation
67
Plone pour les développeurs, Version 1.0.0
L.sort(cmp=None, key=None, reverse=False) -- stable sort *IN PLACE*;
cmp(x, y) -> -1, 0, 1
Faire des menus avec un dictionnaire
On peut utiliser le couple clé : valeur d’un dictionnaire pour implémenter un menu, de la façon suivante :
– on donne à cle le nom d’un élément du menu ;
– la valeur correspondante est une référence : l’appel à une procédure sans argument.
L’exemple proposé est la programmation d’une queue FIFO, une structure de données illustrée par la file d’attente
à un guichet : le premier arrivé est le premier servi.
Source du module de fonctions :
"""Module de gestion d’une queue FIFO."""
queue = [] # initialisation
def enQueue():
queue.append(int(input("Entrez un entier : ")))
def deQueue():
if len(queue) == 0:
print("\nImpossible : la queue est vide !")
else:
print("\nElément ’%d’ supprimé" % queue.pop(0))
def afficheQueue():
print("\nqueue :", queue)
Source du menu de gestion :
"""Implémentation d’une queue FIFO avec une liste.
Un menu (utilisant un dictionnaire)
appelle des procédures sans argument.
"""
# import
from queue_FIFO_menu_m import enQueue, deQueue, afficheQueue
# programme principal ----------------------------------------------afficheQueue()
CMDs = {’a’:enQueue, ’v’:afficheQueue, ’s’:deQueue}
menu = """
(A)jouter
(V)oir
(S)upprimer
(Q)uitter
Votre choix ? """
while True:
while True:
try:
choix = input(menu).strip()[0].lower()
except:
choix = ’q’
if choix not in ’avsq’:
print("Option invalide ! Réessayez")
else:
68
Chapitre 2. Presentation de Python
Plone pour les développeurs, Version 1.0.0
break
if choix == ’q’:
print("\nAu revoir !")
break
CMDs[choix]()
Expressions régulières
Le module re permet d’accéder à la richesse des expressions rationnelles (“régulière” est une traduction malheureuse du terme anglais “regular”).
Expression rationnelle C’est une suite de caractères qui permet de décrire d’autres chaînes de caractères (les
motifs) afin de les retrouver dans un bloc de caractères.
La construction de l’expression repose sur l’usage de caractères qui seront interprétés comme joker, répéteur ou
groupe et des autres caractères interprétés comme motif.
Ainsi :
– (experssion) indique un groupe,
– [caractères] indique un ensemble de caractère, ex :
– [aez] soit a, soit e, soit z
– [a-z] tout de a à z
– [^caractères] indique tout caractère autres que ceux de l’ensemble, ex [^a-c] tout sauf a, b, c,
– . tout caractère,
– | indique une alternative,
– ? indique que le motif précédent peut être ou non présent,
– * indique la possibilité de répéter le motif indéfiniment ou aucune fois,
– + indique la possibilité de répéter le motif au moins une fois.
Les symboles peuvent être échappés par exemple : [[] indique le caractère [
Beaucoup d’autres symboles ont un sens. Le module sre plus riche en donne la liste.
Une fois l’expression construite nous allons l’utiliser pour trouver les motifs d’une chaine :
>>> import re
>>> expression_rationnelle = "[a-zA-Z]*"
>>> phrase = "Cette phrase contient cinq motifs"
>>> [phrase[x.start(): x.end()] for x in
... re.finditer(expression_rationnelle, phrase) if x.end() != x.start()]
[’Cette’, ’phrase’, ’contient’, ’cinq’, ’motifs’]
>>> re.match(expression_rationnelle, phrase)
<_sre.SRE_Match object at ...>
La fonction finditer permet de retourner un liste d’objet Match.
Les objets Match permettent de connaitre le début et la fin d’un motif, si la fin est égale au début c’est qu’il ne
s’agit pas d’un motif reconnu.
L’expression rationnelle est analysée à chaque demande ce qui consomme de la puissance pour rien. On peut alors
optimiser en compilant l’expression :
>>> import re
>>> expression_rationnelle = "[a-zA-Z]*"
>>> phrase = "Cette phrase contient cinq motifs"
>>> erc = re.compile(expression_rationnelle)
>>> re.match(erc, phrase)
<_sre.SRE_Match object at ...
Les fonctions récursives
Fonction récursive Une fonction récursive peut s’appeler elle-même.
2.9. Quelques Techniques avancées de programmation
69
Plone pour les développeurs, Version 1.0.0
Par exemple, trier un tableau de N éléments par ordre croissant c’est extraire le plus petit élément puis trier le
tableau restant à N - 1 éléments.
Bien que cette méthode soit souvent plus difficile à comprendre et à coder que la méthode classique dite itérative,
elle est, dans certains cas, l’application la plus directe de sa définition mathématique.
Voici l’exemple classique de définition par récurrence de la fonction factorielle :
n! = | 1 si n = 0
| n (n - 1)! si n > 1
Note : Son code est très exactement calqué sur sa définition mathématique :
def factorielle(n):
if n == 0: # cas de base : condition terminale
return 1
else # cas récursif
return n*factorielle(n-1)
Dans cette définition, la valeur de n n’est pas connue tant que l’on n’a pas atteint la condition terminale (ici n ==
0). Le programme empile les appels récursifs jusqu’à atteindre la condition terminale puis dépile les valeurs.
Les générateurs et les expressions génératrices
Les générateurs
Les générateurs fournissent un moyen de générer des exécutions paresseuses 4 , ce qui signifie qu’elles ne calculent
que les valeurs réellement demandées. Les générateurs s’appuient sur le mot-clé yield.
Cette instruction suspend l’exécution de la fonction qui l’exécute, à la façon de return et retourne l’argument à
sa droite. Lors de l’appel suivant, l’exécution reprend à la suite de la ligne contenant le yield.
Ceci peut s’avérer beaucoup plus efficace (en terme de mémoire) que le calcul, par exemple, d’une énorme liste
en une seule fois.
Voici un exemple de générateur qui fournit autant de valeurs que demandées :
def quarters(next_quarter=0.0):
while True:
yield next_quarter
next_quarter += 0.25
if __name__ == "__main__":
result = []
for x in quarters():
result.append(x)
if x == 1.0:
break
print("Liste résultante : ", result)
# Liste résultante : [0.0, 0.25, 0.5, 0.75, 1.0]
Il est aussi possible de passer une initialisation au générateur :
import sys
def quarters(next_quarter=0.0):
while True:
received = (yield next_quarter)
if received is None:
4. “Paresseux” est une traduction littérale du terme anglais “lazy”.
70
Chapitre 2. Presentation de Python
Plone pour les développeurs, Version 1.0.0
next_quarter += 0.25
else:
next_quarter += received
if __name__ == "__main__":
result = []
generator = quarters()
while len(result) < 5:
x = next(generator)
if abs(x - 0.5) < sys.float_info.epsilon:
x = generator.send(1.0) # réinitialise le générarteur
result.append(x)
print("Liste résultante : ", result)
# Liste résultante : [0.0, 0.25, 1.5, 1.75, 2.0]
Syntaxe
Une expression génératrice possède une syntaxe presque identique à celle des listes en intension ; la différence est
qu’une expression génératrice est entourée de parenthèses.
Utilisation
Les expressions génératrices sont aux générateurs ce que les listes en intension sont aux fonctions.
Les fonctions incluses
La syntaxe de définition des fonctions en Python permet tout à fait d’emboîter leur définition. Distinguons deux
cas d’emplois :
– Idiome de la fonction fabrique renvoyant une fermeture :
def creer_plus(ajout):
"""Fonction ’fabrique’."""
def plus(increment):
"""Fonction ’fermeture’ : utilise des noms locaux à creer_plus()."""
return increment + ajout
return plus
# Programme principal ----------------------------------------------## création de deux fabriques distinctes
p = creer_plus(23)
q = creer_plus(42)
## utilisation
print("p(100) =", p(100))
print("q(100) =", q(100))
Fonction fabrique renvoyant une classe :
# classes
class CasNormal(object):
def uneMethode(self):
print("normal")
class CasSpecial(object):
def uneMethode(self):
print("spécial")
# fonction
def casQuiConvient(estNormal=True):
2.9. Quelques Techniques avancées de programmation
71
Plone pour les développeurs, Version 1.0.0
"""Fonction fabrique renvoyant une classe."""
return CasNormal() if estNormal else CasSpecial()
# Programme principal ----------------------------------------------une_instance = casQuiConvient()
une_instance.uneMethode() # normal
autre_instance = casQuiConvient(False)
autre_instance.uneMethode() # spécial
Les décorateurs
Note : On utilise un décorateur lorsqu’on a besoin d’effectuer un prétraitement lors de l’appel d’une fonction ou
un post-traitement à la suite de l’appel d’une fonction.
Soit le prétraitement suivant :
def pretraitement(fonction):
fonction.__doc__ += "(fonction décorée)."
return fonction
def traitement():
"""ma fonction """
print("traitement")
traitement = pretraitement(traitement)
print(traitement.__doc__) # ma fonction (fonction décorée).
Nous obtenons le même résultat en utilisant un décorateur :
def pretraitement(fonction):
fonction.__doc__ += "(fonction décorée)."
return fonction
@pretraitement
def traitement():
"""ma fonction """
print("traitement")
print(traitement.__doc__) # ma fonction (fonction décorée).
Enfin il est possible d’enchaîner les décorateurs (à l’image de la composition des fonctions en mathématique) :
@f1 @f2 @f3
def fonction():
pass
# Notation équivalente à : fonction = f1(f2(f3(fonction)))
2.9.2 Techniques objets
Comme nous l’avons vu lors du chapitre précédent, Python est un langage complètement objet.
Tous les types de base ou dérivés sont en réalité des types abstrait de données implémentés sous forme de classe.
Toutes ces classes dérivent d’une unique classe de base, ancêtre de tous les autres : la classe object.
72
Chapitre 2. Presentation de Python
Plone pour les développeurs, Version 1.0.0
__slot__ et __dict__
Examinons le code suivant :
>>> class Point:
...
__slots__ = ("x", "y")
...
def __init__(self, x=0, y=0):
...
self.x = x
...
self.y = y
>>> p=Point()
>>> p.v = "Interdit"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: ’Point’ object has no attribute ’v’
Quand une classe est créée sans utiliser l’attribut __slots__, ce que nous avons fait jusqu’à maintenant, Python
crée de manière transparente un dictionnaire privé appelé __dict__ pour chaque instance de la classe, et ce
dictionnaire contient les attributs de l’instance.
Voici pourquoi il est possible d’ajouter ou de retirer des attributs d’un objet.
Mais si l’on se contente d’objets sur lesquels nous accédons aux attributs sans en ajouter ou en ôter, nous pouvons
créer des classes sans dictionnaire privé, ce qui économisera de la mémoire à chaque instanciation.
C’est ce qui est réalisé dans l’exemple ci-dessus en définissant un attribut de classe __slots__ dont la valeur
est un tuple formé des noms des attributs.
Warning : Sous python 2 la classe utilisant slots doit être dérivée de object.
Functor
En Python un objet fonction ou fonctor est une référence à tout objet appelable (callable) : fonction, fonction
lambda, méthode, classe.
La fonction prédéfinie callable permet de tester cette propriété :
>>> def maFonction():
...
print(’Ceci est "appelable"’)
...
>>> callable(maFonction)
True
>>> chaine = ’Ceci est "appelable"’
>>> callable(chaine)
False
Il est possible de transformer les instances d’une classe en functor si la méthode spéciale __call__ est définie
dans la la classe :
>>> class A:
def __call__(self, un , deux):
return un + deux
>>> a = A()
>>> callable(a)
True
>>> a(1, 6)
7
Les gestionnaires de contexte
Les gestionnaires de contexte simplifient nos codes en assurant que certaines opérations sont effectuées avant et
après un bloc d’instruction donné.
2.9. Quelques Techniques avancées de programmation
73
Plone pour les développeurs, Version 1.0.0
Syntaxe
On utilise l’instruction with.
with_block
::=
“with” expression “as” symbol ”:” suite
Une utilisation classique est d’assurer la fermeture d’un fichier :
with open("hello.txt") as f:
for line in f:
print line
L’ <expression> retourne un gestionnaire de contexte associé à <variable>. un gestionnaire de contexte est un
objet quelconque fournissant les méthodes __enter__ et __exit__.
La méthode __enter__ de l’objet retourné par l’expression est exécutée par le mot-clé with. L’objet retourné
par cette méthode est associé au symbole suivant le mot-clé as. Généralement, il s’agit du gestionnaire de contexte
lui-même (donc return self).
Lors de la cloture du bloc commencé par with, la méthode __exit__ du gestionnaire de ocontexte est appellée,
avec les informations sur une éventuelle exception survenue dans le bloc. Le rôle de cette méthode est d’effectuer
l’éventuel “nettoyage” associé à la fin d’utilisation du gestionnaire de contexte.
2.9.3 Techniques fonctionnelles
Directive lambda
Issue de langages fonctionnels (comme Lisp), la directive lambda permet de définir un objet fonction anonyme
dont le retour est limité à une expression.
Syntaxe
lambda_stmt
::=
“lambda” [parametres] ”:” expression
Par exemple cette fonction retourne “s” si son argument est différent de 1, une chaîne vide sinon :
>>> s = lambda x: "" if x == 1 else "s"
>>> s(0)
’s’
>>> s(1)
’’
>>>
Dans la présentation des interfaces graphiques, on verra que les callbacks sont souvent codés ainsi :
f = lambda x=1, y=1, z=0: 3*x + 2*y - z
print(f()) # 5
def make_increment(n):
return lambda x: x + n
f2, f6 = make_increment(2), make_increment(6)
print(f2(42), f6(42)) # 44 48
lc = [lambda x: x**2, lambda x: x**3, lambda x: x**4]
for f in lc:
print(f(2), end=" ") # 4 8 16
74
Chapitre 2. Presentation de Python
Plone pour les développeurs, Version 1.0.0
Les fonctions map, filter et reduce
La programmation fonctionnelle est un paradigme de programmation qui considère le calcul en tant qu’évaluation
de fonctions mathématiques et rejette le changement d’état et la mutation des données.
Elle souligne l’application des fonctions, contrairement au modèle de programmation impérative qui met en avant
les changements d’état.
Le paradigme fonctionnel n’utilise pas de machine d’états pour décrire un programme, mais un emboîtement de
fonctions que l’on peut voir comme des “boîtes noires” que l’on peut imbriquer les unes dans les autres.
Chaque boîte possédant plusieurs paramètres en entrée mais une seule sortie, elle ne peut sortir qu’une seule valeur
possible pour chaque n-uplet de valeurs présentées en entrée. Ainsi, les fonctions n’introduisent pas d’effets de
bord.
Un programme est donc une application, au sens mathématique, qui ne donne qu’un seul résultat pour chaque
ensemble de valeurs en entrée.
La programmation fonctionnelle repose sur trois concepts : mapping, filtering et reducing qui sont implémentésen
Python par trois fonctions : map, filter et reduce.
La fonction map()
map applique une fonction à chaque élément d’une séquence et retourne un itérateur :
>>> def renvoiTitres(element):
...
return element.title()
>>> map(renvoiTitres, [’fiat lux’, "lord of the fly"])
[’Fiat Lux’, ’Lord Of The Fly’]
>>>
>>> [element.title() for element in [’fiat lux’, "lord of the fly"]]
[’Fiat Lux’, ’Lord Of The Fly’]
On remarque que map peut être remplacée par une liste en intension. Voir Les listes en intension.
La fonction filter()
filter construit et renvoie un itérateur sur une liste qui contient tous les éléments de la séquence initiale répondant au critère function(element) == True :
>>>
[1,
>>>
>>>
[1,
filter(lambda x: x > 0, [1, -2, 3, -4])
3]
[x for x in [1, -2, 3, -4] if x > 0]
3]
On remarque que filter peut être remplacée par une liste en intension avec un test.
La fonction reduce()
reduce est une fonction du module functools. Elle applique de façon cumulative une fonction de deux arguments
aux éléments d’une séquence, de gauche à droite, de façon à réduire cette séquence à une seule valeur qu’elle
renvoie :
>>> def somme(x, y):
...
print x, ’+’, y
...
return x + y
>>> reduce(somme, [1, 2, 3, 4])
1 + 2
2.9. Quelques Techniques avancées de programmation
75
Plone pour les développeurs, Version 1.0.0
3 + 3
6 + 4
10
>>>
>>> sum([1, 2, 3, 4])
10
On remarque que reduce peut être remplacée par une des fonctions suivantes : all, any, func :max, min ou
func :sum.
Les applications partielles de fonctions
Issue de la programmation fonctionnelle, une PFA (application partielle de fonction) de n paramètres prend le
premier argument comme paramètre fixe et retourne un objet fonction (ou instance) utilisant les n-1 arguments
restants.
Exemple simple :
>>> from functools import partial
>>> baseTwo = partial(int, base=2)
>>> baseTwo(’10010’)
18
Les PFA sont très utiles pour fournir des modèles partiels de widgets, qui ont souvent de nombreux paramètres.
Dans l’exemple suivant, on redéfinie la classe Button en fixant certains de ses attributs (qui peuvent toujours
être surchargés) :
from functools import partial
import tkinter as tk
root = tk.Tk()
# instanciation partielle de classe :
MonBouton = partial(tk.Button, root, fg=’purple’, bg=’green’)
MonBouton(text="Bouton 1").pack()
MonBouton(text="Bouton 2").pack()
MonBouton(text="QUITTER", bg=’red’, fg=’black’,
command=root.quit).pack(fill=tk.X, expand=True)
root.title("PFA !")
root.mainloop()
2.9.4 Exercice
Lecture d’un fichier, traitement avec une expression régulière et écriture des résultats dans un fichier.
76
Chapitre 2. Presentation de Python
CHAPITRE 3
ZODB - Une Base de données objet
native pour python
N’éclatez pas vos objets dans des tables : stockez les dans une base objet.
3.1 Avertissement
Ce document est une adaptation libre de la documentation accessible à http ://www.zodb.org/ par Michael Launay.
3.2 Licence
Ce document et régi par la licence Creative Common BY SA, vous pouvez donc le partager, le modifier tant que
vous citez votre source initiale et que vous conservez les termes de la licence initiale.
3.3 Résumé
Les programmes Python sont généralement écrits en suivant le paradigme orienté objet. Vous pouvez utiliser des
objets qui se référencent les uns les autres sans contraintes de type ou d’architecture : les objets ne sont liés à
aucun schéma spécifique et peuvent porter n’importe quelle information.
Stocker ces objets dans une base de données relationnelle exige que vous sacrifiez votre liberté d’architecture et la
puissance des références. Les contraintes des bases relationnelles réduisent votre capacité à écrire du code orienté
objet.
La ZODB est une base de données objet native, qui stocke vos objets et vous permet de travailler selon n’importe
lequel des paradigmes possibles en Python. En conséquence votre code est plus simple, plus robuste et plus
compréhensible.
De plus il n’y a pas de gap entre la base de données et le programme : pas de code “glue” à écrire, ni de mappings
à configurer. Le tutoriel vous montre à quel point c’est facile à utiliser.
Voici quelques- unes des fonctionnalités que la ZODB vous apporte :
– Persistance transparente pour les objets Python.
– Support des transactions ACID (avec les points de sauvegardes).
– Historique et fonctionnalité de défaire refaire (undo).
– Support efficace pour les objets binaires volumineux (BLOBs).
– Dépôts pluggables.
– Architecture adaptable.
77
Plone pour les développeurs, Version 1.0.0
3.4 Documentation
3.4.1 Introduction
Ce guide explique comment écrire des programmes Python qui utilise la Z Objects Database (ZODB)
et le Zope Enterprise Objects (ZEO). La dernière version de ce guide est accessible en anglais à
http ://www.zope.org/Wikis/ZODB/guide/index.html.
Qu’est ce que la ZODB ?
La ZODB est un système de persistance pour les objets Python. Les langages de programmation persistants offrent
la possibilité d’écrire automatiquement les objets vers le disque et de les relire quand ils sont requis par le programme. En installant la ZODB, vous ajoutez cette possibilité à Python.
Il vous est possible de créer votre propre système de persistance des objets Python. Le point de départ habituel
d’une telle construction est d’utiliser le module pickle, qui convertit les objets en une représentation texte,
ainsi que différents modules de base de données, tels que les modules gdbm ou bsdbd, qui fournissent un moyen
d’écrire des chaînes vers le disque et de les relire. Il est très puissant de combiner le module pickle et un module
de base de données pour stocker et restituer les objets ; le module shelve qui fait partie de la bibliothèque
standard de Python, fait exactement cela.
La difficulté pour le développeur est de gérer explicitement les objets, lire un objet quand il en a besoin et l’écrire
sur le disque lorsque l’objet n’est plus requis. La ZODB gère les objets pour vous, les gardes en cache, les écrit
sur le disque lorsqu’ils sont modifiés et les supprime du cache s’ils n’ont pas été utilisés depuis un moment.
OODBs vs. DBs relationnelles
La ZODB est une base de données orientée objet (OODB) spécifique à Python. Les bases de données objets
commerciales pour le C++ et Java réclament souvent de surmonter certains obstacles tels que l’usage d’un préprocesseur dédié ou que vous n’utilisiez pas certains types. Comme nous allons le voir, la ZODB a aussi quelques
pièges à éviter, mais en comparaison la simplicité de la ZODB est à couper le souffle.
Les bases de données relationnelles (RDBs) sont plus communes que les OOBDs. Les bases de données relationnelles stockent les informations dans des tables. Une table consiste en un ensemble de lignes, chaque ligne
contient plusieurs colonnes d’informations. (Les lignes sont généralement appelées des relations, et c’est de là que
vient le nom de base de données relationnelles).
Mais regardons un cas concret. Notre exemple est issu d’une journée de travail de l’auteur initial à MEMS Exchange, en version simplifiée. Le travail consiste à tracer l’exécution de processus, qui sont constitués d’une liste
d’étapes de fabrication dans une usine de semi-conducteurs. L’exécution est réalisée par un utilisateur unique,
identifié par un nom et un numéro d’identification. L’exécution est constituée par un nombre déterminé d’opérations, comme déposer quelque chose sur le wafer ou en enlever quelque chose.
Les opérations peuvent avoir des paramètres, qui sont des informations additionnelles requises pour réaliser l’opération. Par exemple, si vous déposez quelque chose sur wafer, vous avez besoins de savoir deux choses : 1) qu’est
ce que vous déposez, 2) quelle quantité vous devez déposer. Par exemple vous déposez 100 microns d’oxide de
silicium ou 1 micron de cuivre.
Mapper cette structure vers une base de données relationnelles est direct :
CREATE TABLE runs (
int
run_id,
varchar owner,
varchar title,
int
acct_num,
primary key(run_id)
);
CREATE TABLE operations (
int
run_id,
78
Chapitre 3. ZODB - Une Base de données objet native pour python
Plone pour les développeurs, Version 1.0.0
int
step_num,
varchar process_id,
PRIMARY KEY(run_id, step_num),
FOREIGN KEY(run_id) REFERENCES runs(run_id),
);
CREATE TABLE parameters (
int
run_id,
int
step_num,
varchar param_name,
varchar param_value,
PRIMARY KEY(run_id, step_num, param_name)
FOREIGN KEY(run_id, step_num)
REFERENCES operations(run_id, step_num),
);
En Python, vous pouvez écrire trois classes nommées Run, Operation, and Parameter. Je ne présenterai
pas le code de définition des ces classes, car le code est en dehors du propos. Chaque classe devra contenir un
constructeur __init__ qui créera les attributs et leur assignera des valeurs par défaut.
Il n’est pas difficile d’écrire du code Python qui instanciera la classe Run et la remplira avec les données venant d’une base de données relationnelle, vous pouvez aussi créer directement un utilitaire pour cette tâche classiquement appelée un mapper objet-relationnel. (Voir http ://www.amk.ca/python/unmaintained/ordb.html pour
un aperçu rapide d’un test de mapper objet - relationnel en Python, et http ://www.python.org/workshops/199710/proceedings/shprentz.html pour un autre qui est basé sur la même idée mais qui est utilisé en production).
Néanmoins, il est difficile de réaliser un mapper objet-relationnel rapide ; une implémentation simple comme celle
réalisée est très lente car elle doit réaliser plusieurs requêtes pour accéder à toutes les données des objets. Plusieurs
techniques de cache peuvent optimiser les requêtes SQL et les limiter au strict nécessaire.
Cela aide si vous souhaitez accéder à l’exécution numéro 123. Mais qu’en est il si vous voulez trouver toutes
les exécutions dont l’une des étapes possède un paramètre nommé ‘thickness’ avec une valeur de 2,0 ? Dans une
version relationnelle, vous avez deux possibilités peu attrayantes :
1. Écrire une requête SQL spécialement pour ce cas : SELECT run_id FROM operations WHERE
param_name = ’thickness’ AND param_value = 2.0
Si de telles requêtes sont communes, vous pouvez vous retrouver avec énormément de requêtes de ce type.
Quand vous modifierez la structure de votre base ou de votre code vous aurez à maintenir toutes vos requêtes.
2. Utiliser un mapper objet relationnel ne vous aidera pas beaucoup plus. Chercher parmi les exécutions signifie
que le mapper déroulera la requête SQL pour lire l’exécution numéro 1 et qu’un code Python vérifiera si
l’une de ses étapes possède le paramètre que nous cherchons. Même chose pour l’exécution 2 puis 3 etc. Ce
qui aboutit a traitement de nombreuses requêtes SQL, et risque d’être extrêmement long.
Une base de données comme la ZODB va simplement stocker la référence interne entre objets, en conséquence
lire un objet unique est plus rapide qu’envoyer une série de requêtes et d’assembler les résultats. Scanner toute les
exécutions, est également inefficace.
Qu’est ce que la ZEO ?
La ZODB est fournie avec quelques classes qui implémentent l’interface Storage. Ces classes sont responsables du travail d’écriture des objets Python vers un média de stockage, qui peut être un fichier sur le disque
(la classe FileStorage), un fichier BerkleyDB (BDBFullStorage), une base de données relationnelle
(DCOracleStorage), ou tout autre média. La ZEO ajoute ClientStorage, un nouveau Storage qui
n’écrit pas vers un média physique mais qui envoie chaque requête à travers le réseau vers un serveur. Le serveur
qui fait fonctionner une instance de la classe StorageServer, va simplement agir comme le ‘front end’ d’un
média physique de la classe Storage. C’est une idée simple et efficace qui comme on le verra dans la suite du
document ouvre énormément de possibilités.
3.4. Documentation
79
Plone pour les développeurs, Version 1.0.0
À propos de ce guide
Les premiers auteurs de ce guide travaillaient sur un projet qui utilisait la ZODB et ZEO comme principale
technologie de stockage. Ils utilisaient la ZODB pour y stocker les processus, les opérations, un catalogue des
processus possibles, des informations utilisateurs, des informations sur les comptes et autres données. Un des
objectifs de ce document et de partager notre expérience. Ce guide est un concentré de toute l’expérience acquise
lors des nombreuses heures passées à essayer de résoudre un problème. Il a pour but d’éviter à d’autres de passer
du temps sur les même erreurs que celles que nous avons faites lors de notre apprentissage.
Les auteurs du projet ZODB sont présentés dans un article accessible ici http ://www.amk.ca/python/writing/mxarchitecture/
Ce guide et en évolution constante. Si vous souhaitez contribuer et suggérer des éclaircissements ou de nouveau
chapitre, envoyez vos commentaires à [email protected].
Remerciements
Andrew Kuchling est l’auteur initial de la première version anglaise de ce guide, il a fournit la première documentation de la ZODB pour les programmeur Python. Sa version a été complété de nombreuse fois par Jeremy Hylton
et Tim Peters.
Les auteurs remercient les personnes qui ont relevé les inexactitudes et les bogues, qui ont proposé des suggestions
sur le texte, ou de nouveau sujet qui ont put être traités : Jeff Bauer, Willem Broekema, Thomas Guettler, Chris
McDonough, George Runyan.
Traduction en Français par Michael Launay
3.4.2 Tutoriel
Ce tutoriel a pour objectif de guider les développeurs avec une introduction pas à pas sur comment développer
une application qui stocke ses données dans la ZODB.
Introduction
Commençons par un petit morceau de code que nous allons rendre fonctionnel avec la ZODB :
class Account(object):
def __init__(self):
self.balance = 0.0
def deposit(self, amount):
self.balance += amount
def cash(self, amount):
assert amount < self.balance
self.balance -= amount
Ce code définit une classe simple qui gère le solde d’un compte en banque et fournit deux méthodes pour manipuler
le solde : deposit et cash.
Installation
Avant d’être capable d’utiliser la ZODB nous avons à l’installer, en utilisant easy_install. Remarquez bien que le
paquet s’appelle en fait “ZODB3” :
$ easy_install ZODB3
...
$ python
>>> import ZODB
80
Chapitre 3. ZODB - Une Base de données objet native pour python
Plone pour les développeurs, Version 1.0.0
La ZODB est maintenant installée et peut être importée depuis votre installation Python.
Note : Si vous ne disposez pas de la commande easy_install sur votre système, suivez les instructions d’installation EasyInstall
Configuration
Lorsqu’un programme souhaite utiliser la ZODB il doit établir une connexion, comme pour les autres bases données. Pour la ZODB nous avons besoins de trois éléments différents : un système de stockage, une base de données
et une connexion : :
>>>
>>>
>>>
>>>
>>>
>>>
from ZODB.FileStorage import FileStorage
from ZODB.DB import DB
storage = FileStorage(’Data.fs’)
db = DB(storage)
connection = db.open()
root = connection.root()
Nous créons un système de stockage appelé FileStorage, qui est le système par défaut utilisé par pratiquement tout
le monde. Il garde les traces de toutes les données dans un fichier unique sur le disque nommé avec le premier
paramètre fourni à l’instanciation. Enfin, nous récupérons la racine de la base de données à partir de la connexion
que nous avons ouverte.
Stocker des objets
Pour stocker un objet dans la ZODB nous avons simplement à le lier à n’importe quel objet qui est déjà dans la
base de données. En fait, l’objet racine fonctionne comme un point d’entrée. L’objet racine est un dictionnaire et
vous pouvez commencer à y stocker directement vos objets de la façon suivante : :python :
>>> root[’account-1’] = Account()
>>> root[’account-2’] = Account()
Note : Les frameworks comme Zope ne créent qu’un seul objet à la racine, qui représente l’application elle
même, et sous lequel chaque sous-objet est référencé. Ils choisissent un nom comme ‘app’ pour le premier objet
qu’il place dans la ZODB.
Transactions
Maintenant vous avez deux objets placés dans votre objet racine et dans votre base de données. Toutefois ils ne
sont pas encore stockés de façon permanente. La ZODB utilise le mécanisme des transactions et pour rendre vos
modifications permanentes vous devez terminer votre transaction : :python :
>>> import transaction
>>> transaction.commit()
>>> root.keys()
[’account-1’, ’account-2’]
Maintenant vous pouvez arrêter et redémarrer votre application et observer votre objet racine à nouveau, vous y
trouverez les entrées ‘account-1’ et ‘account-2’ encore présentes ainsi que les objets que vous avez créés.
Si votre code réalise des modifications pendant une transaction et votre base se trouve dans un état non souhaité, il
est possible de ne pas livrer les modifications. Vous pouvez annuler la transaction ce qui restituera les objets dans
l’état d’avant le début de transaction : :python :
3.4. Documentation
81
Plone pour les développeurs, Version 1.0.0
>>> del root[’account-1’]
>>> root.keys()
[’account-2’]
>>> transaction.abort()
>>> root.keys()
[’account-1’, ’account-2’]
Warning : Les objets qui n’ont pas été stockés dans la ZODB ne seront pas restitués par la commande abort.
Objets persistants
Un des derniers aspects que nous devons couvrir est la persistance des objets par eux-mêmes. La ZODB peut
stocker presque tous les objets que vous lui donnez (elle ne stocke pas les fichiers tels quels par exemple). Mais
en fait pour savoir quel objet a été modifié la ZODB a besoin que ces objets collaborent avec la base de données.
En général, vous vous contentez de dériver de la classe persistent.Persistent pour que cela fonctionne. Appliqué à
notre classe d’exemple donne :python :
import persistent
class Account(persistent.Persistent):
# ... same code as above ...
Résumé
Vous avez vu comment installer la ZODB et comment ouvrir une connexion avec la base de données dans votre
application pour pouvoir stocker des objets. Nous avons également abordé deux commandes simple commit et
abort. La documentation de référence contient plus d’informations sur chaque sujet.
3.4.3 Programmation ZODB
Installation de la ZODB
La ZODB est empaquetée à l’aide des outils standards ‘distutils’.
Vous avez besoin au minimum d’une version de Python supérieure ou égale à 2.3.5.
Installez la ZODB avec la commande easy_install ZODB3 sous Unix sinon installer les paquets pré-construits
pour Windows.
En effet, sous Unix vous aurez besoin d’un compilateur C pour construire le paquet car la ZODB possède de
nombreux modules écrits en C pour des raisons de performance.
Comment fonctionne la ZODB
La ZODB est conceptuellement simple. Les classes Python dérivent une classe de base
persistent.Persistent pour être compatibles avec la ZODB. Les instances des objets persistants
sont construits, lorsque le programme en a besoin, à partir du média de stockage, qui peut être par exemple un
fichier sur le disque, et sont mis en cache dans la mémoire. La ZODB est prévenue de la modification des objets,
si bien que lorsque une instruction réalise une modification comme obj.size = 1, l’objet modifié est marqué
‘dirty’. En une requête tous les objets marqués ‘dirty’ sont écrits sur le stockage permanent, cette opération
s’appelle valider une transaction (“committing a transaction” ou “commiter” en langue de développeur). Une
transaction peut aussi être abandonnée ou annulée, ce qui à pour conséquence d’annuler toutes les modifications,
les objets marqués ‘dirty’ sont remis à leur état initial d’avant le début de transaction.
Le terme “transaction” a une signification spécifique en informatique. Il est extrêmement important que le contenu
d’une base de données ne soit pas corrompue par les crashs logiciels ou matériels, et la plupart des logiciels de
82
Chapitre 3. ZODB - Une Base de données objet native pour python
Plone pour les développeurs, Version 1.0.0
base de données offrent cette protection en ayant quatre caractéristiques : atomique, consistante, isolée, et durable
(ACID) :
– atomique : les opérations sont indivisibles, en cas d’échec la suite des opérations est complètement annulée
quelles que soientt le nombre d’opérations effectuées (rollback), inversement en cas de succès elles sont toutes
appliquées ; en cas de crash cela garantit que la base ne sera pas dans un état partiellement modifié.
– cohérente : en fin de transaction, la base est de nouveau dans un état cohérent - ce qui n’est pas forcément le cas
pendant la transaction. Un contenu final incohérent provoque l’annulation de la transaction.
– isolée : deux transactions simultanées n’ont pas connaissance de modifications apportées à la base par l’autre
tant qu’elle n’a pas été validée (commitée).
– durable : une transaction validée ne peut être annulée ou écrasée par une transaction ayant démarré simultanément. Lorsque la seconde voudra écraser les données de la première elle se verra annulée. Ce qui provoque
l’émission d’un message d’erreur prévenant l’auteur de la seconde transaction que sa requête n’a pu aboutir en
raison d’un conflit de transaction.
La ZODB fournit toutes les caractéristiques ACID.
Créer une ZODB
Il y a trois interfaces principales fournies par la ZODB : les classes Storage, DB, et Connection. Les interfaces DB et Connection ont toutes deux une implémentation unique, mais il y a plein de classes différentes qui
implémentent l’interface Storage.
– Les classes de type Storage sont les couches les plus basses, manipulant, stockant et restituant les objets
depuis les différents types de stockage. Quelques types de stockage différents ont été écrits, telles les classes
FileStorage, qui utilise un fichier de stockage classique, et BDBFullStorage, qui utilise le logiciel de
la base de données BerkeleyDB Sleepycat. Vous pouvez écrire votre propre classes Storage qui stocke les objets dans une base de données relationnelle, par exemple, si cela convient mieux à votre application. D’autres
exemples de stockage, DemoStorage, MappingStorage et RelStorage (storage MySQL), sont disponibles et peuvent servir de modèle si vous voulez écrire un nouveau système de stockage.
– La classe DB chapeaute le stockage, et réalise la médiation entre les différentes connexions. Une seule instance
de DB est créée par processus.
– Enfin, la classe Connection réalise la mise en cache des objets, et les déplace depuis ou vers la solution de
stockage. Un programme muti-threadé doit ouvrir une instance de Connection pour chaque flux d’exécution.
Les différents flux d’exécution peuvent alors modifier les objets et valider leur modifications indépendamment.
Projeter d’utiliser une ZODB demande trois étapes : vous avez à instancier la classe Storage, et obtenir une
connexion sous forme d’une instance de Connection à partir de l’instance de :class :’DB’. Tout cela ne représente que très peu de ligne de code : :
>>>
>>>
>>>
>>>
from ZODB import FileStorage, DB
storage = FileStorage.FileStorage(’/tmp/test-filestorage.fs’)
db = DB(storage)
conn = db.open()
Remarquez que vous pouvez utiliser un système de stockage complètement différent simplement en changeant la
ligne qui instancie la classe du type Storage, l’exemple précédent utilise FileStorage. Dans la section zeo,
“Comment fonctionne la ZEO”, vous verrez comment ZEO utilise cette possibilité.
Utiliser un fichier de configuration pour la ZODB
La ZODB supporte également les fichiers de configuration écrits dans le format ZConfig. Un fichier de configuration peut être utilisé pour séparer la configuration de l’applicatif. Les classes de stockage et la classe DB supportent
divers arguments. Toutes ces options peuvent être spécifiées par le fichier de configuration.
Le format du fichier est simple, L’exemple du chapitre précédent peut être réalisé comme suit : :
<zodb>
<filestorage>
path /tmp/test-filestorage.fs
</filestorage>
</zodb>
3.4. Documentation
83
Plone pour les développeurs, Version 1.0.0
Le module ZODB.config inclut plusieurs fonctions pour ouvrir une base de données et un stockage depuis un
fichier de configuration. :python :
>>> import ZODB.config
>>> db = ZODB.config.databaseFromURL(’/tmp/test.conf’)
>>> conn = db.open()
La documentation sur ZConfig est inclue dans la livraison de ZODB3, elle explique le format en détail. Chaque
fichier de configuration est décrit par un schéma, qui par convention est stocké dans un fichier component.xml.
ZODB, ZEO, zLOG, et zdaemon ont tous un schéma.
Écriture d’une classe persistante
Faire une classe persistante est assez simple ; il suffit de dériver de la classe Persistent, comme montré dans
l’exemple suivant :python :
>>> from persistent import Persistent
>>> class User(Persistent):
...
pass
La classe Persistent est une classe de type ‘new-style’ c’est à dire qu’elle dérive de object. Elle est implémentée en C.
Pour des raisons de simplicité, dans l’exemple la classe User sera simplement utilisée comme un support à un
ensemble d’attributs. Habituellement la classe devrait définir plusieurs méthodes qui ajoutent des fonctionnalités,
mais cela n’a aucun impact sur le traitement qu’en fait la ZODB.
La ZODB utilise la persistance par accessibilité : à partir d’un ensemble d’objets racines tous les attributs de ces
objets sont rendus persistants, qu’il s’agisse de type de données Python ou d’instances de classe. Il n’y a pas de
méthode explicite pour stocker les objets dans la base ZODB : ajoutez les simplement comme attribut à un objet
ou dans un dictionnaire qui soit déjà dans la base. Cette chaîne de contenance doit finir par rejoindre l’objet racine
de la base de données.
Comme exemple, nous allons créer une base de données d’utilisateurs simple qui permette de récupérer des instances de la classe User pour un ID d’utilisateur donné. Premièrement, nous récupérons l’objet à la racine primaire de la ZODB en utilisant la méthode root de l’instance Connection. L’objet racine se comporte comme
un dictionnaire, en conséquence vous pouvez ajouter une nouvelle entrée clé/valeur pour la racine de votre application. Nous allons insérer un objet OOBTree qui va contenir toute les objets User. (Le module BTree est
également inclus comme faisant partie des éléments de Zope.) :python :
>>>
>>>
...
>>>
...
>>>
...
...
...
>>>
dbroot = conn.root()
# Ensure that a ’userdb’ key is present
# in the root
if not dbroot.has_key(’userdb’):
from BTrees.OOBTree import OOBTree
dbroot[’userdb’] = OOBTree()
userdb = dbroot[’userdb’]
Insérer un nouvel utilisateur est simple : créez un objet de la classe User, remplissez le avec les données, insérer
le dans l’instance du BTree, et validez (commitez) la transaction. :python :
>>>
>>>
>>>
>>>
...
>>>
>>>
>>>
84
newuser.id = ’amk’
newuser.first_name = ’Andrew’ ; newuser.last_name = ’Kuchling’
# Add object to the BTree, keyed on the ID
userdb[newuser.id] = newuser
# Commit the change
Chapitre 3. ZODB - Une Base de données objet native pour python
Plone pour les développeurs, Version 1.0.0
...
>>> transaction.commit()
Le module transaction définit quelque fonction de haut niveau pour travailler avec les transactions. La fonction commit écrit tous les objets modifiés sur le disque, ce qui rend les modifications permanentes. La fonction
abort annule toutes les modifications qui ont été réalisées depuis le dernier appel à commit, restaurant l’état
initial des objets. Si vous êtes familier avec la sémantique des bases de données relationnelles, vous n’êtes pas
dépaysé. La fonction get retourne une instance de la classe Transaction qui ont des méthodes additionnelles
comme la fonction note qui ajoute une note au métadata de la transaction.
Plus précisément, le module transaction expose une instance de la classe de gestion des transactions ThreadTransactionManager comme transaction.manager, et les fonctions du module transaction comme get et begin qui redirige vers des méthodes du même nom du
transaction.manager. La fonction commit et abort appliquent les méthodes de même nom de l’instance de la classe Transaction retourné par transaction.manager.get(). Tout ceci pour des raisons
de commodité. Il est également possible de créer votre propre gestionnaire de transaction, et de dire à DB.open()
de l’utiliser à la place.
Par ce que l’intégration avec Python est complète, c’est presque comme avoir une sémantique transactionnelle pour
les variables de vos programme, vous pouvez expérimenter les transactions dans un interpréteur Python : :python :
>>> newuser
<User instance at 81b1f40>
>>> newuser.first_name
’Andrew’
>>> newuser.first_name = ’Bob’
>>> newuser.first_name
’Bob’
>>> transaction.abort()
>>> newuser.first_name
’Andrew’
# Print initial value
# Change first name
# Verify the change
# Abort transaction
# The value has changed back
Règles d’écriture de classes persistantes
Pratiquement tous les langages persistants imposent des restrictions sur le style des programmes, avertissant des
constructions qu’ils ne peuvent gérer ou y ajoutent de subtiles modifications sémantiques, et la ZODB ne fait pas
exception. Heureusement, les restrictions de la ZODB sont assez simples à comprendre, et dans la pratique il n’est
pas douloureux de les contourner.
Le résumé des règles est le suivant :
– Si vous modifiez un objet mutable qui est la valeur d’un attribut d’un autre objet la ZODB ne peut le savoir,
et ne marquera pas l’objet comme ‘dirty’. La solution consiste soit à positionner le drapeau ‘dirty’ vous même
quand vous modifiez l’objet, soit à utiliser un ‘wrapper’ (un objet enveloppe qui fournit les services manquants)
pour les listes et les dictionnaires Python (PersistentList, PersistentMapping) qui positionne le
drapeau ‘dirty’ proprement.
– Les versions récentes de la ZODB autorisent l’écriture de classe qui ont des méthodes __setattr__,
__getattr__, ou __delattr__. Ce que ne permettaient pas du tout les anciennes versions. Si vous écrivez
des méthodes __setattr__ ou __delattr__, leur code doit positionner le drapeau ‘dirty’ manuellement.
– Une classe persistante ne doit pas avoir de méthode __del__. La base de données doit pouvoir déplacer librement les objets entre le système de stockage et la mémoire. Si un objet n’est pas utilisé depuis un moment, il
peut être relâché et son contenu chargé depuis le système de stockage à la prochaine utilisation. Parce que l’interpréteur Python n’est pas conscient des mécanismes de persistance, il pourrait appeler la méthode __del__
chaque fois que l’objet a été libéré.
Nous allons regarder chaque règle en détail.
Modification des objets modifiables
La ZODB utilise différents hameçons Python pour attraper les accès aux attributs, et peut détourner la majorité
des façons de modifier un objet, mais pas tous. Si vous modifiez un objet de la classe User par affectation d’un de
3.4. Documentation
85
Plone pour les développeurs, Version 1.0.0
ses attributs, comme dans userobj.first_name = ’Andrew’, la ZODB va marquer l’objet comme ayant
changé, et il sera écrit dans le système de stockage lors du prochain commit (validation).
Le cas le plus typique qui n’est pas pris en charge par la ZODB est la liste ou le dictionnaire. Si les objets de type User ont un attribut nommé friends contenant une liste, appelant
userobj.friends.append(otherUser) qui ne marque pas userobj comme étant modifié. Du point
de vue de la ZODB, userobj.friends n’a été que lu, et sa valeur, ce qui arrive à une liste Python ordinaire,
a été retournée. La ZODB n’est pas consciente que l’objet retourné a été modifié après.
C’est l’une des quelque bizarreries dont vous devez vous rappeler quand vous utilisez la ZODB : si vous modifiez
un objet modifiable attribut d’un objet en place, vous devez marquer manuellement l’objet qui a été modifié pour
que son drapeau ‘dirty’ soit à vrai. Ceci est fait en positionnant l’attribut _p_changed de l’objet à vrai :python :
>>> userobj.friends.append(otherUser)
>>> userobj._p_changed = True
Vous pouvez cacher le détails d’implémentation du marquage de l’objet comme ‘dirty’ en concevant l’API de
vos classes pour qu’elles n’utilisent pas directement l’accès aux attributs. En lieu et place, vous pouvez utiliser
l’approche Java des accesseurs pour tout, et positionner le drapeau de modification à l’intérieur des méthodes. Par
exemple, vous pouvez interdire l’accès direct à l’attribut friends, et ajouter une méthode get_friend_list
et une méthode add_friend de modification. La méthode add_friend devrait ressembler à :python :
>>> def add_friend(self, friend):
>>>
self.friends.append(otherUser)
>>>
self._p_changed = True
Vous pouvez aussi utiliser le mécanisme des ‘properties’ pour cacher les accesseurs à l’usage (@property).
Vous pouvez également utiliser une liste ou un dictionnaire compatible avec la ZODB qui gère pour vous le
drapeau de modification. La ZODB est fournie avec la classe PersistentMapping et PersistentList
Vous pouvez rendre silencieuses les modifications d’un objet en changeant la valeur du drapeau de modification
(_p_changed ) à False.
__getattr__, __delattr__, and __setattr__
La ZODB autorise la persistance des classes qui ont des méthodes crochets comme __getattr__ et
__setattr__. Il y a quatre méthodes spéciales qui contrôlent l’accès aux attributs : les règles de chacune
diffèrent.
La méthode __getattr__ fonctionne presque de la même façon pour les classes persistantes que pour les autres
classes. Pas besoin de manipuler quoi que ce soit. Si un objet est rendu silencieux, il devra être manipulé avant
l’appel à __getattr__.
Les autres méthodes sont plus délicates. Elles vont surcharger les crochets fournis par la Persistent, si bien
que l’utilisateur doit appeler des méthodes spéciales pour invoquer ces crochets.
La méthode __getattribute__ sera appelée pour les accès aux attributs : Elle surcharge l’accès au code
fourni lors de la dérivation de la classe Persistent. Une méthode __getattribute__ surchargée par
l’utilisateur doit toujours faire en sorte que la classe de base Persistent ait une chance de manipuler les
attributs spéciaux comme __dict__ ou __class__. La surcharge doit appeler la méthode _p_getattr, et
doit lui passer comme seul argument le nom de l’attribut. Si elle retourne True, le code de la fonction surchargée
par l’utilisateur doit appeler la méthode __getattribute__ de la classe Persistent pour obtenir la valeur.
Sinon le code peut continuer sont exécution.
Un crochet de la méthode __setattr__ va également surcharger la méthode __setattr__ de la classe
Persistent et l’utilisateur doit la traiter un peu comme la précédente. Le code réalisé par l’utilisateur doit
appeler la méthode _p_setattr de la classe Persistent en lui passant le nom et la valeur de l’attribut. Si la
méthode retourne True, la classe Persistent gère l’attribut, sinon le code peut continuer sont exécution. Si le
code de l’utilisateur modifie l’état de l’objet, le code doit positionner l’attribut _p_changed.
86
Chapitre 3. ZODB - Une Base de données objet native pour python
Plone pour les développeurs, Version 1.0.0
Le crochet de la méthode meth :__delattr__ doit être implémenté de la même façon. Le code de l’utilisateur doit
appeler _p_delattr, en passant le nom de l’attribut comme argument. Si l’appel renvoit True alors la classe
Persistent gère l’attribut sinon c’est au code de l’utilisateur de le faire.
Méthode __del__
La méthode __del__ est invoquée juste avant que la mémoire occupée par un objet Python non référencé soit
libérée. Parce que la ZODB peut matérialiser ou dématérialiser un objet persistant en mémoire un nombre quelconque de fois, il y a une relation très forte entre la persistance d’un objet et la méthode __del__ qui est
normalement invoquée durant le cycle de vie de l’objet. Par exemple, la méthode __del__ d’un objet persistant
n’est pas invoqué uniquement dans le cas d’un objet qui n’est plus référencé par d’autres objets de la base de
donnée car la méthode __del__ est aussi mise en jeu dans le cas de l’accessibilité des objets en mémoire.
Pire, une méthode __del__ peut interférer avec l’objectif de la machinerie de persistance. Par exemple, de
nombreux objets restent dans le cache d’une class :Connection. À plusieurs reprises, pour réduire la charge du
cache, les objets qui n’ont pas été référencés récemment sont enlevés du cache. Si un objet persistant est enlevé du
cache et que le cache contenait la dernière référence en mémoire de cet objet la méthode __del__ de l’objet sera
appelée. Si la méthode __del__ référence n’importe quel attribut de l’objet, la ZODB devra recharger l’objet
à partir de la base de données à nouveau, avant de pouvoir satisfaire la référence. Ce qui a pour conséquence de
remettre l’objet dans le cache. Un tel objet est virtuellement immortel, occupant de l’espace en mémoire pour
toujours, puisque chaque essai pour l’enlever du cache abouti à l’y remettre. Avec les ZODB antérieures à la
version 3.2.2 cela causait le bouclage infini du code de réduction de la taille du cache. Cette boucle infinie ne se
produit plus mais les objets continuent à vivre en cache pour toujours.
Parce que la méthode __del__ n’a pas beaucoup de sens dans le cas d’objets persistants et peut créer des
problèmes, les méthodes persistantes ne devraient pas surcharger la méthode __del__.
Écrire des classes persistantes
Maintenant que nous connaissons les bases de la programmation de la ZODB, nous allons regarder quelques tâches
plus délicates qui sont nécessaires à tous les utilisateurs de la ZODB dans un système en production.
Modifier les attributs d’une instance
Idéalement, avant de rendre des classes persistantes, vous voulez définir leur interface correctement du premier
coup, en conséquence de quoi il n’y aurait pas besoin d’ajouter de nouveaux attributs au cours du temps. C’est un
objectif difficile et pratiquement impossible à atteindre à moins de connaître exactement vos besoins futurs. De
telles demandes peuvent être réclamées par d’autres personnes, si bien que vous devez vous préparer à recevoir
des demandes impliquant des changement structurels. En terminologie de base de données orientées objets, cela
s’appelle une mise à jour du schéma. La ZODB n’a pas besoin de spécification du schéma, si vous changez ce que
le logiciel attend comme données de la base pour un objet, vous changez implicitement le schéma.
Une façon de gérer de tels changements est de réaliser des programmes qui vont chercher tous les objets de la base
pour les mettre à jour selon le nouveau schéma. C’est facile si votre réseau d’objet est bien structuré, par exemple
si toutes les instances de la classe User se trouvent dans un unique dictionnaire ou BTree, il suffit alors de boucler
sur chaque instance User. C’est plus difficile si le graphe est moins structuré. Si vos objets User ne peuvent être
trouvés comme attributs d’un faible nombre d’objets, il vous faudra écrire un traverseur qui parcourera la base et
qui vérifiera que chaque objet de la ZODB est du type User ou non.
Certaines OODBs supportent une fonctionnalité appelée “extends”, qui peut rapidement trouver les objets d’un
type données, quelque soit le graphe des objets, malheureusement ce n’est pas le cas de la ZODB.
3.5 Téléchargements
La ZODB est distribuée sous forme d’un egg (paquet) Python accessible sur le dépôt de la communauté Python
Python Package Index.
3.5. Téléchargements
87
Plone pour les développeurs, Version 1.0.0
Il est à la base de Zope et donc de Plone.
Vous pouvez l’installer seul à l’aide de la commande easy_install ‘setuptools’ :
$ easy_install ZODB3
L’installation des anciennes versions sont possibles via le gestionnaire de version Subversion.
3.6 Communauté et contribution
Les discussions ont lieux sur la mailing liste ZODB developers’ mailing list.
Bug reporting, Feature requests, et le planning des version sur Launchpad.
Si vous voulez contribuer, la communauté sera heureuse de recevoir de l’aide sur la documentation, l’aide aux
autres développeurs et utilisateurs sur la mailing liste, soumission de rapport de bogues, propositions d’améliorations et écriture de code.
La ZODB est un projet géré par la fondation Zope où vous pouvez obtenir les droits pour contribuer directement
sur le code - pour cela récupérez la documentation de la communauté Zope Developer Information.
88
Chapitre 3. ZODB - Une Base de données objet native pour python
CHAPITRE 4
Le guide complet de l’Architecture de
Composants de Zope
Author Baiju M
Version 0.5.8
Printed Book http ://www.lulu.com/content/1561045
Online PDF http ://www.muthukadan.net/docs/zca.pdf
Traducteur Christophe Combelles <[email protected]>, Stéphane Klein <[email protected]>
Révision http ://bazaar.launchpad.net/~ccomb/zcadoc/book-fr/revision/109
Copyright (C) 2007,2008,2009 Baiju M <baiju.m.mail AT gmail.com>.
Chacun est autorisé à copier, distribuer et/ou modifier ce document suivant les termes de la « GNU Free Documentation License », version 1.3 ou toute version ultérieure publiée par la Free Software Foundation.
Le code source présent dans ce document est soumis aux conditions de la « Zope Public License », Version 2.1
(ZPL).
THE SOURCE CODE IN THIS DOCUMENT AND THE DOCUMENT ITSELF IS PROVIDED “AS IS” AND
ANY AND ALL EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT,
AND FITNESS FOR A PARTICULAR PURPOSE.
Remerciements
De nombreuses personnes m’ont aidé à écrire ce livre. Le brouillon initial a été relu par mon collègue Brad
Allen. Quand j’ai annoncé ce livre sur mon blog, j’ai reçu de nombreux encouragements pour poursuivre
ce travail. Kent Tenney a modifié de nombreuses parties du livre et a également réécrit l’application servant d’exemple. De nombreuses autres personnes m’ont envoyé des corrections et des commentaires, dont
Lorenzo Gil Sanchez, Michael Haubenwallner, Nando Quintana, Stephane Klein, Tim Cook, Kamal Gill et
Thomas Herve. Lorenzo a traduit ce travail en espagnol et Stephane initié la traduction en français. Merci à
toutes ces personnes !
4.1 Mise en route
4.1.1 Introduction
Le développement de grands systèmes logiciels est toujours une tâche ardue L’expérience montre que l’approche
orientée objet est bien appropriée à l’analyse, à la modélisation et à la programmation des systèmes complexes.
89
Plone pour les développeurs, Version 1.0.0
La conception orientée composants et la programmation basée sur des composants deviennent actuellement très
populaires. L’approche basée composants est d’une grande aide pour l’écriture et la maintenance de systèmes
logiciels et de leurs tests unitaires. Il existe de nombreux frameworks permettant la conception basée sur des composants dans différents langages, certains étant même indépendants du langage. On peut citer comme exemple :
COM de Microsoft, ou XPCOM de Mozilla.
La Zope Component Architecture (ZCA) est un framework en Python qui autorise la conception et la programmation basée sur des composants. Elle est très bien adaptée au développement de grands systèmes logiciels.
La ZCA n’est pas liée au serveur d’application Zope : elle peut être utilisée pour développer n’importe quelle
application en Python. Peut-être devrait-elle s’appeler la « Python Component Architecture ».
Le but de la ZCA est d’utiliser des objets Python de manière efficace. Les composants sont des objets réutilisables
fournissant une interface que l’on peut introspecter. Une interface est un objet qui décrit comment on peut travailler
avec un composant particulier. Autrement dit, un composant fournit une interface implémentée dans une classe
ou tout autre objet appelable (callable). La façon dont le composant est implémenté n’a pas d’importance : ce
qui compte est que celui-ci soit en accord avec l’interface. Grâce à la ZCA, vous pouvez éclater la complexité
d’un système dans plusieurs composants travaillant ensemble. Elle vous aide à créer notamment deux types de
composants : les « adaptateurs » et les « utilitaires ».
Trois paquets composent la ZCA :
– zope.interface est utilisé pour la définition d’une interface.
– zope.event fournit un système simple d’événements.
– zope.component sert à la création, l’inscription et la récupération des composants.
Souvenez-vous que la ZCA ne fournit pas de composants par elle-même. Elle n’a pour but que de créer, inscrire et
récupérer des composants. Gardez également à l’esprit qu’un adaptateur est une classe Python tout à fait normale
(en général une fabrique) et qu’un utilitaire est un simple objet Python appelable.
Le framework ZCA est développé comme une partie du projet Zope 3. Comme mentionné plus haut, c’est un
framework purement Python et il peut être utilisé dans n’importe quelle application Python. Actuellement, Zope
3, Zope 2 et Grok l’utilisent abondamment. De nombreux autres projets l’utilisent, y compris des applications non
liées au web 1 .
4.1.2 Petit historique
Le développement de la ZCA a débuté en 2001 dans le cadre du projet Zope 3. Ce framework est le fruit de
l’expérience acquise pendant le développement d’applications d’envergure avec Zope 2. Jim Fulton est le père de
ce projet. De nombreuses personnes ont participé à sa conception et à son implémentation, notamment Stephan
Richter, Philipp von Weitershausen, Guido van Rossum (aka. Python BDFL), Tres Seaver, Phillip J Eby et Martijn
Faassen.
À ses débuts, la ZCA définissait des types de composants supplémentaires : les services et les vues, mais les développeurs ont fini par réaliser qu’un utilitaire peut remplacer un service et qu’un multi-adaptateur peut remplacer
une vue. Aujourd’hui, la ZCA comporte un nombre très réduit de types de composants de base : les utilitaires (en
anglais utilities), les adaptateurs (adapters), les abonnés (subscribers) et les gestionnaires (handlers). Et encore,
les abonnés et les gestionnaires sont des cas particuliers de l’adaptateur.
Pendant le cycle de développement de Zope 3.2, Jim Fulton a proposé une importante simplification de la ZCA 2 .
Grâce à cette simplification, une nouvelle interface unique (IComponentRegistry) a été créée pour l’inscription des
composants globaux et locaux.
Le paquet zope.component avait une longue liste de dépendances, dont une grande partie n’était pas nécessaire
pour une application non liée à Zope 3. Pendant PyCon 2007, Jim Fulton a ajouté la fonctionnalité extras_require
à setuptools pour permettre d’extraire le cœur de la ZCA des fonctionnalités annexes 3 .
En mars 2009, Tres Seaver a supprimé les dépendances à zope.deferredimport et zope.proxy.
Aujourd’hui, le projet ZCA est indépendant et possède son propre cycle de publications et son propre dépôt
Subversion. Il est fourni avec le framework Zope. 4 . Cependant, les anomalies et les bogues sont toujours pris en
1.
2.
3.
4.
90
http ://wiki.zope.org/zope3/ComponentArchitecture
http ://wiki.zope.org/zope3/LocalComponentManagementSimplification
http ://peak.telecommunity.com/DevCenter/setuptools#declaring-dependencies
http ://docs.zope.org/zopeframework/
Chapitre 4. Le guide complet de l’Architecture de Composants de Zope
Plone pour les développeurs, Version 1.0.0
charge au travers du projet Zope 3 5 et la liste de diffusion zope-dev est utilisée pour débattre du développement 6 .
Il existe également une liste pour les utilisateurs de Zope 3 (zope3-users) qui peut être utilisée pour toute demande
au sujet de la ZCA 7 .
4.1.3 Installation
Les paquets zope.component, zope.interface et zope.event constituent le cœur de la Zope Component Architecture. Ils fournissent des outils permettant de définir, inscrire et rechercher des composants. Le
paquet zope.component et ses dépendances sont disponibles sous forme d’egg dans l’index des paquets Python (PyPI) 8 .
Vous pouvez installer zope.component et ses dépendances avec easy_install 9
$ easy_install zope.component
Cette commande télécharge zope.component et ses dépendances depuis PyPI et l’installe dans votre Python
path.
Une autre manière de procéder est de télécharger zope.component et ses dépendances depuis PyPI et de
les installer. Il faut installer les paquets dans l’ordre donné ci-dessous. Sous Windows, vous aurez probablement
besoin du paquet binaire de zope.interface.
1. zope.interface
2. zope.event
3. zope.component
Pour installer ces paquet, après téléchargement, vous pouvez utiliser la commande easy_install avec les eggs
comme argument (éventuellement en fournissant tous les eggs sur la même ligne de commande)
$ easy_install /path/to/zope.interface-3.x.x.tar.gz
$ easy_install /path/to/zope.event-3.x.x.tar.gz
$ easy_install /path/to/zope.component-3.x.x.tar.gz
Vous pouvez aussi installer ces paquets en les décompressant séparément. Par exemple
$
$
$
$
tar zxvf /path/to/zope.interface-3.x.x.tar.gz
cd zope.interface-3.x.x
python setup.py build
python setup.py install
Ces méthodes installeront la ZCA dans votre Python système, dans le dossier site-packages, ce qui peut
être problématique. Dans un message sur la liste de diffusion Zope 3, Jim Fulton déconseille d’utiliser le Python
système 10 . Vous pouvez utiliser virtualenv ou zc.buildout pour jouer avec n’importe quel paquet Python,
et également pour les déploiements.
Attention : Cette installation n’est nécessaire que pour effectuer les exercices proposés ci-après. Zope - donc
Plone - intègre en standard les composants requis.
4.1.4 Méthode pour expérimenter
Il existe deux approches populaires en Python pour construire un environnement de travail isolé. La première est
virtualenv créé par Ian Biking, la deuxième est zc.buildout créé par Jim Fulton. Il est même possible
d’utiliser ces paquets ensemble. Ils vous aideront à installer zope.component et d’autres dépendances dans
5.
6.
7.
8.
9.
10.
https ://bugs.launchpad.net/zope3
http ://mail.zope.org/mailman/listinfo/zope-dev
http ://mail.zope.org/mailman/listinfo/zope3-users
Dépôt des paquets Python : http ://pypi.python.org/pypi
http ://peak.telecommunity.com/DevCenter/EasyInstall
http ://article.gmane.org/gmane.comp.web.zope.zope3/21045
4.1. Mise en route
91
Plone pour les développeurs, Version 1.0.0
un environnement de développement isolé. Passer un peu de temps à se familiariser avec ces outils vous sera
bénéfique lors des développements et des déploiements.
virtualenv
Vous pouvez installer virtualenv grâce à easy_install
$ easy_install virtualenv
Puis vous pouvez créer un nouvel environnement de cette façon
$ virtualenv --no-site-packages myve
Ceci crée un nouvel environnement virtuel dans le dossier myve. Maintenant, depuis ce dossier, vous pouvez
installer zope.component et ses dépendances avec le script easy_install situé dans myve/bin
$ cd myve
$ ./bin/easy_install zope.component
Ensuite vous pouvez importer zope.interface et zope.component depuis le nouvel interprète python
situé dans myve/bin
$ ./bin/python
Cette commande démarre un interprète Python que vous pourrez utiliser pour lancer le code de ce livre.
zc.buildout
En combinant zc.buildout et la recette zc.recipe.egg, vous pouvez créer un interprète Python ayant
accès aux eggs Python spécifiés. Tout d’abord, installez zc.buildout grâce à la commande easy_install
(vous pouvez le faire à l’intérieur de l’environnement virtuel). Pour créer un nouveau buildout servant à expérimenter des eggs Python, commencez par créer un dossier, puis initialisez-le grâce à la commande buildout
init
$ mkdir mybuildout
$ cd mybuildout
$ buildout init
De cette façon, le dossier mybuildout devient un « buildout ». Le fichier de configuration de buildout est par
défaut buildout.cfg. Après initialisation, il doit contenir les lignes suivantes
[buildout]
parts =
Vous pouvez le modifier pour qu’il corresponde à ceci
[buildout]
parts = py
[py]
recipe = zc.recipe.egg
interpreter = python
eggs = zope.component
Si maintenant vous lancez la commande buildout disponible à l’intérieur du dossier mybuildout/bin, vous
obtiendrez un nouvel interprète Python dans le dossier mybuildout/bin
$ ./bin/buildout
$ ./bin/python
Cette commande démarre un interprète Python que vous pourrez utiliser pour lancer le code de ce livre.
92
Chapitre 4. Le guide complet de l’Architecture de Composants de Zope
Plone pour les développeurs, Version 1.0.0
4.2 Un exemple
4.2.1 Introduction
Imaginez une application servant à enregistrer les clients d’un hôtel. En Python, il est possible de faire ceci de
différentes manières. Nous allons commencer par étudier rapidement la méthode procédurale, puis nous diriger
vers une approche basique orientée objet. En examinant l’approche orientée objet, nous verrons comment nous
pouvons bénéficier des motifs de conception adaptateur et interface. Cela nous fera entrer dans le monde de la
Zope Component Architecture.
4.2.2 Approche procédurale
Dans toute application professionnelle, le stockage des données est un point critique. Pour une question de simplicité, cet exemple utilise un simple dictionnaire Python comme méthode de stockage. Nous allons générer des
identifiants uniques pour le dictionnaire, la valeur associée sera elle-même un dictionnaire contenant les détails de
l’enregistrement.
>>> bookings_db = {} #key: unique ID, value: details in a dictionary
Une implémentation minimaliste nécessite une fonction à laquelle on transmet les détails de l’enregistrement et
une fonction annexe qui fournit les identifiants uniques utilisés comme clés du dictionnaire de stockage.
Nous pouvons obtenir un identifiant unique de cette façon
>>> def get_next_id():
...
db_keys = bookings_db.keys()
...
if db_keys == []:
...
next_id = 1
...
else:
...
next_id = max(db_keys) + 1
...
return next_id
Comme vous pouvez voir, l’implémentation de la fonction get_next_id est très simple. La fonction récupère une
liste de clés et vérifie si la liste est vide. Si c’est le cas, nous savons que nous en sommes au premier enregistrement
et nous renvoyons 1. Sinon, nous ajoutons 1 à la valeur maximale de la liste et nous renvoyons la nouvelle valeur
en résultant.
Ensuite, pour créer des enregistrements dans le dictionnaire bookings_db, nous utilisons la fonction suivante
>>> def book_room(name, place):
...
next_id = get_next_id()
...
bookings_db[next_id] = {
...
’name’: name,
...
’room’: place
...
}
Une application de gestion des enregistrements d’hôtel a besoin de données supplémentaires :
– les numéros de téléphone
– les options des chambres
– les méthodes de paiement
– ...
Et de code pour gérer les données :
– annuler une réservation
– Modifier une réservation
– payer une chambre
– stocker les données
– assurer la sécurité des données
– ...
4.2. Un exemple
93
Plone pour les développeurs, Version 1.0.0
Si nous devions continuer l’exemple procédural, il faudrait créer plusieurs fonctions, en renvoyant les données
de l’une à l’autre. Au fur à mesure que les fonctionnalités seraient ajoutées, le code deviendrait de plus en plus
difficile à maintenir et les bogues deviendraient difficiles à trouver et à corriger.
Nous n’irons pas plus loin dans l’approche procédurale. Il sera beaucoup plus facile d’assurer la persistance des
données, la flexibilité de la conception et l’écriture de tests unitaires en utilisant des objets.
4.2.3 Approche orientée objet
Notre discussion sur la conception orientée objet nous amène à la notion de « classe » qui sert à encapsuler les
données et le code qui les gère.
Notre classe principale sera « FrontDesk ». Le FrontDesk et d’autres classes auxquelles il délèguera du traitement,
sauront comment gérer les données de l’hôtel. Nous allons créer des instances de FrontDesk pour appliquer cette
connaissance au métier de la gestion d’hôtel.
L’expérience a montré qu’en consolidant le code et les contraintes sur les données via des objets, on aboutit à un
code qui est plus facile à comprendre, à tester et à modifier.
Observons les détails d’implémentation de la classe FrontDesk
>>> class FrontDesk(object):
...
...
def book_room(self, name, place):
...
next_id = get_next_id()
...
bookings_db[next_id] = {
...
’name’: name,
...
’place’: place
...
}
Dans cette implémentation, l’objet frontdesk (une instance de la classe FrontDesk) est capable de prendre en
charge les réservations. Nous pouvons l’utiliser de la façon suivante
>>> frontdesk = FrontDesk()
>>> frontdesk.book_room("Jack", "Bangalore")
Dans tout projet, on est confronté à des changements de besoins. Dans notre cas, la direction a décidé que chaque
client devait fournit un numéro de téléphone, donc nous devons changer le code.
Nous pouvons satisfaire ce nouveau besoin en ajoutant un argument à la méthode book_room qui sera ajouté au
dictionnaire des valeurs
>>> class FrontDesk(object):
...
...
def book_room(self, name, place, phone):
...
next_id = get_next_id()
...
bookings_db[next_id] = {
...
’name’: name,
...
’place’: place,
...
’phone’: phone
...
}
En plus de devoir migrer les données vers le nouveau schéma, nous devons changer tous les appels à la classe
FrontDesk. Si nous incorporons les coordonnées de chaque client dans un objet et que nous utilisons cet objet
pour les réservations, les modifications de code pourront être minimisées. Nous pouvons maintenant changer les
détails de l’objet client sans avoir à changer les appels à FrontDesk.
Nous avons donc
>>> class FrontDesk(object):
...
...
def book_room(self, guest):
...
next_id = get_next_id()
94
Chapitre 4. Le guide complet de l’Architecture de Composants de Zope
Plone pour les développeurs, Version 1.0.0
...
...
...
...
...
bookings_db[next_id] = {
’name’: guest.name,
’place’: guest.place,
’phone’: guest.phone
}
Nous devons toujours modifier le code pour répondre aux nouvelles demandes. C’est inévitable, cependant notre
but est de minimiser ces changements et donc d’améliorer la maintenabilité.
Note : Lors du développement il ne faut jamais hésiter à faire des changements sans craindre de casser l’application. L’avertissement nécessaire doit être immédiatement obtenu grâce à des tests automatisés. Avec des tests
bien écrits (et un bon contrôle de versions), vous pouvez faire impunément des changements aussi importants que
vous souhaitez. Une bonne source d’informations à propos de cette philosophie de programmation est l’ouvrage
Extreme Programming Explained par Kent Beck.
En introduisant l’objet guest, vous avez économisé un peu de temps. Mais plus important, l’abstraction apportée
par l’objet guest a rendu le système plus simple et mieux compréhensible. Par conséquent, le code est plus facile
à restructurer et à maintenir.
4.2.4 Le motif adaptateur
Dans une vraie application, l’objet frontdesk devra gérer des fonctionnalités comme les annulations et les modifications. Avec la conception actuelle, nous devons transmettre l’objet guest au frontdesk à chaque fois que nous
appelons les méthodes cancel_booking et update_booking.
Nous pouvons éviter ceci si nous transmettons l’objet guest à FrontDesk.__init__() et si nous le stockons en
attribut de l’instance.
>>> class FrontDeskNG(object):
...
...
def __init__(self, guest):
...
self.guest = guest
...
...
def book_room(self):
...
guest = self.guest
...
next_id = get_next_id()
...
bookings_db[next_id] = {
...
’name’: guest.name,
...
’place’: guest.place,
...
’phone’: guest.phone
...
}
...
...
def cancel_booking(self):
...
guest = self.guest
...
#code for cancellations goes here ...
...
...
def update_booking(self):
...
guest = self.guest
...
#code for updatiion goes here ...
La solution que nous avons obtenue est un motif de conception courant appelé adaptateur. Le Gang
of Four 11 résume ainsi l’esprit de l’adaptateur
« Convertir l’interface d’une classe en une autre interface
à laquelle le client s’attend. L’adaptateur permet à des
classes de fonctionner ensemble même si elles ont des
interfaces incompatibles. »
En général, un objet adaptateur contient un objet adapté
11. http ://en.wikipedia.org/wiki/Design_Patterns
4.2. Un exemple
95
Plone pour les développeurs, Version 1.0.0
>>> class Adapter(object):
...
...
def __init__(self, adaptee):
...
self.adaptee = adaptee
Ce motif sera utile lorsqu’on sera confronté à des détails d’implémentation qui dépendent de considérations telles
que :
– modification des besoins client
– modification des méthodes de stockage (ZODB, RDBM, XML)
– modification des méthodes de sortie (HTML, PDF, texte pur)
– modification de la source utilisée (ReST, Markdown, Textile)
La ZCA utilise des adaptateurs et un registre de composants pour fournir la capacité de changer les détails d’implémentation du code via de la configuration.
Comme nous pouvons le constater dans la section sur les adaptateurs de la ZCA, la capacité de configurer les
détails d’implémentation fournit des avantages intéressants :
– on peut interchanger les implémentations
– on peut ajouter de nouvelles implémentations si besoin
– on améliore les possibilités de réutilisation, aussi bien d’un code existant que du code utilisant la ZCA.
Ces possibilités mènent à du code qui est flexible, évolutif et réutilisable. Il y a un cependant un coût : maintenir
le registre de composants ajoute un niveau de complexité à l’application. Si une application n’a pas besoin de ces
avantages, la ZCA n’est pas pas nécessaire.
Nous sommes maintenant prêts à étudier la Zope Component Architecture, en commençant avec les interfaces.
4.3 Interfaces
4.3.1 Introduction
Le fichier README.txt 12 dans chemin/vers/zope/interface définit les interfaces de cette manière
Les interfaces sont des objets qui spécifient (documentent) le
comportement externe des objets qui les « fournissent ».
Une interface spécifie un comportement au travers :
- de documentation informelle dans une docstring
- de définitions d’attributs
- d’invariants qui sont des conditions posées sur les objets
fournissant l’interface.
L’ouvrage de référence Design Patterns 12 par le Gang of Four recommande que vous devez « programmer pour
une interface, pas pour une implémentation ». Définir une interface formelle est utile dans la compréhension d’un
système. De plus, les interfaces vous apportent tous les bénéfices de la ZCA.
Une interface spécifie les caractéristiques d’un objet, son comportement et ses capacités. Elle décrit le « quoi »
d’un objet. Pour apprendre le « comment », vous devez regarder l’implémentation.
Les métaphores couramment utilisées pour les interfaces sont contrat ou plan, des termes légaux et architecturaux
pour représenter un jeu de spécifications.
Dans certains langages de programmation comme Java, C# ou VB.NET, les interfaces sont un aspect explicite
du langage. Étant donné que Python ne possède pas nativement d’interfaces, la ZCA les implémente comme des
meta-classes desquelles on hérite.
Voici un exemple classique de Hello World
12. L’arborescence du code de Zope contient de nombreux fichiers README.txt qui offrent une très bonne documentation.
96
Chapitre 4. Le guide complet de l’Architecture de Composants de Zope
Plone pour les développeurs, Version 1.0.0
>>> class Host(object):
...
...
def goodmorning(self, name):
...
"""Say good morning to guests"""
...
...
return "Good morning, %s!" % name
Dans la classe ci-dessus, on a défini une méthode goodmorning. Si vous appelez cette méthode depuis un objet
créé en utilisant cette classe, vous obtiendrez un Good morning, ... !
>>> host = Host()
>>> host.goodmorning(’Jack’)
’Good morning, Jack!’
Ici, host est l’objet réel que votre code utilise. Si vous voulez examiner les détails d’implémentation, vous devez
accéder à la classe Host, soit via le code source, soit au travers d’un outil de documentation d’API 13 .
Maintenant nous allons commencer à utiliser les interfaces de la ZCA. Pour la classe ci-dessus, vous pouvez définir
l’interface comme suit
>>> from zope.interface import Interface
>>> class IHost(Interface):
...
...
def goodmorning(guest):
...
"""Say good morning to guest"""
Vous pouvez constater que l’interface hérite de zope.interface.Interface. C’est de cette façon (abusive ?) que la
ZCA définit des interfaces. Le préfixe « I » pour le nom de la classe est une convention utile.
4.3.2 Déclarer des interfaces
Vous avez déjà vu comment déclarer une interface en utilisant zope.interface dans la section précédente.
Cette section va expliquer les concepts en détail.
Prenez cette exemple d’interface
>>> from zope.interface import Interface
>>> from zope.interface import Attribute
>>> class IHost(Interface):
...
"""A host object"""
...
...
name = Attribute("""Name of host""")
...
...
def goodmorning(guest):
...
"""Say good morning to guest"""
L’interface IHost possède deux attributs, name et goodmorning. Rappelez-vous qu’en Python
les méthodes des classes sont aussi des attributs. L’attribut name est défini en utilisant la classe
zope.interface.Attribute. Quand vous ajoutez l’attribut name à l’interface IHost, vous ne définissez
pas de valeur initiale. La raison de définir l’attribut name ici est simplement d’indiquer que toute implémentation
de cette interface doit fournir un attribut nommé name. Dans ce cas, vous n’indiquez même pas de quel type doit
être l’attribut ! Vous pouvez juste fournir une chaîne de documentation comme premier argument à Attribute.
L’autre attribut, goodmorning est une méthode définie en utilisant une fonction. Notez bien que self n’est pas
nécessaire dans les interfaces, car self est un détail d’implémentation de la classe. Il est possible pour un module
d’implémenter cette interface. Si un module implémente cette interface, cela signifiera qu’un attribut name et une
fonction goodmorning seront définis. Et la fonction goodmorning devra accepter un argument.
13. http ://en.wikipedia.org/wiki/Application_programming_interface
4.3. Interfaces
97
Plone pour les développeurs, Version 1.0.0
Nous allons maintenant voir comment effectuer le lien entre les objets, les classes et les interfaces. Seuls les objets
sont vivants : ce sont des instances de classes. Et les interfaces représentent la définition des objets, donc les classes
ne sont qu’un détail d’implémentation. C’est pour cette raison que vous devez programmer pour une interface, pas
pour une implémentation.
Nous devons nous familiariser avec deux termes supplémentaires pour comprendre les autres concepts. Le premier
est fournir, le deuxième est implémenter. Les objets fournissent des interfaces, tandis que les classes implémentent
des interfaces. Autrement dit, les objets fournissent les interfaces que les classes implémentent. Dans l’exemple
ci-dessus, l’objet host fournit l’interface IHost et la classe Host implémente l’interface IHost. Un objet peut
fournir plusieurs interfaces. Les objets peuvent également fournir directement des interfaces, en plus de celles
implémentées par leur classe.
Note : Les classes sont les détails d’implémentation des objets. En Python, les classes sont des objets appelables
(callable). Pourquoi un autre objet appelable ne pourrait-il pas implémenter une interface ? En fait c’est possible.
Pour n’importe quel objet appelable, vous pouvez déclarer qu’il produit des objets qui fournissent une interface
donnée, en disant simplement que cet objet appelable implémente l’interface. Ces objets appelables sont généralement nommés des « fabriques ». Étant donné que les fonctions sont des objets appelables, une fonction peut
implémenter une interface.
4.3.3 Implémentation des interfaces
Pour
déclarer
qu’une
classe
implémente
une
interface
zope.interface.implements dans la déclaration de classe.
donnée,
utilisez
la
fonction
Considérons cet exemple. Ici, Host implémente IHost
>>> from zope.interface import implements
>>> class Host(object):
...
...
implements(IHost)
...
...
name = u’’
...
...
def goodmorning(self, guest):
...
"""Say good morning to guest"""
...
...
return "Good morning, %s!" % guest
Note : Si vous vous demandez comment implements fonctionne, faites un tour sur l’article de blog de James
Henstridge (http ://blogs.gnome.org/jamesh/2005/09/08/python-class-advisors/) . Dans la section sur les adaptateurs, vous allez voir une fonction adapts, celle-ci fonctionne de manière similaire.
Comme Host implémente IHost, les instances de Host fournissent IHost. Il existe des méthodes permettant
d’introspecter les déclarations. La déclaration peut également s’écrire en dehors de la classe. Au lieu d’écrire
interface.implements(IHost) comme dans l’exemple ci-dessus, vous pouvez écrire la chose suivante
après la déclaration de classe
>>> from zope.interface import classImplements
>>> classImplements(Host, IHost)
4.3.4 L’exemple revisité
Maintenant retournons à notre application exemple. Voici comment définir l’interface de l’objet frontdesk
98
Chapitre 4. Le guide complet de l’Architecture de Composants de Zope
Plone pour les développeurs, Version 1.0.0
>>> from zope.interface import Interface
>>> class IDesk(Interface):
...
"""A frontdesk will register object’s details"""
...
...
def register():
...
"""Register object’s details"""
...
Nous avons d’abord importé la classe Interface depuis le module zope.interface. Si vous définissez une
sous-classe de cette classe Interface, ce sera considéré comme une interface, du point de vue de l’Architecture
de Composants. Une interface peut être implémentée, comme nous l’avons vu, dans une classe ou tout autre objet
appelable.
L’interface de frontdesk ici définie est IDesk. La chaîne de documentation de l’interface donne une idée sur
la nature de l’objet. En définissant une méthode dans l’interface, vous avez passé un contrat avec le composant,
imposant la présence d’une méthode du même nom. Dans la déclaration de la méthode côté interface, le premier
argument ne doit pas être self, car une interface ne sera jamais instanciée et ses méthodes ne seront jamais appelées.
Le rôle d’une interface est simplement de documenter quels arguments et quelles méthodes doivent apparaître dans
une classe qui l’implémente, et le paramètre self n’est qu’un détail d’implémentation qui n’a pas besoin d’être
documenté.
Comme vous le savez, une interface peut aussi spécifier des attributs normaux
>>> from zope.interface import Interface
>>> from zope.interface import Attribute
>>> class IGuest(Interface):
...
...
name = Attribute("Name of guest")
...
place = Attribute("Place of guest")
Dans cette interface, l’objet guest a deux attributs contenant de la documentation. Une interface peut spécifier à la
fois des méthodes et des attributs. Une interface peut être implémentée dans une classe, un module ou tout autre
objet. Par exemple une fonction peut créer dynamiquement le composant et le renvoyer, auquel cas on dit que la
fonction implémente l’interface.
Vous savez maintenant ce qu’est une interface, comment la créer et l’utiliser. Dans le chapitre suivant nous allons
voir comment une interface peut être utilisée pour définir un composant adaptateur.
4.3.5 Interfaces marqueurs
Une interface peut être utilisée pour déclarer qu’un objet appartient à un type donné. Une interface sans aucun
attribut ni aucune méthode est appelée une « interface marqueur ».
Voici une interface marqueur
>>> from zope.interface import Interface
>>> class ISpecialGuest(Interface):
...
"""A special guest"""
Cette interface peut être utilisée pour déclarer qu’un objet est un client spécial.
4.3.6 Invariants
Parfois vous aurez besoin de définir des règles ou des contraintes sur les attributs de vos composants. Ces types
de règles sont appelés des invariants. Vous pouvez utiliser zope.interface.invariant pour définir des
invariants sur vos objets dans leur interface.
4.3. Interfaces
99
Plone pour les développeurs, Version 1.0.0
Considérons un exemple simple, avec un objet person. Une objet person a un attribut nom, un attribut email et un
attribut phone. Comment peut-on implémenter une règle de validation qui oblige à définir au choix le phone ou
l’email mais pas forcément les deux ?
Créez tout d’abord un objet appelable, soit une simple fonction soit une instance appelable d’une classe de la façon
suivante
>>> def contacts_invariant(obj):
...
...
if not (obj.email or obj.phone):
...
raise Exception(
...
"At least one contact info is required")
Ensuite définissez l’interface de l’objet person en utilisant la fonction zope.interface.invariant pour
définir l’invariant
>>> from zope.interface import Interface
>>> from zope.interface import Attribute
>>> from zope.interface import invariant
>>> class IPerson(Interface):
...
...
name = Attribute("Name")
...
email = Attribute("Email Address")
...
phone = Attribute("Phone Number")
...
...
invariant(contacts_invariant)
Maintenant, utilisez la méthode validateInvariants de l’interface pour effectuer la validation
>>> from zope.interface import implements
>>> class Person(object):
...
implements(IPerson)
...
...
name = None
...
email = None
...
phone = None
>>> jack = Person()
>>> jack.email = u"[email protected]"
>>> IPerson.validateInvariants(jack)
>>> jill = Person()
>>> IPerson.validateInvariants(jill)
Traceback (most recent call last):
...
Exception: At least one contact info is required
Vous constatez que l’ojet jack est validé sans erreur. Mais l’objet jill n’a pas pu valider la contrainte de l’invariant,
il a donc levé une exception.
4.4 Adaptateurs
4.4.1 Implémentation
Cette section décrit les adaptateurs en détail. L’Architecture de Composants, comme vous l’avez remarqué, fournit une aide dans l’utilisation efficace des objets Python. Les composants adaptateurs sont l’un des composants
basiques utilisés par l’Architecture de Composants de Zope pour utiliser efficacement des objets Python. Les
adaptateurs sont aussi des objets Python, mais avec une interface bien définie.
100
Chapitre 4. Le guide complet de l’Architecture de Composants de Zope
Plone pour les développeurs, Version 1.0.0
Pour déclarer qu’une classe est un adaptateur, utilisez la fonction adapts définie dans le paquet
zope.component. Voici un nouvel adaptateur FrontDeskNG avec une déclaration explicite d’interface
>>> from zope.interface import implements
>>> from zope.component import adapts
>>> class FrontDeskNG(object):
...
...
implements(IDesk)
...
adapts(IGuest)
...
...
def __init__(self, guest):
...
self.guest = guest
...
...
def register(self):
...
guest = self.guest
...
next_id = get_next_id()
...
bookings_db[next_id] = {
...
’name’: guest.name,
...
’place’: guest.place,
...
’phone’: guest.phone
...
}
Ce que nous avons défini ici est un adaptateur pour IDesk, qui s’adapte à l’objet IGuest. L’interface IDesk est
implémentée par la classe FrontDeskNG. Donc une instance de cette classe fournira l’interface IDesk.
>>> class Guest(object):
...
...
implements(IGuest)
...
...
def __init__(self, name, place):
...
self.name = name
...
self.place = place
>>> jack = Guest("Jack", "Bangalore")
>>> jack_frontdesk = FrontDeskNG(jack)
>>> IDesk.providedBy(jack_frontdesk)
True
FrontDeskNG est juste un adaptateur que nous avons créé. Vous pouvez créer d’autres adaptateurs qui prendront
en charge le bureau d’enregistrement différemment.
4.4.2 Inscription
Pour utiliser ce composant adaptateur, vous devez l’inscrire dans un registre de composants (appelé également «
gestionnaire de site »). Un gestionnaire de site réside normalement à l’intérieur d’un site. Le site et le gestionnaire
de site prendront leur importance lors du développement d’une application Zope 3. Pour l’instant vous avez juste
besoin de connaître les notions de site global et de gestionnaire global de site (ou registre de composant). Un
gestionnaire global de site est situé en mémoire, alors qu’un gestionnaire de site local est persistant.
Pour inscrire votre composant, commencez par récupérer le gestionnaire global de site
>>> from zope.component import getGlobalSiteManager
>>> gsm = getGlobalSiteManager()
>>> gsm.registerAdapter(FrontDeskNG,
...
(IGuest,), IDesk, ’ng’)
Pour récupérer le gestionnaire global de site, vous devez appeler la fonction getGlobalSiteManager‘
disponible dans le paquet ‘‘zope.component. En fait, le gestionnaire global de site est disponible dans un attribut (globalSiteManager) du paquet zope.component. Vous pouvez donc utiliser directement l’attribut zope.component.globalSiteManager. Pour inscrire l’adaptateur comme un compo4.4. Adaptateurs
101
Plone pour les développeurs, Version 1.0.0
sant, utilisez la méthode registerAdapter du registre de composants. Le premier argument doit être votre
classe ou fabrique d’adaptateur. Le deuxième argument est un tuple d’objets adaptés, c’est à dire les objets sur lesquels vous vous adaptez. Dans cet exemple, vous vous adaptez seulement à l’objet IGuest. Le troisième argument
est l’interface implémentée par le composant adaptateur. Le quatrième argument est optionnel, il s’agit du nom
de cet adaptateur particulier. Si vous donnez un nom à l’adaptateur, celui devient un adaptateur nommé. Si aucun
nom n’est donné, la valeur transmise par défaut est une chaîne vide (‘’).
Dans l’inscription ci-dessus, vous avez donné l’interface de l’objet adapté et l’interface fournie par l’adaptateur.
Comme vous avez déjà donné ces informations dans l’implémentation de l’adaptateur, il est inutile de les spécifier
à nouveau. En réalité, vous pouvez effectuer l’inscription de la manière suivante
>>> gsm.registerAdapter(FrontDeskNG, name=’ng’)
Pour effectuer l’inscription, il existe d’anciennes API que vous devriez éviter. Les fonctions de l’ancienne API
commencent par provide, par exemple : provideAdapter, provideUtility, etc. Si vous développez une
application Zope 3, vous pouvez utiliser le langage ZCML (Zope Configuration Markup Language) pour effectuer
les inscriptions des composants. Avec Zope 3, les composants locaux (persistants) peut être inscrits depuis la ZMI
(Zope Management Interface), ou bien par programmation.
Vous avez inscrit FrontDeskNG avec le nom ng. De la même manière, vous pouvez inscrire d’autre adaptateurs
avec différents noms. Si un composant est inscrit sans nom, son nom sera la chaîne vide par défaut.
Note : Les composants locaux sont persistants mais les composants globaux sont en mémoire. Les composants
globaux sont inscrits en fonction de la configuration de l’application. Les composants locaux sont récupérés dans
la base de données au démarrage de l’application.
4.4.3 récupération d’un adaptateur
La récupération des composants inscrits dans le registre est effectuée grâce à deux fonctions disponibles dans le
paquet zope.component. La première est getAdapter, la deuxième queryAdapter. Les deux fonctions
prennent les mêmes arguments. getAdapter lève une exception ComponentLookupError si la recherche
de composant échoue, tandis que queryAdapter renvoie None.
Vous pouvez importer ces fonctions comme ceci
>>> from zope.component import getAdapter
>>> from zope.component import queryAdapter
Dans la section précédente, nous avons inscrit un composant qui fournit l’interface IDesk avec un nom ng, et qui
s’adapte à l’objet guest. Dans la première section nous avons créé un objet nommé jack.
Voici maintenant comment récupérer un composant qui s’adapte à l’interface de l’objet jack (IGuest) et qui fournit
l’interface IDesk également avec le nom ng. Ici, getAdapter et queryAdapter fonctionnent de la même
manière
>>> getAdapter(jack, IDesk, ’ng’)
<FrontDeskNG object at ...>
>>> queryAdapter(jack, IDesk, ’ng’)
<FrontDeskNG object at ...>
Vous pouvez constater que le premier argument doit être l’objet adapté, puis l’interface qui doit être fournie par le
composant et finalement le nom du composant adaptateur.
Si vous essayez de récupérer le composant grâce à un nom inutilisé pour l’inscription mais pour le même objet
adapté et la même interface, la recherche échouera. Voici dans ce cas comment fonctionnent les deux méthodes
>>> getAdapter(jack, IDesk, ’not-exists’)
Traceback (most recent call last):
...
ComponentLookupError: ...
>>> reg = queryAdapter(jack,
102
Chapitre 4. Le guide complet de l’Architecture de Composants de Zope
Plone pour les développeurs, Version 1.0.0
...
IDesk, ’not-exists’)
>>> reg is None
True
getAdapter a levé une exception ComponentLookupError mais queryAdapter a renvoyé None.
Le troisième argument, le nom d’inscription, est optionnel. Si le troisième argument n’est pas fourni, sa valeur
par défaut est la chaîne vide (‘’). Comme il n’y a aucun composant inscrit avec la chaîne vide comme nom,
getAdapter lève l’exception ComponentLookupError. De la même manière, queryAdapter renvoie
None. Voyez vous-même comment cela fonctionne
>>> getAdapter(jack, IDesk)
Traceback (most recent call last):
...
ComponentLookupError: ...
>>> reg = queryAdapter(jack, IDesk)
>>> reg is None
True
Dans cette section, vous avez appris à inscrire un simple adaptateur et à le récupérer depuis le registre de composants. Ce type d’adaptateurs est appelé un adaptateur simple, car il ne s’adapte qu’à un seul objet. Si un adaptateur
s’adapte à plusieurs objets, on l’appelle un multi-adaptateur.
4.4.4 Récupérer un adaptateur en utilisant l’interface
Les adaptateurs peuvent être récupérés directement en utilisant les interfaces, mais cela ne fonctionne qu’avec les
adaptateurs simples. Le premier argument est l’objet adapté, le deuxième argument est un argument mot-clé. Si la
recheche d’adaptateur échoue, le deuxième argument est renvoyé.
>>> IDesk(jack, alternate=’default-output’)
’default-output’
Le mot-clé peut être omis :
>>> IDesk(jack, ’default-output’)
’default-output’
Si le deuxième argument n’est pas fourni, une erreur TypeError est levée :
>>> IDesk(jack)
Traceback (most recent call last):
...
TypeError: (’Could not adapt’,
<Guest object at ...>,
<InterfaceClass __builtin__.IDesk>)
Ici, FrontDeskNG est inscrit sans nom :
>>> gsm.registerAdapter(FrontDeskNG)
Maintenant la récupération de l’adaptateur doit réussir :
>>> IDesk(jack, ’default-output’)
<FrontDeskNG object at ...>
Pour les cas simples, vous pouvez utiliser l’interface pour récupérer des adaptateurs.
4.4. Adaptateurs
103
Plone pour les développeurs, Version 1.0.0
4.4.5 Motif adaptateur
Le principe de l’adaptateur dans l’Architecture de Composants de Zope est très similaire au motif adaptateur
classique, tel que décrit dans le livre « Design Patterns ». Mais l’objectif visé par l’utilisation des adaptateurs
dans la ZCA est plus large que le principe de l’adaptateur lui-même. Le but de l’adaptateur est de convertir
l’interface d’une classe en une autre interface attendue par le client. Ceci permet de faire fonctionner des classes
ensemble même si elles ont des interfaces incompatibles entre elles. Mais dans la section motivations du livre «
Design Patterns », le GoF dit : « souvent, l’adaptateur est responsable d’une fonctionnalité que la classe adaptée
ne fournit pas ». Un adaptateur de la ZCA se concentre effectivement plus sur l’ajout de fonctionnalités que sur
la création d’une nouvelle interface pour un objet adapté. L’adaptateur de la ZCA permet aux classes adaptateurs
d’étendre des fonctionnalités par ajout de méthodes. (Il peut être intéressant de remarquer que l’adaptateur était
appelé Fonctionnalité (feature) dans les premières étapes de conception de la ZCA.) 14
La citation du Gang of Four dans le paragraphe ci-dessus se termine de la façon suivante : « ... que la classe adaptée
ne fournit pas ». Mais dans la phrase suivante nous avons utilisé « objet adapté » plutôt que « classe adaptée », car
le GoF décrit deux variantes d’adaptateurs selon les implémentations. Le premier est appelé adaptateur de classe,
le deuxième adaptateur d’objet. Un adaptateur de classe utilise l’héritage multiple pour adapter une interface à une
autre, alors que l’adaptateur d’objet se base sur la composition d’objet. L’adaptateur de la ZCA utilise le principe
de l’adaptateur d’objet, qui utilise la délégation comme mécanisme de composition. Le second principe du GoF à
propos de la conception orientée objets est la suivante : « favorisez la composition d’objets plutôt que l’héritage
de classes ». Pour approfondir le sujet, reportez-vous au livre « Design Patterns ».
Les principaux atouts des adaptateurs de la ZCA sont les interfaces explicites et les registres de composants. Les
composants adaptateurs de la ZCA sont inscrits dans le registre de composants et sont récupérés par les objets
clients via leur interface et leur nom si besoin.
4.5 Utilitaires
4.5.1 Introduction
Vous connaissez maintenant le principe des interfaces, des adaptateurs et du registre de composants. Parfois, il
peut être utile d’inscrire un objet qui ne s’adapte à rien du tout, par exemple une connexion à une base de données,
un analyseur XML ou un objet retournant des identifiants uniques. Ce type de composant fourni par la ZCA est
appelé un « utilitaire » (utility).
Les utilitaires sont simplement des objets qui fournissent une interface et qui sont récupérés à partir de cette
interface et d’un nom. Cette approche permet de créer un registre global dans lequel des instances peuvent être
inscrites et récupérées à différents endroits de votre application, sans avoir besoin de transmettre les instances
comme paramètres.
Vous n’avez pas besoin d’inscrire toutes les instances de composants de cette façon. Inscrivez seulement les
composants que vous voulez rendre interchangeables.
4.5.2 Utilitaire simple
Un utilitaire peut être inscrit avec ou sans nom. Un utilitaire inscrit avec un nom est appelé un utilitaire nommé.
Vous le verrez dans la prochaine section. Avant d’implémenter l’utilitaire, comme d’habitude, définissez son interface. Voici une interface qui dit bonjour
>>> from zope.interface import Interface
>>> from zope.interface import implements
>>> class IGreeter(Interface):
...
...
def greet(name):
...
"""Say hello"""
14. Thread discussing renaming of Feature to Adapter : http ://mail.zope.org/pipermail/zope3-dev/2001-December/000008.html
104
Chapitre 4. Le guide complet de l’Architecture de Composants de Zope
Plone pour les développeurs, Version 1.0.0
Comme un adaptateur, un utilitaire peut avoir plusieurs implémentations. Voici une implémentation possible de
l’interface ci-dessus
>>> class Greeter(object):
...
...
implements(IGreeter)
...
...
def greet(self, name):
...
return "Hello " + name
L’utilitaire réel sera une instance de cette classe. Pour utiliser cet utilitaire, vous devez l’inscrire, puis vous pourrez
la récupérer en utilisant l’API de la ZCA. Vous pouvez inscrire une instance de cette classe (utilitaire) grâce à
registerUtility
>>> from zope.component import getGlobalSiteManager
>>> gsm = getGlobalSiteManager()
>>> greet = Greeter()
>>> gsm.registerUtility(greet, IGreeter)
Dans cet exemple, vous avez inscrit l’utilitaire en fournissant l’interface IGreeter. Vous pouvez récupérer l’utilitaire avec queryUtility ou getUtility
>>> from zope.component import queryUtility
>>> from zope.component import getUtility
>>> queryUtility(IGreeter).greet(’Jack’)
’Hello Jack’
>>> getUtility(IGreeter).greet(’Jack’)
’Hello Jack’
Comme vous pouvez le constater, alors que les adaptateurs sont habituellement des classes, les utilitaires sont
habituellement des instances de classes. vous ne créez une instance d’utilitaire qu’une fois, alors que les instances
d’adaptateurs sont créées à chaque fois que vous les récupérez.
4.5.3 Utilitaire nommé
Lorsque vous inscrivez un composant utilitaire, de la même manière que pour les adaptateurs, vous pouvez utiliser
un nom. Comme mentionné dans la section précédente, un utilitaire inscrit avec un nom est appelé un utilitaire
nommé.
Voici comment inscrire l’utilitaire greeter avec un nom
>>> greet = Greeter()
>>> gsm.registerUtility(greet, IGreeter, ’new’)
Dans cet exemple, nous avons inscrit l’utilitaire avec un nom en fournissant l’interface IGreeter. Vous pouvez
récupérer l’utilitaire avec queryUtility ou getUtility
>>> from zope.component import queryUtility
>>> from zope.component import getUtility
>>> queryUtility(IGreeter, ’new’).greet(’Jill’)
’Hello Jill’
>>> getUtility(IGreeter, ’new’).greet(’Jill’)
’Hello Jill’
Remarquez que vous devez utiliser le nom de l’utilitaire comme second argument pour que la récupération fonctionne.
4.5. Utilitaires
105
Plone pour les développeurs, Version 1.0.0
Appeler getUtility sans nom (comme second argument) est équivalent à l’appeler avec un nom vide (‘’), car la
valeur par défaut pour ce second argument est justement la chaîne vide. Donc le mécanisme de recherche de composant essaiera de trouver le composant ayant une chaîne vide comme nom et il échouera. Lorsqu’une recherche
de composant échoue, le résultat est une exception ComponentLookupError. Souvenez-vous qu’aucun composant aléatoire avec un autre nom ne sera renvoyé. Les fonctions de récupération d’adaptateurs, getAdapter et
queryUtility fonctionnent de manière similaire.
4.5.4 Fabrique
Une fabrique est un composant utilitaire qui fournit l’interface IFactory.
Pour créer une fabrique, commencez par définir l’interface de l’objet
>>> from zope.interface import Attribute
>>> from zope.interface import Interface
>>> from zope.interface import implements
>>> class IDatabase(Interface):
...
...
def getConnection():
...
"""Return connection object"""
Voici une implémentation factice de l’interface IDatabase
>>> class FakeDb(object):
...
...
implements(IDatabase)
...
...
def getConnection(self):
...
return "connection"
Vous pouvez créer une fabrique en utilisant zope.component.factory.Factory
>>> from zope.component.factory import Factory
>>> factory = Factory(FakeDb, ’FakeDb’)
Maintenant vous pouvez l’inscrire de cette façon
>>> from zope.component import getGlobalSiteManager
>>> gsm = getGlobalSiteManager()
>>> from zope.component.interfaces import IFactory
>>> gsm.registerUtility(factory, IFactory, ’fakedb’)
Pour utiliser la fabrique, vous pouvez faire comme ceci
>>> from zope.component import queryUtility
>>> queryUtility(IFactory, ’fakedb’)()
<FakeDb object at ...>
Voici l’écriture simplifiée pour utiliser une fabrique
>>> from zope.component import createObject
>>> createObject(’fakedb’)
<FakeDb object at ...>
4.6 Adaptateurs avancés
Ce chapître traite de concepts avancés comme les multi-adaptateurs, les abonnés (subscribers) et les gestionnaires
(handles).
106
Chapitre 4. Le guide complet de l’Architecture de Composants de Zope
Plone pour les développeurs, Version 1.0.0
4.6.1 Multi-adaptateur
Un adaptateur simple s’adapte normalement à un seul objet, mais un adaptateur peut s’adapter à plusieurs objets.
Si un adaptateur s’adapte à plus d’un objet, il s’appelle un multi-adaptateur.
>>> from zope.interface import Interface
>>> from zope.interface import implements
>>> from zope.component import adapts
>>> class IAdapteeOne(Interface):
...
pass
>>> class IAdapteeTwo(Interface):
...
pass
>>> class IFunctionality(Interface):
...
pass
>>> class MyFunctionality(object):
...
implements(IFunctionality)
...
adapts(IAdapteeOne, IAdapteeTwo)
...
...
def __init__(self, one, two):
...
self.one = one
...
self.two = two
>>> from zope.component import getGlobalSiteManager
>>> gsm = getGlobalSiteManager()
>>> gsm.registerAdapter(MyFunctionality)
>>> class One(object):
...
implements(IAdapteeOne)
>>> class Two(object):
...
implements(IAdapteeTwo)
>>> one = One()
>>> two = Two()
>>> from zope.component import getMultiAdapter
>>> getMultiAdapter((one,two), IFunctionality)
<MyFunctionality object at ...>
>>> myfunctionality = getMultiAdapter((one,two), IFunctionality)
>>> myfunctionality.one
<One object at ...>
>>> myfunctionality.two
<Two object at ...>
4.6.2 Adaptateur d’abonnement
À la différence des adaptateurs habituels, les adaptateurs d’abonnement sont utilisés quand on veut récupérer tous
les adaptateurs qui adaptent un objet à une interface donnée. Un adaptateur d’abonnement est également appelé
abonné.
Considérons un problème de validation. Nous avons des objets et nous voulons vérifier s’ils satisfont à un certain
critère. Nous définissons une interface de validation
4.6. Adaptateurs avancés
107
Plone pour les développeurs, Version 1.0.0
>>> from zope.interface import Interface
>>> from zope.interface import Attribute
>>> from zope.interface import implements
>>> class IValidate(Interface):
...
...
def validate(ob):
...
"""Determine whether the object is valid
...
...
Return a string describing a validation problem.
...
An empty string is returned to indicate that the
...
object is valid.
...
"""
Nous avons également des documents
>>> class IDocument(Interface):
...
...
summary = Attribute("Document summary")
...
body = Attribute("Document text")
>>> class Document(object):
...
...
implements(IDocument)
...
...
def __init__(self, summary, body):
...
self.summary, self.body = summary, body
Maintenant, nous pouvons avoir besoin de préciser différentes règles de validation pour les documents. Par
exemple, nous voulons que le résumé tienne sur une seule ligne
>>> from zope.component import adapts
>>> class SingleLineSummary:
...
...
adapts(IDocument)
...
implements(IValidate)
...
...
def __init__(self, doc):
...
self.doc = doc
...
...
def validate(self):
...
if ’\n’ in self.doc.summary:
...
return ’Summary should only have one line’
...
else:
...
return ’’
Ou bien nous voulons que le corps du document fasse au moins 1000 caractères
>>> class AdequateLength(object):
...
...
adapts(IDocument)
...
implements(IValidate)
...
...
def __init__(self, doc):
...
self.doc = doc
...
...
def validate(self):
...
if len(self.doc.body) < 1000:
...
return ’too short’
...
else:
...
return ’’
108
Chapitre 4. Le guide complet de l’Architecture de Composants de Zope
Plone pour les développeurs, Version 1.0.0
Nous pouvons inscrire ces deux composants comme des adaptateurs d’abonnement
>>> from zope.component import getGlobalSiteManager
>>> gsm = getGlobalSiteManager()
>>> gsm.registerSubscriptionAdapter(SingleLineSummary)
>>> gsm.registerSubscriptionAdapter(AdequateLength)
Nous pouvons ensuite utiliser ces abonnés pour valider les objets
>>> from zope.component import subscribers
>>> doc = Document("A\nDocument", "blah")
>>> [adapter.validate()
... for adapter in subscribers([doc], IValidate)
... if adapter.validate()]
[’Summary should only have one line’, ’too short’]
>>> doc = Document("A\nDocument", "blah" * 1000)
>>> [adapter.validate()
... for adapter in subscribers([doc], IValidate)
... if adapter.validate()]
[’Summary should only have one line’]
>>> doc = Document("A Document", "blah")
>>> [adapter.validate()
... for adapter in subscribers([doc], IValidate)
... if adapter.validate()]
[’too short’]
4.6.3 Gestionnaire
Les gestionnaires sont des fabriques d’abonnés qui ne produisent rien du tout. Ils font la totalité de leur travail
lorsqu’ils sont appelés. Les gestionnaires sont typiquement utilisés pour gérer des événements. Les gestionnaires
sont aussi connus sous le nom d’abonnés à des événements ou adaptateurs d’abonnement à
des événements.
Les abonnés à des événements sont différents des autres abonnés car l’objet qui appelle les abonnés ne s’attend pas
à interagir avec eux d’une quelconque façon. Par exemple, un publicateur d’événements ne s’attend pas à récupérer
une valeur de retour. Comme les abonnés n’ont pas besoin de fournir une API aux objets qui les appellent, il est
plus naturel de les définir avec des fonctions, plutôt qu’avec des classes. Par exemple, dans un système de gestion
de documents, nous pouvons avoir besoin de gérer les heures de création des documents
>>> import datetime
>>> def documentCreated(event):
...
event.doc.created = datetime.datetime.utcnow()
Dans cet exemple, nous avons une fonction qui prend un événement et effectue un traitement. La fonction ne
retourne en fait rien du tout. C’est un cas particulier de l’adaptateur d’abonnement qui s’adapte à un événement
et ne fournit rien. Tout le travail est effectué lorsque la fabrique de l’adaptateur est appelée. Nous appelons les
abonnés qui ne fabriquent rien des « gestionnaires ». Il existe des APIs spécialisées dans leur inscription et leur
récupération.
Pour inscrire l’abonné ci-dessus, nous définissons un événement « document créé »
>>> from zope.interface import Interface
>>> from zope.interface import Attribute
>>> from zope.interface import implements
>>> class IDocumentCreated(Interface):
...
4.6. Adaptateurs avancés
109
Plone pour les développeurs, Version 1.0.0
...
doc = Attribute("The document that was created")
>>> class DocumentCreated(object):
...
...
implements(IDocumentCreated)
...
...
def __init__(self, doc):
...
self.doc = doc
Nous modifions également notre définition du gestionnaire
>>> def documentCreated(event):
...
event.doc.created = datetime.datetime.utcnow()
>>> from zope.component import adapter
>>> @adapter(IDocumentCreated)
... def documentCreated(event):
...
event.doc.created = datetime.datetime.utcnow()
Ceci marque le gestionnaire comme étant un adaptateur des événements IDocumentCreated.
Nous inscrivons le gestionnaire
>>> from zope.component import getGlobalSiteManager
>>> gsm = getGlobalSiteManager()
>>> gsm.registerHandler(documentCreated)
Il est maintenant possible de créer un événement et d’utiliser la fonction handle pour appeler les gestionnaires
inscrits à l’événement
>>> from zope.component import handle
>>> handle(DocumentCreated(doc))
>>> doc.created.__class__.__name__
’datetime’
4.7 Usage de la ZCA dans Zope
L’Architecture de Composants de Zope est utilisée dans Zope 2 et dans Zope 3. Ce chapitre traite de l’usage de la
ZCA dans Zope.
4.7.1 ZCML
Le Zope Configuration Markup Language (ZCML) est un système de configuration pour l’inscription des
composants. Au lieu d’utiliser l’API Python pour les inscriptions, vous pouvez utiliser le ZCML. Pour pouvoir
utiliser le ZCML, vous devez installer des paquets supplémentaires.
Vous pouvez installer zope.component avec la prise en charge du ZCML en utilisant easy_install comme ceci
$ easy_install "zope.component [zcml]"
Un fichier ZCML doit commencer par une directive configure avec la déclaration d’espace de nom appropriée
<configure xmlns="http://namespaces.zope.org/zope">
...
</configure>
La directive adapter peut être utilisée pour inscrire des adaptateurs
110
Chapitre 4. Le guide complet de l’Architecture de Composants de Zope
Plone pour les développeurs, Version 1.0.0
<adapter
factory=".company.EmployeeSalary"
provides=".interfaces.ISalary"
for=".interfaces.IEmployee"
/>
Les attributs provides et for sont optionnels, à condition que vous ayez fait les déclaration correspondantes dans
l’implémentation
<adapter
factory=".company.EmployeeSalary"
/>
Si vous voulez inscrire le composant comme un adaptateur nommé, vous pouvez utiliser l’attribut name
<adapter
factory=".company.EmployeeSalary"
name="salary"
/>
Un utilitaire peut être inscrit avec la directive utility
<utility
component=".database.connection"
provides=".interfaces.IConnection"
/>
L’attribut provides est optionnel, à condition que vous ayez fait la déclaration correspondante dans l’implémentation
<configure xmlns="http://namespaces.zope.org/zope">
<utility
component=".database.connection"
/>
Si vous voulez inscrire le composant comme un utilitaire nommé, vous pouvez utiliser l’attribut name
<utility
component=".database.connection"
name="db_connection"
/>
Plutôt que d’utiliser directement le composant, vous pouvez aussi fournir une fabrique
<utility
factory=".database.Connection"
/>
4.7.2 Surchargements
Lorsque vous inscrivez des composants avec l’API Python (méthodes register*), le dernier composant inscrit
remplace le précédent, si les deux sont inscrits avec les mêmes arguments. Considérons l’exemple suivant
>>> from zope.interface import Attribute
>>> from zope.interface import Interface
>>> class IA(Interface):
...
pass
>>> class IP(Interface):
...
pass
4.7. Usage de la ZCA dans Zope
111
Plone pour les développeurs, Version 1.0.0
>>> from zope.interface import implements
>>> from zope.component import adapts
>>> from zope.component import getGlobalSiteManager
>>> gsm = getGlobalSiteManager()
>>> class AP(object):
...
...
implements(IP)
...
adapts(IA)
...
...
def __init__(self, context):
...
self.context = context
>>> class AP2(object):
...
...
implements(IP)
...
adapts(IA)
...
...
def __init__(self, context):
...
self.context = context
>>> class A(object):
...
...
implements(IA)
>>> a = A()
>>> ap = AP(a)
>>> gsm.registerAdapter(AP)
>>> getAdapter(a, IP)
<AP object at ...>
Si vous inscrivez un autre adaptateur, celui existant sera surchargé
>>> gsm.registerAdapter(AP2)
>>> getAdapter(a, IP)
<AP2 object at ...>
Mais lorsque vous inscrivez des composants en ZCML, la deuxième inscription provoquera un conflit. Ceci est fait
pour éviter les surchargements accidentels d’inscriptions, qui sont difficiles à retrouver et corriger. L’utilisation de
ZCML est donc un avantage pour votre application.
Parfois vous aurez besoin de surcharger une inscription existante. ZCML fournit une directive
includeOverrides à cet effet. Avec la directive ci-dessous, vous pouvez écrire vos surcharges dans un fichier séparé
<includeOverrides file="overrides.zcml" />
4.7.3 NameChooser
Emplacement : zope.app.container.contained.NameChooser
C’est un adaptateur qui choisit un nom unique pour un objet dans un conteneur.
L’inscription de l’adaptateur ressemble à ceci
<adapter
provides=".interfaces.INameChooser"
for="zope.app.container.interfaces.IWriteContainer"
112
Chapitre 4. Le guide complet de l’Architecture de Composants de Zope
Plone pour les développeurs, Version 1.0.0
factory=".contained.NameChooser"
/>
Dans cette inscription, vous pouvez observer qu’on s’adapte à un IWriteContainer et que l’adaptateur fournit
INameChooser.
Cet adaptateur fournit une fonctionnalité très pratique pour les développeurs Zope. Les implémentations principales de IWriteContainer dans Zope 3 sont zope.app.container.BTreeContainer‘ et
zope.app.folder.Folder. Normalement vous hériterez de ces implémentations pour créer vos propres
classes de conteneurs. Supposez qu’il n’y ait pas d’interface INameChooser ni d’adaptateur : vous seriez obligé
de recréer cette fonctionnalité séparément pour chaque implémentation.
4.7.4 LocationPhysicallyLocatable
Emplacement : zope.location.traversing.LocationPhysicallyLocatable
Cet adaptateur est fréquemment utilisé dans les applications Zope 3, mais habituellement il est appelé
au travers d’une API dans zope.traversing.api. (Certains vieux codes utilisent même les fonctions
zope.app.zapi qui ne sont qu’une indirection supplémentaire).
L’inscription de l’adaptateur ressemble à ceci
<adapter
factory="zope.location.traversing.LocationPhysicallyLocatable"
/>
L’interface fournie et l’interface adaptée sont données dans l’implémentation, dont voici le début
class LocationPhysicallyLocatable(object):
"""Provide location information for location objects
"""
zope.component.adapts(ILocation)
zope.interface.implements(IPhysicallyLocatable)
...
Normalement, presque tous les objets persistants d’une application Zope 3 fournissent l’interface ILocation.
Cette interface n’a que deux attributs, __parent__ et __name__. __parent__ est le parent dans la hiérarchie
des objets et __name__ est le nom, vu depuis le parent.
L’interface IPhysicallyLocatable a quatre méthodes : getRoot, getPath, getName, et
getNearestSite.
– getRoot renvoie l’objet racine physique.
– getPath renvoie une chaîne donnant le chemin physique vers l’objet.
– getName renvoie le dernier segment du chemin physique.
– getNearestSite renvoie le site dans lequel l’objet est contenu. Si l’objet est lui-même un site, il est renvoyé.
Si vous apprenez Zope 3, ce sont des choses importantes dont vous aurez besoin très souvent. Pour comprendre
la beauté de ce système, vous devriez regarder comment Zope 2 récupère l’objet racine physique et comment ceci
est implémenté. Il existe une méthode getPhysicalRoot virtuellement pour tous les objets conteneurs.
4.7.5 DefaultSized
Emplacement : zope.size.DefaultSized
Cet adaptateur n’est qu’une implémentation par défaut de l’interface ISized. Il est inscrit pour tous les types
d’objets. Si vous voulez inscrire cet adaptateur pour une interface particulière, vous devez surcharger cette inscription avec votre implémentation.
L’inscription de l’adaptateur ressemble à ceci
4.7. Usage de la ZCA dans Zope
113
Plone pour les développeurs, Version 1.0.0
<adapter
for="*"
factory="zope.size.DefaultSized"
provides="zope.size.interfaces.ISized"
permission="zope.View"
/>
Comme vous pouvez voir, l’interface adaptée est « * », pour pouvoir s’adapter à tous les types d’objets.
ISized est une interface simple possédant deux méthodes
class ISized(Interface):
def sizeForSorting():
"""Returns a tuple (basic_unit, amount)
Used for sorting among different kinds of sized objects.
’amount’ need only be sortable among things that share the
same basic unit."""
def sizeForDisplay():
"""Returns a string giving the size.
"""
Vous pouvez trouver un autre adaptateur ISized pour IZPTPage dans le paquet zope.app.zptpage.
4.7.6 ZopeVersionUtility
Emplacement : zope.app.applicationcontrol.ZopeVersionUtility
Cet utilitaire donne la version de Zope en fonctionnement.
L’inscription est la suivante
<utility
component=".zopeversion.ZopeVersionUtility"
provides=".interfaces.IZopeVersion" />
L’interface fournie, IZopeVersion, n’a qu’une méthode nommée getZopeVersion. Cette méthode renvoie
une chaîne contenant la version de Zope (avec éventuellement un changeset SVN).
L’implémentation par défaut, ZopeVersionUtility, récupère le numéro de version dans un fichier
version.txt du répertoire zope/app. Si Zope fonctionne depuis un espace de travail Subversion, il montre
le dernier numéro de révision. Si aucun des deux ne fonctionne il renvoie DevCenter/Unknown.
4.8 Étude de cas
Note : Ce chapitre n’est pas terminé. Merci d’envoyer vos suggestions !
4.8.1 Introduction
Ce chapitre est un exemple de création d’une application lourde avec la bibliothèque d’interface graphique PyGTK
et la ZCA. Cette application utilise également deux types différents de mécanismes de persistance des données : le
premier est une base de données objet (ZODB), l’autre est une base de données relationnelle (SQLite). Cependant,
en pratique, un seul des deux mécanismes peut être utilisé dans une installation donnée. L’utilisation de deux types
de persistance est là pour démontrer comment utiliser la ZCA pour brancher des composants entre eux. La majorité
de code de cette application est lié à PyGTK.
114
Chapitre 4. Le guide complet de l’Architecture de Composants de Zope
Plone pour les développeurs, Version 1.0.0
Lorsque l’application grossit, vous pouvez utiliser les composants de la ZCA aux endroits où vous souhaitez
fournir de la modularité ou de l’extensibilité. Utilisez des objets Python classiques quand vous n’avez pas besoin
de ces propriétés.
Il n’y a pas de différence entre l’utilisation de la ZCA pour une application web, pour une application graphique
lourde, ou pour toute autre application ou framework. Il est préférable de suivre une convention pour l’emplacement où vous allez inscrire les composants. Cette application utilise une convention qui peut être étendue en
plaçant des inscriptions ou des composants similaires dans des modules séparés et en les important plus tard depuis
le module principal d’inscription. Dans cette application, le module principal pour l’inscription est register.py.
Le code source de cette application peut être téléchargé sur : http ://www.muthukadan.net/downloads/zcalib.tar.bz2
4.8.2 Cas d’utilisation
L’application que nous allons créer ici est un système de gestion de bibliothèque avec des fonctionnalités minimales. Les besoins peuvent être résumés comme ceci :
– Ajouter des membres avec un numéro et un nom uniques.
– Ajouter des livres avec un code barre, un auteur et un titre.
– Prêter des livres
– Récupérer des livres
L’application peut être conçue de telle façon que les grandes fonctionnalités seront utilisées dans une fenêtre
unique. La fenêtre principale peut être conçue de cette façon :
Depuis la fenêtre des membres, l’utilisateur devrait être capable de gérer des membres. Donc il devrait être possible
d’ajouter, modifier et effacer des membres comme dans l’image ci-dessous :
Similaire à la fenêtre des membres, la fenêtre du catalogue permet aux utilisateurs d’ajouter, modifier et effacer
des livres :
4.8. Étude de cas
115
Plone pour les développeurs, Version 1.0.0
La fenêtre de circulation doit avoir ce qu’il faut pour prêter et récupérer des livres :
4.8.3 Survol du code PyGTK
Vous pouvez constater que la majorité du code est en rapport avec PyGTK. Sa structure est très similaire pour
les différentes fenêtres. Les écrans de cette application sont conçus avec le créateur d’interface graphiques Glade.
Vous devriez donner des noms appropriés aux widgets que vous allez utiliser dans votre code. Dans la fenêtre
principale, tous les éléments de menu ont des noms du type : circulation, catalog, member, quit et about.
La classe gtk.glade.XML est utilisée pour analyser le fichier Glade, et donc pour créer les objets widgets de
l’interface graphique. Voici comment faire
import gtk.glade
xmlobj = gtk.glade.XML(’/path/to/file.glade’)
widget = xmlobj.get_widget(’widget_name’)
Dans mainwindow.py, vous pouvez voir le code suivant
curdir = os.path.abspath(os.path.dirname(__file__))
xml = os.path.join(curdir, ’glade’, ’mainwindow.glade’)
xmlobj = gtk.glade.XML(xml)
self.mainwindow = xmlobj.get_widget(’mainwindow’)
Le nom du widget fenêtre principal est mainwindow. Les autres widgets sont créés de la même manière
circulation = xmlobj.get_widget(’circulation’)
member = xmlobj.get_widget(’member’)
quit = xmlobj.get_widget(’quit’)
catalog = xmlobj.get_widget(’catalog’)
about = xmlobj.get_widget(’about’)
Ensuite ces widgets sont connectés à certains événements
self.mainwindow.connect(’delete_event’, self.delete_event)
quit.connect(’activate’, self.delete_event)
circulation.connect(’activate’, self.on_circulation_activate)
member.connect(’activate’, self.on_member_activate)
catalog.connect(’activate’, self.on_catalog_activate)
about.connect(’activate’, self.on_about_activate)
116
Chapitre 4. Le guide complet de l’Architecture de Composants de Zope
Plone pour les développeurs, Version 1.0.0
L’événement delete_event est celui correspondant à la fermeture de la fenêtre lors de l’appui sur le bouton de fermeture. L’événement activate est émis lorsque le menu est sélectionné. Les widgets sont connectés à des fonctions
de rappel pour certains événements.
Vous pouvez voir dans le code ci-dessus que la fenêtre principale est connectée à la méthode on_delete_event pour
l’événement delete_event. Le widget quit est aussi connecté aux mêmes méthodes pour l’événement activate
def on_delete_event(self, *args):
gtk.main_quit()
La fonction de rappel lance juste la fonction main_quit.
4.8.4 Le code
Voici zcalib.py
import registry
import mainwindow
if __name__ == ’__main__’:
registry.initialize()
try:
mainwindow.main()
except KeyboardInterrupt:
import sys
sys.exit(1)
Ici, deux modules sont importés : registry et mainwindow. Ensuite le registre est initialisé et la fonction main de
la fenêtre principale est appelée. Si l’utilisateur essaye de fermer l’application en appuyant sur Ctrl-C, celle-ci
s’arrête normalement car nous avons intercepté l’exception KeyboardInterrupt.
Voici registry.py
import sys
from zope.component import getGlobalSiteManager
from
from
from
from
interfaces
interfaces
interfaces
interfaces
import
import
import
import
IMember
IBook
ICirculation
IDbOperation
def initialize_rdb():
from interfaces import IRelationalDatabase
from relationaldatabase import RelationalDatabase
from member import MemberRDbOperation
from catalog import BookRDbOperation
from circulation import CirculationRDbOperation
gsm = getGlobalSiteManager()
db = RelationalDatabase()
gsm.registerUtility(db, IRelationalDatabase)
gsm.registerAdapter(MemberRDbOperation,
(IMember,),
IDbOperation)
gsm.registerAdapter(BookRDbOperation,
(IBook,),
IDbOperation)
gsm.registerAdapter(CirculationRDbOperation,
4.8. Étude de cas
117
Plone pour les développeurs, Version 1.0.0
(ICirculation,),
IDbOperation)
def initialize_odb():
from interfaces import IObjectDatabase
from objectdatabase import ObjectDatabase
from member import MemberODbOperation
from catalog import BookODbOperation
from circulation import CirculationODbOperation
gsm = getGlobalSiteManager()
db = ObjectDatabase()
gsm.registerUtility(db, IObjectDatabase)
gsm.registerAdapter(MemberODbOperation,
(IMember,),
IDbOperation)
gsm.registerAdapter(BookODbOperation,
(IBook,),
IDbOperation)
gsm.registerAdapter(CirculationODbOperation,
(ICirculation,),
IDbOperation)
def check_use_relational_db():
use_rdb = False
try:
arg = sys.argv[1]
if arg == ’-r’:
return True
except IndexError:
pass
return use_rdb
def initialize():
use_rdb = check_use_relational_db()
if use_rdb:
initialize_rdb()
else:
initialize_odb()
Observez la fonction initialize que nous appelons depuis le module principal zcalib.py. Cette fonction commence
par vérifier quelle base de données utiliser : une base relationnelle (RDB) ou une base objet (ODB). Cette vérification est effectuée dans la fonction check_use_relational_db. Si l’option -r est passé dans la ligne de commandes,
elle appelera la fonction initialize_rdb, sinon initialize_odb. Si la fonction RDB est appelée, elle configure tous
les composants liés à la base relationnelle. Si la fonction ODB est appelée, elle configure tous les composants liés
à la base objet.
Voici mainwindow.py
import os
import gtk
import gtk.glade
from circulationwindow import circulationwindow
from catalogwindow import catalogwindow
from memberwindow import memberwindow
class MainWindow(object):
def __init__(self):
118
Chapitre 4. Le guide complet de l’Architecture de Composants de Zope
Plone pour les développeurs, Version 1.0.0
curdir = os.path.abspath(os.path.dirname(__file__))
xml = os.path.join(curdir, ’glade’, ’mainwindow.glade’)
xmlobj = gtk.glade.XML(xml)
self.mainwindow = xmlobj.get_widget(’mainwindow’)
circulation = xmlobj.get_widget(’circulation’)
member = xmlobj.get_widget(’member’)
quit = xmlobj.get_widget(’quit’)
catalog = xmlobj.get_widget(’catalog’)
about = xmlobj.get_widget(’about’)
self.mainwindow.connect(’delete_event’, self.delete_event)
quit.connect(’activate’, self.delete_event)
circulation.connect(’activate’, self.on_circulation_activate)
member.connect(’activate’, self.on_member_activate)
catalog.connect(’activate’, self.on_catalog_activate)
about.connect(’activate’, self.on_about_activate)
def delete_event(self, *args):
gtk.main_quit()
def on_circulation_activate(self, *args):
circulationwindow.show_all()
def on_member_activate(self, *args):
memberwindow.show_all()
def on_catalog_activate(self, *args):
catalogwindow.show_all()
def on_about_activate(self, *args):
pass
def run(self):
self.mainwindow.show_all()
def main():
mainwindow = MainWindow()
mainwindow.run()
gtk.main()
La fonction main crée une instance de la classe MainWindow qui initialise tous les widgets.
Voici memberwindow.py
import os
import gtk
import gtk.glade
from zope.component import getAdapter
from components import Member
from interfaces import IDbOperation
class MemberWindow(object):
def __init__(self):
curdir = os.path.abspath(os.path.dirname(__file__))
xml = os.path.join(curdir, ’glade’, ’memberwindow.glade’)
xmlobj = gtk.glade.XML(xml)
4.8. Étude de cas
119
Plone pour les développeurs, Version 1.0.0
self.memberwindow = xmlobj.get_widget(’memberwindow’)
self.number = xmlobj.get_widget(’number’)
self.name = xmlobj.get_widget(’name’)
add = xmlobj.get_widget(’add’)
update = xmlobj.get_widget(’update’)
delete = xmlobj.get_widget(’delete’)
close = xmlobj.get_widget(’close’)
self.treeview = xmlobj.get_widget(’treeview’)
self.memberwindow.connect(’delete_event’, self.on_delete_event)
add.connect(’clicked’, self.on_add_clicked)
update.connect(’clicked’, self.on_update_clicked)
delete.connect(’clicked’, self.on_delete_clicked)
close.connect(’clicked’, self.on_delete_event)
self.initialize_list()
def show_all(self):
self.populate_list_store()
self.memberwindow.show_all()
def populate_list_store(self):
self.list_store.clear()
member = Member()
memberdboperation = getAdapter(member, IDbOperation)
members = memberdboperation.get()
for member in members:
number = member.number
name = member.name
self.list_store.append((member, number, name,))
def on_delete_event(self, *args):
self.memberwindow.hide()
return True
def initialize_list(self):
self.list_store = gtk.ListStore(object, str, str)
self.treeview.set_model(self.list_store)
tvcolumn = gtk.TreeViewColumn(’Member Number’)
self.treeview.append_column(tvcolumn)
cell = gtk.CellRendererText()
tvcolumn.pack_start(cell, True)
tvcolumn.add_attribute(cell, ’text’, 1)
tvcolumn = gtk.TreeViewColumn(’Member Name’)
self.treeview.append_column(tvcolumn)
cell = gtk.CellRendererText()
tvcolumn.pack_start(cell, True)
tvcolumn.add_attribute(cell, ’text’, 2)
def on_add_clicked(self, *args):
number = self.number.get_text()
name = self.name.get_text()
member = Member()
member.number = number
member.name = name
self.add(member)
self.list_store.append((member, number, name,))
def add(self, member):
memberdboperation = getAdapter(member, IDbOperation)
120
Chapitre 4. Le guide complet de l’Architecture de Composants de Zope
Plone pour les développeurs, Version 1.0.0
memberdboperation.add()
def on_update_clicked(self, *args):
number = self.number.get_text()
name = self.name.get_text()
treeselection = self.treeview.get_selection()
model, iter = treeselection.get_selected()
if not iter:
return
member = self.list_store.get_value(iter, 0)
member.number = number
member.name = name
self.update(member)
self.list_store.set(iter, 1, number, 2, name)
def update(self, member):
memberdboperation = getAdapter(member, IDbOperation)
memberdboperation.update()
def on_delete_clicked(self, *args):
treeselection = self.treeview.get_selection()
model, iter = treeselection.get_selected()
if not iter:
return
member = self.list_store.get_value(iter, 0)
self.delete(member)
self.list_store.remove(iter)
def delete(self, member):
memberdboperation = getAdapter(member, IDbOperation)
memberdboperation.delete()
memberwindow = MemberWindow()
Voici components.py
from zope.interface import implements
from interfaces import IBook
from interfaces import IMember
from interfaces import ICirculation
class Book(object):
implements(IBook)
barcode = ""
title = ""
author = ""
class Member(object):
implements(IMember)
number = ""
name = ""
class Circulation(object):
implements(ICirculation)
book = Book()
member = Member()
4.8. Étude de cas
121
Plone pour les développeurs, Version 1.0.0
Voici interfaces.py
from zope.interface import Interface
from zope.interface import Attribute
class IBook(Interface):
barcode = Attribute("Barcode")
author = Attribute("Author of book")
title = Attribute("Title of book")
class IMember(Interface):
number = Attribute("ID number")
name = Attribute("Name of member")
class ICirculation(Interface):
book = Attribute("A book")
member = Attribute("A member")
class IRelationalDatabase(Interface):
def commit():
pass
def rollback():
pass
def cursor():
pass
def get_next_id():
pass
class IObjectDatabase(Interface):
def commit():
pass
def rollback():
pass
def container():
pass
def get_next_id():
pass
class IDbOperation(Interface):
def get():
pass
def add():
pass
122
Chapitre 4. Le guide complet de l’Architecture de Composants de Zope
Plone pour les développeurs, Version 1.0.0
def update():
pass
def delete():
pass
Voici member.py
from zope.interface import implements
from zope.component import getUtility
from zope.component import adapts
from components import Member
from
from
from
from
interfaces
interfaces
interfaces
interfaces
import
import
import
import
IRelationalDatabase
IObjectDatabase
IMember
IDbOperation
class MemberRDbOperation(object):
implements(IDbOperation)
adapts(IMember)
def __init__(self, member):
self.member = member
def get(self):
db = getUtility(IRelationalDatabase)
cr = db.cursor()
number = self.member.number
if number:
cr.execute("""SELECT
id,
number,
name
FROM members
WHERE number = ?""",
(number,))
else:
cr.execute("""SELECT
id,
number,
name
FROM members""")
rst = cr.fetchall()
cr.close()
members = []
for record in rst:
id = record[’id’]
number = record[’number’]
name = record[’name’]
member = Member()
member.id = id
member.number = number
member.name = name
members.append(member)
return members
def add(self):
db = getUtility(IRelationalDatabase)
cr = db.cursor()
4.8. Étude de cas
123
Plone pour les développeurs, Version 1.0.0
next_id = db.get_next_id("members")
number = self.member.number
name = self.member.name
cr.execute("""INSERT INTO members
(id, number, name)
VALUES (?, ?, ?)""",
(next_id, number, name))
cr.close()
db.commit()
self.member.id = next_id
def update(self):
db = getUtility(IRelationalDatabase)
cr = db.cursor()
number = self.member.number
name = self.member.name
id = self.member.id
cr.execute("""UPDATE members
SET
number = ?,
name = ?
WHERE id = ?""",
(number, name, id))
cr.close()
db.commit()
def delete(self):
db = getUtility(IRelationalDatabase)
cr = db.cursor()
id = self.member.id
cr.execute("""DELETE FROM members
WHERE id = ?""",
(id,))
cr.close()
db.commit()
class MemberODbOperation(object):
implements(IDbOperation)
adapts(IMember)
def __init__(self, member):
self.member = member
def get(self):
db = getUtility(IObjectDatabase)
zcalibdb = db.container()
members = zcalibdb[’members’]
return members.values()
def add(self):
db = getUtility(IObjectDatabase)
zcalibdb = db.container()
members = zcalibdb[’members’]
number = self.member.number
if number in [x.number for x in members.values()]:
db.rollback()
raise Exception("Duplicate key")
next_id = db.get_next_id(’members’)
self.member.id = next_id
members[next_id] = self.member
db.commit()
124
Chapitre 4. Le guide complet de l’Architecture de Composants de Zope
Plone pour les développeurs, Version 1.0.0
def update(self):
db = getUtility(IObjectDatabase)
zcalibdb = db.container()
members = zcalibdb[’members’]
id = self.member.id
members[id] = self.member
db.commit()
def delete(self):
db = getUtility(IObjectDatabase)
zcalibdb = db.container()
members = zcalibdb[’members’]
id = self.member.id
del members[id]
db.commit()
4.8.5 PySQLite
4.8.6 ZODB
4.8.7 Conclusions
4.9 Reference
4.9.1 adaptedBy
Cette fonction permet de trouver les interfaces adaptées.
– Emplacement : zope.component
– Signature : adaptedBy(object)
Exemple
>>> from zope.interface import implements
>>> from zope.component import adapts
>>> from zope.component import adaptedBy
>>> class FrontDeskNG(object):
...
...
implements(IDesk)
...
adapts(IGuest)
...
...
def __init__(self, guest):
...
self.guest = guest
>>> adaptedBy(FrontDeskNG)
(<InterfaceClass __builtin__.IGuest>,)
4.9.2 adapter
Un adaptateur peut être n’importe quel objet appelable. Vous pouvez utiliser le décorateur adapter pour déclarer
qu’un objet appelable s’adapte à des interfaces (ou des classes).
– Emplacement : zope.component
– Signature : adapter(*interfaces)
Exemple
4.9. Reference
125
Plone pour les développeurs, Version 1.0.0
>>>
>>>
>>>
>>>
>>>
from
from
from
from
from
zope.interface
zope.interface
zope.interface
zope.component
zope.interface
import
import
import
import
import
Attribute
Interface
implementer
adapter
implements
>>> class IJob(Interface):
...
"""A job"""
>>> class Job(object):
...
implements(IJob)
>>> class IPerson(Interface):
...
...
name = Attribute("Name")
...
job = Attribute("Job")
>>> class Person(object):
...
implements(IPerson)
...
...
name = None
...
job = None
>>> @implementer(IJob)
... @adapter(IPerson)
... def personJob(person):
...
return person.job
>>> jack = Person()
>>> jack.name = "Jack"
>>> jack.job = Job()
>>> personJob(jack)
<Job object at ...>
4.9.3 adapts
Cette fonction permet de déclarer les classes adaptateurs.
– Emplacement : zope.component
– Signature : adapts(*interfaces)
Exemple
>>> from zope.interface import implements
>>> from zope.component import adapts
>>> class FrontDeskNG(object):
...
...
implements(IDesk)
...
adapts(IGuest)
...
...
def __init__(self, guest):
...
self.guest = guest
...
...
def register(self):
...
next_id = get_next_id()
...
bookings_db[next_id] = {
...
’name’: guest.name,
...
’place’: guest.place,
...
’phone’: guest.phone
...
}
126
Chapitre 4. Le guide complet de l’Architecture de Composants de Zope
Plone pour les développeurs, Version 1.0.0
4.9.4 alsoProvides
Déclare des interfaces additionnelles fournies par un objet. Les arguments après l’objet sont une ou plusieurs
interfaces. Les interfaces données sont ajoutées aux interfaces précédemment déclarées pour l’objet.
– Emplacement : zope.interface
– Signature : alsoProvides(object, *interfaces)
Exemple
>>>
>>>
>>>
>>>
from
from
from
from
zope.interface
zope.interface
zope.interface
zope.interface
import
import
import
import
Attribute
Interface
implements
alsoProvides
>>> class IPerson(Interface):
...
...
name = Attribute("Name of person")
>>> class IStudent(Interface):
...
...
college = Attribute("Name of college")
>>> class Person(object):
...
...
implements(IDesk)
...
name = u""
>>>
>>>
>>>
>>>
jack = Person()
jack.name = "Jack"
jack.college = "New College"
alsoProvides(jack, IStudent)
Vous pouvez tester de cette façon:
>>> from zope.interface import providedBy
>>> IStudent in providedBy(jack)
True
4.9.5 Attribute
En utilisant cette classe, vous pouvez définir des attributs dans une interface.
– Emplacement : zope.interface
– Signature : Attribute(name, doc=’‘)
– Voir aussi : Interface
Exemple
>>> from zope.interface import Attribute
>>> from zope.interface import Interface
>>> class IPerson(Interface):
...
...
name = Attribute("Name of person")
...
email = Attribute("Email Address")
4.9.6 classImplements
Déclare des interfaces additionnelles qui doivent être fournies par les instances d’une classe. Les arguments après
la classe sont une ou plusieurs interfaces. Les interfaces données sont ajoutées aux interfaces précédemment
déclarées.
– Emplacement : zope.interface
4.9. Reference
127
Plone pour les développeurs, Version 1.0.0
– Signature : classImplements(cls, *interfaces)
Exemple
>>>
>>>
>>>
>>>
from
from
from
from
zope.interface
zope.interface
zope.interface
zope.interface
import
import
import
import
Attribute
Interface
implements
classImplements
>>> class IPerson(Interface):
...
...
name = Attribute("Name of person")
>>> class IStudent(Interface):
...
...
college = Attribute("Name of college")
>>> class Person(object):
...
...
implements(IDesk)
...
name = u""
...
college = u""
>>>
>>>
>>>
>>>
classImplements(Person, IStudent)
jack = Person()
jack.name = "Jack"
jack.college = "New College"
Vous pouvez tester de cette façon :
>>> from zope.interface import providedBy
>>> IStudent in providedBy(jack)
True
4.9.7 classImplementsOnly
Déclare les seules interfaces qui devront être fournies par les instances d’une classe. Les arguments après la classe
sont une ou plusieurs interfaces. Les interfaces fournies vont remplacer toutes les interfaces des déclarations
précédentes.
– Emplacement : zope.interface
– Signature : classImplementsOnly(cls, *interfaces)
Exemple
>>>
>>>
>>>
>>>
from
from
from
from
zope.interface
zope.interface
zope.interface
zope.interface
import
import
import
import
Attribute
Interface
implements
classImplementsOnly
>>> class IPerson(Interface):
...
...
name = Attribute("Name of person")
>>> class IStudent(Interface):
...
...
college = Attribute("Name of college")
>>> class Person(object):
...
...
implements(IPerson)
...
college = u""
>>> classImplementsOnly(Person, IStudent)
128
Chapitre 4. Le guide complet de l’Architecture de Composants de Zope
Plone pour les développeurs, Version 1.0.0
>>> jack = Person()
>>> jack.college = "New College"
Vous pouvez tester de cette façon :
>>> from zope.interface import providedBy
>>> IPerson in providedBy(jack)
False
>>> IStudent in providedBy(jack)
True
4.9.8 classProvides
Normalement si une classe implémente une interface particulière, les instances de cette classe fourniront l’interface
implémentée par la classe. Mais si vous voulez qu’une classe fournisse une interface, vous pouvez le déclarer grâce
à la fonction classProvides.
– Emplacement : zope.interface
– Signature : classProvides(*interfaces)
Exemple
>>> from zope.interface import Attribute
>>> from zope.interface import Interface
>>> from zope.interface import classProvides
>>> class IPerson(Interface):
...
...
name = Attribute("Name of person")
>>> class Person(object):
...
...
classProvides(IPerson)
...
name = u"Jack"
Vous pouvez tester de cette façon :
>>> from zope.interface import providedBy
>>> IPerson in providedBy(Person)
True
4.9.9 ComponentLookupError
C’est l’exception levée quand une recherche de composant échoue.
Exemple
>>> class IPerson(Interface):
...
...
name = Attribute("Name of person")
>>> person = object()
>>> getAdapter(person, IPerson, ’not-exists’)
Traceback (most recent call last):
...
ComponentLookupError: ...
4.9.10 createObject
Crée un objet en utilisant une fabrique.
4.9. Reference
129
Plone pour les développeurs, Version 1.0.0
Cette fonction trouve une fabrique nommée dans le site courant et l’appelle avec les arguments donnés. Si la bonne
fabrique ne peut pas être trouvée, une erreur ComponentLookupError est déclenchée. Sinon l’objet créé est
renvoyé.
Un argument mot-clé contextuel peut être fourni pour rechercher une fabrique dans un emplacement différent du
site courant. (Bien sûr, cela signifie qu’il est impossible de transmettre à la fabrique un argument mot-clé nommé
« context ».
– Emplacement : zope.component
– Signature : createObject(factory_name, *args, **kwargs)
Exemple
>>> from zope.interface import Attribute
>>> from zope.interface import Interface
>>> from zope.interface import implements
>>> class IDatabase(Interface):
...
...
def getConnection():
...
"""Return connection object"""
>>> class FakeDb(object):
...
...
implements(IDatabase)
...
...
def getConnection(self):
...
return "connection"
>>> from zope.component.factory import Factory
>>> factory = Factory(FakeDb, ’FakeDb’)
>>> from zope.component import getGlobalSiteManager
>>> gsm = getGlobalSiteManager()
>>> from zope.component.interfaces import IFactory
>>> gsm.registerUtility(factory, IFactory, ’fakedb’)
>>> from zope.component import createObject
>>> createObject(’fakedb’)
<FakeDb object at ...>
4.9.11 Declaration
N’a pas besoin d’être utilisé directement.
4.9.12 directlyProvidedBy
Cette fonction renvoie les interfaces fournies directement par un objet donné.
– Emplacement : zope.interface
– Signature : directlyProvidedBy(object)
Exemple
>>> from zope.interface import Attribute
>>> from zope.interface import Interface
>>> class IPerson(Interface):
...
...
name = Attribute("Name of person")
>>> class IStudent(Interface):
130
Chapitre 4. Le guide complet de l’Architecture de Composants de Zope
Plone pour les développeurs, Version 1.0.0
...
...
college = Attribute("Name of college")
>>> class ISmartPerson(Interface):
...
pass
>>> class Person(object):
...
...
implements(IPerson)
...
name = u""
>>>
>>>
>>>
>>>
jack = Person()
jack.name = u"Jack"
jack.college = "New College"
alsoProvides(jack, ISmartPerson, IStudent)
>>> from zope.interface import directlyProvidedBy
>>> jack_dp = directlyProvidedBy(jack)
>>> IPerson in jack_dp.interfaces()
False
>>> IStudent in jack_dp.interfaces()
True
>>> ISmartPerson in jack_dp.interfaces()
True
4.9.13 directlyProvides
Déclare que des interfaces sont fournies directement par un objet. Les arguments après l’objet sont une ou plusieurs
interfaces. Les interfaces données remplacent celles précédemment déclarées pour l’objet.
– Emplacement : zope.interface
– Signature : directlyProvides(object, *interfaces)
Exemple
>>> from zope.interface import Attribute
>>> from zope.interface import Interface
>>> class IPerson(Interface):
...
...
name = Attribute("Name of person")
>>> class IStudent(Interface):
...
...
college = Attribute("Name of college")
>>> class ISmartPerson(Interface):
...
pass
>>> class Person(object):
...
...
implements(IPerson)
...
name = u""
>>>
>>>
>>>
>>>
jack = Person()
jack.name = u"Jack"
jack.college = "New College"
alsoProvides(jack, ISmartPerson, IStudent)
>>> from zope.interface import directlyProvidedBy
>>> jack_dp = directlyProvidedBy(jack)
4.9. Reference
131
Plone pour les développeurs, Version 1.0.0
>>> ISmartPerson in jack_dp.interfaces()
True
>>> IPerson in jack_dp.interfaces()
False
>>> IStudent in jack_dp.interfaces()
True
>>> from zope.interface import providedBy
>>> ISmartPerson in providedBy(jack)
True
>>> from zope.interface import directlyProvides
>>> directlyProvides(jack, IStudent)
>>> jack_dp = directlyProvidedBy(jack)
>>> ISmartPerson in jack_dp.interfaces()
False
>>> IPerson in jack_dp.interfaces()
False
>>> IStudent in jack_dp.interfaces()
True
>>> ISmartPerson in providedBy(jack)
False
4.9.14 getAdapter
Cette fonction récupère un adaptateur (nommé) vers une interface, pour un objet donné. Elle renvoie un adaptateur qui peut adapter les objets à l’interface voulue. Si aucun adaptateur ne peut être trouvé, une erreur
ComponentLookupError est émise.
– Emplacement : zope.interface
– Signature : getAdapter(object, interface=Interface, name=u’‘, context=None)
Exemple
>>> from zope.interface import Attribute
>>> from zope.interface import Interface
>>> class IDesk(Interface):
...
"""A frontdesk will register object’s details"""
...
...
def register():
...
"""Register object’s details"""
...
>>> from zope.interface import implements
>>> from zope.component import adapts
>>> class FrontDeskNG(object):
...
...
implements(IDesk)
...
adapts(IGuest)
...
...
def __init__(self, guest):
...
self.guest = guest
...
...
def register(self):
...
next_id = get_next_id()
...
bookings_db[next_id] = {
...
’name’: guest.name,
...
’place’: guest.place,
...
’phone’: guest.phone
132
Chapitre 4. Le guide complet de l’Architecture de Composants de Zope
Plone pour les développeurs, Version 1.0.0
...
}
>>> class Guest(object):
...
...
implements(IGuest)
...
...
def __init__(self, name, place):
...
self.name = name
...
self.place = place
>>> jack = Guest("Jack", "Bangalore")
>>> jack_frontdesk = FrontDeskNG(jack)
>>> IDesk.providedBy(jack_frontdesk)
True
>>> from zope.component import getGlobalSiteManager
>>> gsm = getGlobalSiteManager()
>>> gsm.registerAdapter(FrontDeskNG,
...
(IGuest,), IDesk, ’ng’)
>>> getAdapter(jack, IDesk, ’ng’)
<FrontDeskNG object at ...>
4.9.15 getAdapterInContext
Au lieu d’utiliser cette fonction, utilisez getAdapter avec un argument context.
– Emplacement : zope.component
– Signature : getAdapterInContext(object, interface, context)
– Voir aussi : queryAdapterInContext
Exemple
>>> from zope.component.globalregistry import BaseGlobalComponents
>>> from zope.component import IComponentLookup
>>> sm = BaseGlobalComponents()
>>> class Context(object):
...
def __init__(self, sm):
...
self.sm = sm
...
def __conform__(self, interface):
...
if interface.isOrExtends(IComponentLookup):
...
return self.sm
>>> context = Context(sm)
>>> from zope.interface import Attribute
>>> from zope.interface import Interface
>>> class IDesk(Interface):
...
"""A frontdesk will register object’s details"""
...
...
def register():
...
"""Register object’s details"""
...
>>> from zope.interface import implements
>>> from zope.component import adapts
>>> class FrontDeskNG(object):
...
...
implements(IDesk)
4.9. Reference
133
Plone pour les développeurs, Version 1.0.0
...
...
...
...
...
...
...
...
...
...
...
...
adapts(IGuest)
def __init__(self, guest):
self.guest = guest
def register(self):
next_id = get_next_id()
bookings_db[next_id] = {
’name’: guest.name,
’place’: guest.place,
’phone’: guest.phone
}
>>> class Guest(object):
...
...
implements(IGuest)
...
...
def __init__(self, name, place):
...
self.name = name
...
self.place = place
>>> jack = Guest("Jack", "Bangalore")
>>> jack_frontdesk = FrontDeskNG(jack)
>>> IDesk.providedBy(jack_frontdesk)
True
>>> from zope.component import getGlobalSiteManager
>>> gsm = getGlobalSiteManager()
>>> sm.registerAdapter(FrontDeskNG,
...
(IGuest,), IDesk)
>>> from zope.component import getAdapterInContext
>>> getAdapterInContext(jack, IDesk, sm)
<FrontDeskNG object at ...>
4.9.16 getAdapters
Recherche pour des objets tous les adaptateurs fournissant une interface donnée. Une liste d’adaptateurs est renvoyée. Si un adaptateur est nommé, seul l’adaptateur le plus spécifique pour un nom donné est renvoyé.
– Emplacement : zope.component
– Signature : getAdapters(objects, provided, context=None)
Exemple
>>> from zope.interface import implements
>>> from zope.component import adapts
>>> class FrontDeskNG(object):
...
...
implements(IDesk)
...
adapts(IGuest)
...
...
def __init__(self, guest):
...
self.guest = guest
...
...
def register(self):
...
next_id = get_next_id()
...
bookings_db[next_id] = {
...
’name’: guest.name,
...
’place’: guest.place,
134
Chapitre 4. Le guide complet de l’Architecture de Composants de Zope
Plone pour les développeurs, Version 1.0.0
...
...
’phone’: guest.phone
}
>>> jack = Guest("Jack", "Bangalore")
>>> jack_frontdesk = FrontDeskNG(jack)
>>> from zope.component import getGlobalSiteManager
>>> gsm = getGlobalSiteManager()
>>> gsm.registerAdapter(FrontDeskNG, name=’ng’)
>>> from zope.component import getAdapters
>>> list(getAdapters((jack,), IDesk))
[(u’ng’, <FrontDeskNG object at ...>)]
4.9.17 getAllUtilitiesRegisteredFor
Renvoie tous les utilitaires inscrits pour une interface. Ceci inclut les utilitaires surchargés. La valeur renvoyée est
un objet iterable listant les instances d’utilitaires correspondants.
– Emplacement : zope.component
– Signature : getAllUtilitiesRegisteredFor(interface)
Exemple
>>> from zope.interface import Interface
>>> from zope.interface import implements
>>> class IGreeter(Interface):
...
def greet(name):
...
"say hello"
>>> class Greeter(object):
...
...
implements(IGreeter)
...
...
def greet(self, name):
...
print "Hello", name
>>> from zope.component import getGlobalSiteManager
>>> gsm = getGlobalSiteManager()
>>> greet = Greeter()
>>> gsm.registerUtility(greet, IGreeter)
>>> from zope.component import getAllUtilitiesRegisteredFor
>>> getAllUtilitiesRegisteredFor(IGreeter)
[<Greeter object at ...>]
4.9.18 getFactoriesFor
Renvoie un tuple (nom, fabrique) de fabriques inscrites capables de créer des objets qui fournissent une interface
donnée.
– Emplacement : zope.component
– Signature : getFactoriesFor(interface, context=None)
Exemple
>>> from zope.interface import Attribute
>>> from zope.interface import Interface
>>> from zope.interface import implements
4.9. Reference
135
Plone pour les développeurs, Version 1.0.0
>>> class IDatabase(Interface):
...
...
def getConnection():
...
"""Return connection object"""
>>> class FakeDb(object):
...
...
implements(IDatabase)
...
...
def getConnection(self):
...
return "connection"
>>> from zope.component.factory import Factory
>>> factory = Factory(FakeDb, ’FakeDb’)
>>> from zope.component import getGlobalSiteManager
>>> gsm = getGlobalSiteManager()
>>> from zope.component.interfaces import IFactory
>>> gsm.registerUtility(factory, IFactory, ’fakedb’)
>>> from zope.component import getFactoriesFor
>>> list(getFactoriesFor(IDatabase))
[(u’fakedb’, <Factory for <class ’FakeDb’>>)]
4.9.19 getFactoryInterfaces
Récupère les interfaces implémentées par une fabrique. Cette fonction trouve la fabrique possédant le nom donné
la plus proche du contexte, puis renvoie l’interface ou le tuple d’interfaces que les instances créées par la fabrique
vont fournir.
– Emplacement : zope.component
– Signature : getFactoryInterfaces(name, context=None)
Exemple
>>> from zope.interface import Attribute
>>> from zope.interface import Interface
>>> from zope.interface import implements
>>> class IDatabase(Interface):
...
...
def getConnection():
...
"""Return connection object"""
>>> class FakeDb(object):
...
...
implements(IDatabase)
...
...
def getConnection(self):
...
return "connection"
>>> from zope.component.factory import Factory
>>> factory = Factory(FakeDb, ’FakeDb’)
>>> from zope.component import getGlobalSiteManager
>>> gsm = getGlobalSiteManager()
>>> from zope.component.interfaces import IFactory
136
Chapitre 4. Le guide complet de l’Architecture de Composants de Zope
Plone pour les développeurs, Version 1.0.0
>>> gsm.registerUtility(factory, IFactory, ’fakedb’)
>>> from zope.component import getFactoryInterfaces
>>> getFactoryInterfaces(’fakedb’)
<implementedBy __builtin__.FakeDb>
4.9.20 getGlobalSiteManager
Renvoie le gestionnaire global de site. Cette fonction ne doit jamais échouer et doit toujours renvoyer un objet qui
fournit IGlobalSiteManager.
– Emplacement : zope.component
– Signature : getGlobalSiteManager()
Exemple
>>> from zope.component import getGlobalSiteManager
>>> from zope.component import globalSiteManager
>>> gsm = getGlobalSiteManager()
>>> gsm is globalSiteManager
True
4.9.21 getMultiAdapter
Cette fonction recherche pour des objets un multi-adaptateur vers une interface donnée. Elle renvoie un multiadaptateur qui peut s’adapter aux objets donnés et fournir l’interface voulue. Si aucun adaptateur ne peut être
trouvé, une erreur ComponentLookupError est émise. Le nom constitué de la chaîne vide est réservé aux adaptateur
sans nom. Les méthodes des adaptateurs sans nom appellent souvent les méthodes des adaptateurs nommés en
fournissant un nom vide (‘’).
– Emplacement : zope.component
– Signature : getMultiAdapter(objects, interface=Interface, name=’‘, context=None)
– Voir aussi : queryMultiAdapter
Exemple
>>> from zope.interface import Interface
>>> from zope.interface import implements
>>> from zope.component import adapts
>>> class IAdapteeOne(Interface):
...
pass
>>> class IAdapteeTwo(Interface):
...
pass
>>> class IFunctionality(Interface):
...
pass
>>> class MyFunctionality(object):
...
implements(IFunctionality)
...
adapts(IAdapteeOne, IAdapteeTwo)
...
...
def __init__(self, one, two):
...
self.one = one
...
self.two = two
>>> from zope.component import getGlobalSiteManager
>>> gsm = getGlobalSiteManager()
>>> gsm.registerAdapter(MyFunctionality)
4.9. Reference
137
Plone pour les développeurs, Version 1.0.0
>>> class One(object):
...
implements(IAdapteeOne)
>>> class Two(object):
...
implements(IAdapteeTwo)
>>> one = One()
>>> two = Two()
>>> from zope.component import getMultiAdapter
>>> getMultiAdapter((one,two), IFunctionality)
<MyFunctionality object at ...>
>>> myfunctionality = getMultiAdapter((one,two), IFunctionality)
>>> myfunctionality.one
<One object at ...>
>>> myfunctionality.two
<Two object at ...>
4.9.22 getSiteManager
Récupère le gestionnaire de site le plus proche du contexte donné. Si context est None, cette fonction renvoie le
gestionnaire global de site. Si le context n’est pas None, on s’attend à ce qu’un adaptateur sur le contexte vers
IComponentLookup soit trouvé. Si aucun adaptateur n’est trouvé, une erreur ComponentLookupError est émise.
– Emplacement : zope.component
– Signature : getSiteManager(context=None)
Exemple 1
>>> from zope.component.globalregistry import BaseGlobalComponents
>>> from zope.component import IComponentLookup
>>> sm = BaseGlobalComponents()
>>> class Context(object):
...
def __init__(self, sm):
...
self.sm = sm
...
def __conform__(self, interface):
...
if interface.isOrExtends(IComponentLookup):
...
return self.sm
>>> context = Context(sm)
>>> from zope.component import getSiteManager
>>> lsm = getSiteManager(context)
>>> lsm is sm
True
Exemple 2
>>> from zope.component import getGlobalSiteManager
>>> gsm = getGlobalSiteManager()
>>> sm = getSiteManager()
>>> gsm is sm
True
138
Chapitre 4. Le guide complet de l’Architecture de Composants de Zope
Plone pour les développeurs, Version 1.0.0
4.9.23 getUtilitiesFor
Récupère les utilitaires inscrits pour une interface particulière. Renvoie un objet iterable listant les paires
nom/utilitaire.
– Emplacement : zope.component
– Signature : getUtilitiesFor(interface)
Exemple
>>> from zope.interface import Interface
>>> from zope.interface import implements
>>> class IGreeter(Interface):
...
def greet(name):
...
"say hello"
>>> class Greeter(object):
...
...
implements(IGreeter)
...
...
def greet(self, name):
...
print "Hello", name
>>> from zope.component import getGlobalSiteManager
>>> gsm = getGlobalSiteManager()
>>> greet = Greeter()
>>> gsm.registerUtility(greet, IGreeter)
>>> from zope.component import getUtilitiesFor
>>> list(getUtilitiesFor(IGreeter))
[(u’’, <Greeter object at ...>)]
4.9.24 getUtility
Récupère l’utilitaire qui fournit une interface donnée. Cette fonction renvoie l’utilitaire le plus proche du contexte
et qui fournit l’interface donnée. Si aucun n’est trouvé, une erreur ComponentLookupError est levée.
– Emplacement : zope.component
– Signature : getUtility(interface, name=’‘, context=None)
Exemple
>>> from zope.interface import Interface
>>> from zope.interface import implements
>>> class IGreeter(Interface):
...
def greet(name):
...
"say hello"
>>> class Greeter(object):
...
...
implements(IGreeter)
...
...
def greet(self, name):
...
return "Hello " + name
>>> from zope.component import getGlobalSiteManager
>>> gsm = getGlobalSiteManager()
>>> greet = Greeter()
>>> gsm.registerUtility(greet, IGreeter)
4.9. Reference
139
Plone pour les développeurs, Version 1.0.0
>>> from zope.component import getUtility
>>> getUtility(IGreeter).greet(’Jack’)
’Hello Jack’
4.9.25 handle
Appelle tous les gestionnaires pour un objet donné. Les gestionnaires sont des fabriques d’abonnés qui ne produisent rien. Ils font tout leur travail lors de l’appel. Ils sont typiquement utilisés pour gérer des événements.
– Emplacement : zope.component
– Signature : handle(*objects)
Exemple
>>> import datetime
>>> def documentCreated(event):
...
event.doc.created = datetime.datetime.utcnow()
>>> from zope.interface import Interface
>>> from zope.interface import Attribute
>>> from zope.interface import implements
>>> class IDocumentCreated(Interface):
...
doc = Attribute("The document that was created")
>>> class DocumentCreated(object):
...
implements(IDocumentCreated)
...
...
def __init__(self, doc):
...
self.doc = doc
>>> def documentCreated(event):
...
event.doc.created = datetime.datetime.utcnow()
>>> from zope.component import adapter
>>> @adapter(IDocumentCreated)
... def documentCreated(event):
...
event.doc.created = datetime.datetime.utcnow()
>>> from zope.component import getGlobalSiteManager
>>> gsm = getGlobalSiteManager()
>>> gsm.registerHandler(documentCreated)
>>> from zope.component import handle
>>> handle(DocumentCreated(doc))
>>> doc.created.__class__.__name__
’datetime’
4.9.26 implementedBy
Renvoie les interfaces implémentées par une classe.
– Emplacement : zope.interface
– Signature : implementedBy(class_)
Exemple 1
140
Chapitre 4. Le guide complet de l’Architecture de Composants de Zope
Plone pour les développeurs, Version 1.0.0
>>> from zope.interface import Interface
>>> from zope.interface import implements
>>> class IGreeter(Interface):
...
def greet(name):
...
"say hello"
>>> class Greeter(object):
...
...
implements(IGreeter)
...
...
def greet(self, name):
...
print "Hello", name
>>> from zope.interface import implementedBy
>>> implementedBy(Greeter)
<implementedBy __builtin__.Greeter>
Exemple 2
>>> from zope.interface import Attribute
>>> from zope.interface import Interface
>>> from zope.interface import implements
>>> class IPerson(Interface):
...
name = Attribute("Name of person")
>>> class ISpecial(Interface):
...
pass
>>> class Person(object):
...
implements(IPerson)
...
name = u""
>>> from zope.interface import classImplements
>>> classImplements(Person, ISpecial)
>>> from zope.interface import implementedBy
Pour récupérer une liste de toutes les interfaces implémentées par cette
classe ::
>>> [x.__name__ for x in implementedBy(Person)]
[’IPerson’, ’ISpecial’]
4.9.27 implementer
Créer un décorateur permettant de déclarer des interfaces implémentées par une fabrique. Un objet appelable est
renvoyé, qui fait une déclaration d’implémentation sur les objets qui lui sont transmis.
– Emplacement : zope.interface
– Signature : implementer(*interfaces)
Exemple
>>> from zope.interface import implementer
>>> class IFoo(Interface):
...
pass
>>> class Foo(object):
...
implements(IFoo)
>>> @implementer(IFoo)
... def foocreator():
4.9. Reference
141
Plone pour les développeurs, Version 1.0.0
...
foo = Foo()
...
return foo
>>> list(implementedBy(foocreator))
[<InterfaceClass __builtin__.IFoo>]
4.9.28 implements
Déclare que des interfaces seront fournies par une classe. Cette fonction est appelée dans une définition de classe.
Les arguments sont une ou plusieurs interfaces. Les interfaces données sont ajoutées aux interfaces précédemment
déclarées. Les déclarations précédentes incluent les déclarations des classes debase, à moins que implementsOnly
ne soit utilisé.
– Emplacement : zope.interface
– Signature : implements(*interfaces)
Exemple
>>> from zope.interface import Attribute
>>> from zope.interface import Interface
>>> from zope.interface import implements
>>> class IPerson(Interface):
...
...
name = Attribute("Name of person")
>>> class Person(object):
...
...
implements(IPerson)
...
name = u""
>>> jack = Person()
>>> jack.name = "Jack"
You can test it like this:
>>> from zope.interface import providedBy
>>> IPerson in providedBy(jack)
True
4.9.29 implementsOnly
Déclare les seules interfaces qui doivent être implémentées par une classe. Cette fonction est appelée dans la
définition de classe. Les arguments sont une ou plusieurs interfaces. Les déclarations précédentes, y compris
celles des classes de base, sont remplacées.
– Emplacement : zope.interface
– Signature : implementsOnly(*interfaces)
Exemple
>>>
>>>
>>>
>>>
from
from
from
from
zope.interface
zope.interface
zope.interface
zope.interface
import
import
import
import
Attribute
Interface
implements
implementsOnly
>>> class IPerson(Interface):
...
...
name = Attribute("Name of person")
>>> class IStudent(Interface):
...
...
college = Attribute("Name of college")
142
Chapitre 4. Le guide complet de l’Architecture de Composants de Zope
Plone pour les développeurs, Version 1.0.0
>>> class Person(object):
...
...
implements(IPerson)
...
name = u""
>>> class NewPerson(Person):
...
implementsOnly(IStudent)
...
college = u""
>>> jack = NewPerson()
>>> jack.college = "New College"
You can test it like this:
>>> from zope.interface import providedBy
>>> IPerson in providedBy(jack)
False
>>> IStudent in providedBy(jack)
True
4.9.30 Interface
Grâce à cette classe, vous pouvez définir une interface. Pour définir une interface, contentez-vous d’hériter de cette
classe.
– Emplacement : zope.interface
– Signature : Interface(name, doc=’‘)
Exemple 1
>>> from zope.interface import Attribute
>>> from zope.interface import Interface
>>> class IPerson(Interface):
...
...
name = Attribute("Name of person")
...
email = Attribute("Email Address")
Exemple 2
>>> from zope.interface import Interface
>>> class IHost(Interface):
...
...
def goodmorning(guest):
...
"""Say good morning to guest"""
4.9.31 moduleProvides
Déclare les interfaces fournies par un module. Cette fonction est utilisée dans une définition de module. Les arguments sont une ou plusieurs interfaces. Les interfaces fournies sont utilisées pour créer la spécification d’interface
objet du module. Une erreur est levée si le module a déjà une spécification d’interface. Autrement dit, c’est une
erreur d’appeler cette fonction plus d’une fois dans une définition de module.
Cette fonction est fournie pour des raisons pratiques. Elle fournit une manière plus pratique d’appeler
directlyProvides pour un module.
– Emplacement : zope.interface
– Signature : moduleProvides(*interfaces)
– Voir aussi : directlyProvides
Vous pouvez consulter un exemple d’utilisation dans le code source de zope.component lui-même. Le fichier
__init__.py possède celle instruction
4.9. Reference
143
Plone pour les développeurs, Version 1.0.0
moduleProvides(IComponentArchitecture,
IComponentRegistrationConvenience)
Ainsi, le paquet zope.component fournit deux interfaces : IComponentArchitecture et IComponentRegistrationConvenience.
4.9.32 noLongerProvides
Retire une interface de la liste des interfaces directement fournies par un objet.
– Emplacement : zope.interface
– Signature : noLongerProvides(object, interface)
Exemple
>>>
>>>
>>>
>>>
from
from
from
from
zope.interface
zope.interface
zope.interface
zope.interface
import
import
import
import
Attribute
Interface
implements
classImplements
>>> class IPerson(Interface):
...
...
name = Attribute("Name of person")
>>> class IStudent(Interface):
...
...
college = Attribute("Name of college")
>>> class Person(object):
...
...
implements(IPerson)
...
name = u""
>>>
>>>
>>>
>>>
jack = Person()
jack.name = "Jack"
jack.college = "New College"
directlyProvides(jack, IStudent)
You can test it like this:
>>> from zope.interface import providedBy
>>> IPerson in providedBy(jack)
True
>>> IStudent in providedBy(jack)
True
>>> from zope.interface import noLongerProvides
>>> noLongerProvides(jack, IStudent)
>>> IPerson in providedBy(jack)
True
>>> IStudent in providedBy(jack)
False
4.9.33 provideAdapter
Il est recommandé d’utilier registerAdapter .
4.9.34 provideHandler
Il est recommandé d’utiliser registerHandler .
144
Chapitre 4. Le guide complet de l’Architecture de Composants de Zope
Plone pour les développeurs, Version 1.0.0
4.9.35 provideSubscriptionAdapter
Il est recommandé d’utiliser registerSubscriptionAdapter .
4.9.36 provideUtility
Il est recommandé d’utiliser registerUtility .
4.9.37 providedBy
Teste si l’interface est implémentée par l’objet. Renvoie True si l’objet affirme qu’il implémente l’interface, y
compris les interfaces étendues.
– Emplacement : zope.interface
– Signature : providedBy(object)
Exemple 1
>>> from zope.interface import Attribute
>>> from zope.interface import Interface
>>> from zope.interface import implements
>>> class IPerson(Interface):
...
...
name = Attribute("Name of person")
>>> class Person(object):
...
...
implements(IPerson)
...
name = u""
>>> jack = Person()
>>> jack.name = "Jack"
You can test it like this:
>>> from zope.interface import providedBy
>>> IPerson in providedBy(jack)
True
Exemple 2
>>> from zope.interface import Attribute
>>> from zope.interface import Interface
>>> from zope.interface import implements
>>> class IPerson(Interface):
...
name = Attribute("Name of person")
>>> class ISpecial(Interface):
...
pass
>>> class Person(object):
...
implements(IPerson)
...
name = u""
>>>
>>>
>>>
>>>
>>>
from zope.interface import classImplements
classImplements(Person, ISpecial)
from zope.interface import providedBy
jack = Person()
jack.name = "Jack"
4.9. Reference
145
Plone pour les développeurs, Version 1.0.0
Pour obtenir une liste de toutes les interfaces fournies par cet objet ::
>>> [x.__name__ for x in providedBy(jack)]
[’IPerson’, ’ISpecial’]
4.9.38 queryAdapter
Recherche pour un objet un adaptateur nommé vers une interface. Renvoie un adaptateur qui peut s’adapter à un
objet et fournir une interface. Si aucun adaptateur ne peut être trouvé, la valeur par défaut est renvoyée.
– Emplacement : zope.component
– Signature : queryAdapter(object, interface=Interface, name=u’‘, default=None, context=None)
Exemple
>>> from zope.interface import Attribute
>>> from zope.interface import Interface
>>> class IDesk(Interface):
...
"""A frontdesk will register object’s details"""
...
...
def register():
...
"""Register object’s details"""
...
>>> from zope.interface import implements
>>> from zope.component import adapts
>>> class FrontDeskNG(object):
...
...
implements(IDesk)
...
adapts(IGuest)
...
...
def __init__(self, guest):
...
self.guest = guest
...
...
def register(self):
...
next_id = get_next_id()
...
bookings_db[next_id] = {
...
’name’: guest.name,
...
’place’: guest.place,
...
’phone’: guest.phone
...
}
>>> class Guest(object):
...
...
implements(IGuest)
...
...
def __init__(self, name, place):
...
self.name = name
...
self.place = place
>>> jack = Guest("Jack", "Bangalore")
>>> jack_frontdesk = FrontDeskNG(jack)
>>> IDesk.providedBy(jack_frontdesk)
True
>>> from zope.component import getGlobalSiteManager
>>> gsm = getGlobalSiteManager()
>>> gsm.registerAdapter(FrontDeskNG,
...
(IGuest,), IDesk, ’ng’)
146
Chapitre 4. Le guide complet de l’Architecture de Composants de Zope
Plone pour les développeurs, Version 1.0.0
>>> queryAdapter(jack, IDesk, ’ng’)
<FrontDeskNG object at ...>
4.9.39 queryAdapterInContext
Recherche pour un objet un adaptateur spécial vers une interface.
NOTE : cette méthode ne doit être utilisée que si un contexte personnalisé doit être fourni lors d’une recherche de
composant personnalisée. Sinon, appelez l’interface comme ci-dessous
interface(object, default)
Renvoie un adaptateur qui peut s’adapter à l’objet et fournir l’interface. Si aucun adaptateur n’est fourni, la valeur
par défaut est renvoyée.
Le contexte est adapté à IServiceService, et ce service d’adaptateurs, fourni par l’adaptateur, est utilisé.
Si l’objet possède une méthode __conform__, cette méthode est appelée avec l’interface demandée. Si la méthode
renvoie une valeur différente de None, cette valeur est retournée. Sinon, si l’objet implémente déjà l’interface, cet
objet est renvoyé.
– Emplacement : zope.component
– Signature : queryAdapterInContext(object, interface, context, default=None)
– Voir aussi : getAdapterInContext
Exemple
>>> from zope.component.globalregistry import BaseGlobalComponents
>>> from zope.component import IComponentLookup
>>> sm = BaseGlobalComponents()
>>> class Context(object):
...
def __init__(self, sm):
...
self.sm = sm
...
def __conform__(self, interface):
...
if interface.isOrExtends(IComponentLookup):
...
return self.sm
>>> context = Context(sm)
>>> from zope.interface import Attribute
>>> from zope.interface import Interface
>>> class IDesk(Interface):
...
"""A frontdesk will register object’s details"""
...
...
def register():
...
"""Register object’s details"""
...
>>> from zope.interface import implements
>>> from zope.component import adapts
>>> class FrontDeskNG(object):
...
...
implements(IDesk)
...
adapts(IGuest)
...
...
def __init__(self, guest):
...
self.guest = guest
...
...
def register(self):
...
next_id = get_next_id()
...
bookings_db[next_id] = {
4.9. Reference
147
Plone pour les développeurs, Version 1.0.0
...
...
...
...
’name’: guest.name,
’place’: guest.place,
’phone’: guest.phone
}
>>> class Guest(object):
...
...
implements(IGuest)
...
...
def __init__(self, name, place):
...
self.name = name
...
self.place = place
>>> jack = Guest("Jack", "Bangalore")
>>> jack_frontdesk = FrontDeskNG(jack)
>>> IDesk.providedBy(jack_frontdesk)
True
>>> from zope.component import getGlobalSiteManager
>>> gsm = getGlobalSiteManager()
>>> sm.registerAdapter(FrontDeskNG,
...
(IGuest,), IDesk)
>>> from zope.component import queryAdapterInContext
>>> queryAdapterInContext(jack, IDesk, sm)
<FrontDeskNG object at ...>
4.9.40 queryMultiAdapter
Recherche pour des objets un multi-adaptateur vers une interface. Cette fonction renvoie un multi-adaptateur qui
peut adapter les objets à l’interface. Si aucun adaptateur ne peut être trouvé, la valeur par défaut est renvoyée. Le
nom correspondant à la chaîne vide est réservé aux adaptateurs sans nom. Les methodes des adaptateurs sans nom
appellent souvent les méthodes des adaptateurs nommés avec un nom vide (‘’).
– Emplacement : zope.component
– Signature : queryMultiAdapter(objects, interface=Interface, name=u’‘, default=None, context=None)
– Voir aussi : getMultiAdapter
Exemple
>>> from zope.interface import Interface
>>> from zope.interface import implements
>>> from zope.component import adapts
>>> class IAdapteeOne(Interface):
...
pass
>>> class IAdapteeTwo(Interface):
...
pass
>>> class IFunctionality(Interface):
...
pass
>>> class MyFunctionality(object):
...
implements(IFunctionality)
...
adapts(IAdapteeOne, IAdapteeTwo)
...
...
def __init__(self, one, two):
...
self.one = one
...
self.two = two
148
Chapitre 4. Le guide complet de l’Architecture de Composants de Zope
Plone pour les développeurs, Version 1.0.0
>>> from zope.component import getGlobalSiteManager
>>> gsm = getGlobalSiteManager()
>>> gsm.registerAdapter(MyFunctionality)
>>> class One(object):
...
implements(IAdapteeOne)
>>> class Two(object):
...
implements(IAdapteeTwo)
>>> one = One()
>>> two = Two()
>>> from zope.component import queryMultiAdapter
>>> getMultiAdapter((one,two), IFunctionality)
<MyFunctionality object at ...>
>>> myfunctionality = queryMultiAdapter((one,two), IFunctionality)
>>> myfunctionality.one
<One object at ...>
>>> myfunctionality.two
<Two object at ...>
4.9.41 queryUtility
Cette fonction est utilisée pour rechercher un utilitaire qui fournit une interface. Si aucun n’est trouvé, la valeur
par défaut est renvoyée.
– Emplacement : zope.component
– Signature : queryUtility(interface, name=’‘, default=None)
Exemple
>>> from zope.interface import Interface
>>> from zope.interface import implements
>>> class IGreeter(Interface):
...
def greet(name):
...
"say hello"
>>> class Greeter(object):
...
...
implements(IGreeter)
...
...
def greet(self, name):
...
return "Hello " + name
>>> from zope.component import getGlobalSiteManager
>>> gsm = getGlobalSiteManager()
>>> greet = Greeter()
>>> gsm.registerUtility(greet, IGreeter)
>>> from zope.component import queryUtility
>>> queryUtility(IGreeter).greet(’Jack’)
’Hello Jack’
4.9. Reference
149
Plone pour les développeurs, Version 1.0.0
4.9.42 registerAdapter
Cette fonction est utilisée pour inscrire une fabrique d’adaptateur.
– Emplacement : zope.component - IComponentRegistry
– Signature : registerAdapter(factory, required=None, provided=None, name=u’‘, info=u’‘)
– Voir aussi : unregisterAdapter
Exemple
>>> from zope.interface import Attribute
>>> from zope.interface import Interface
>>> class IDesk(Interface):
...
"""A frontdesk will register object’s details"""
...
...
def register():
...
"""Register object’s details"""
...
>>> from zope.interface import implements
>>> from zope.component import adapts
>>> class FrontDeskNG(object):
...
...
implements(IDesk)
...
adapts(IGuest)
...
...
def __init__(self, guest):
...
self.guest = guest
...
...
def register(self):
...
next_id = get_next_id()
...
bookings_db[next_id] = {
...
’name’: guest.name,
...
’place’: guest.place,
...
’phone’: guest.phone
...
}
>>> class Guest(object):
...
...
implements(IGuest)
...
...
def __init__(self, name, place):
...
self.name = name
...
self.place = place
>>> jack = Guest("Jack", "Bangalore")
>>> jack_frontdesk = FrontDeskNG(jack)
>>> IDesk.providedBy(jack_frontdesk)
True
>>> from zope.component import getGlobalSiteManager
>>> gsm = getGlobalSiteManager()
>>> gsm.registerAdapter(FrontDeskNG,
...
(IGuest,), IDesk, ’ng’)
Vous pouvez tester de cette façon :
>>> queryAdapter(jack, IDesk, ’ng’)
<FrontDeskNG object at ...>
150
Chapitre 4. Le guide complet de l’Architecture de Composants de Zope
Plone pour les développeurs, Version 1.0.0
4.9.43 registeredAdapters
Renvoie un objet iterable listant les IAdapterRegistrations. Ces inscriptions décrivent les inscriptions d’adaptateurs
actuelles valables.
– Emplacement : zope.component - IComponentRegistry
– Signature : registeredAdapters()
Exemple
>>> from zope.interface import Attribute
>>> from zope.interface import Interface
>>> class IDesk(Interface):
...
"""A frontdesk will register object’s details"""
...
...
def register():
...
"""Register object’s details"""
...
>>> from zope.interface import implements
>>> from zope.component import adapts
>>> class FrontDeskNG(object):
...
...
implements(IDesk)
...
adapts(IGuest)
...
...
def __init__(self, guest):
...
self.guest = guest
...
...
def register(self):
...
next_id = get_next_id()
...
bookings_db[next_id] = {
...
’name’: guest.name,
...
’place’: guest.place,
...
’phone’: guest.phone
...
}
>>> class Guest(object):
...
...
implements(IGuest)
...
...
def __init__(self, name, place):
...
self.name = name
...
self.place = place
>>> jack = Guest("Jack", "Bangalore")
>>> jack_frontdesk = FrontDeskNG(jack)
>>> IDesk.providedBy(jack_frontdesk)
True
>>> from zope.component import getGlobalSiteManager
>>> gsm = getGlobalSiteManager()
>>> gsm.registerAdapter(FrontDeskNG,
...
(IGuest,), IDesk, ’ng2’)
>>> reg_adapter = list(gsm.registeredAdapters())
>>> ’ng2’ in [x.name for x in reg_adapter]
True
4.9. Reference
151
Plone pour les développeurs, Version 1.0.0
4.9.44 registeredHandlers
Renvoie un objet iterable listant les IHandlerRegistrations. Ces inscriptions décrivent les inscriptions de gestionnaires actuelles.
– Emplacement : zope.component - IComponentRegistry
– Signature : registeredHandlers()
Exemple
>>> import datetime
>>> def documentCreated(event):
...
event.doc.created = datetime.datetime.utcnow()
>>> from zope.interface import Interface
>>> from zope.interface import Attribute
>>> from zope.interface import implements
>>> class IDocumentCreated(Interface):
...
doc = Attribute("The document that was created")
>>> class DocumentCreated(object):
...
implements(IDocumentCreated)
...
...
def __init__(self, doc):
...
self.doc = doc
>>> def documentCreated(event):
...
event.doc.created = datetime.datetime.utcnow()
>>> from zope.component import adapter
>>> @adapter(IDocumentCreated)
... def documentCreated(event):
...
event.doc.created = datetime.datetime.utcnow()
>>> from zope.component import getGlobalSiteManager
>>> gsm = getGlobalSiteManager()
>>> gsm.registerHandler(documentCreated, info=’ng3’)
>>> reg_adapter = list(gsm.registeredHandlers())
>>> ’ng3’ in [x.info for x in reg_adapter]
True
>>> gsm.registerHandler(documentCreated, name=’ng4’)
Traceback (most recent call last):
...
TypeError: Named handlers are not yet supported
4.9.45 registeredSubscriptionAdapters
Renvoie un objet iterable listant les ISubscriptionAdapterRegistrations. Ces inscriptions décrivent les inscriptions
d’adaptateurs d’abonnement actuelles.
– Emplacement : zope.component - IComponentRegistry
– Signature : registeredSubscriptionAdapters()
Exemple
152
Chapitre 4. Le guide complet de l’Architecture de Composants de Zope
Plone pour les développeurs, Version 1.0.0
>>> from zope.interface import Interface
>>> from zope.interface import Attribute
>>> from zope.interface import implements
>>> class IValidate(Interface):
...
def validate(ob):
...
"""Determine whether the object is valid
...
...
Return a string describing a validation problem.
...
An empty string is returned to indicate that the
...
object is valid.
...
"""
>>> class IDocument(Interface):
...
summary = Attribute("Document summary")
...
body = Attribute("Document text")
>>> class Document(object):
...
implements(IDocument)
...
def __init__(self, summary, body):
...
self.summary, self.body = summary, body
>>> from zope.component import adapts
>>> class AdequateLength(object):
...
...
adapts(IDocument)
...
implements(IValidate)
...
...
def __init__(self, doc):
...
self.doc = doc
...
...
def validate(self):
...
if len(self.doc.body) < 1000:
...
return ’too short’
...
else:
...
return ’’
>>> from zope.component import getGlobalSiteManager
>>> gsm = getGlobalSiteManager()
>>> gsm.registerSubscriptionAdapter(AdequateLength, info=’ng4’)
>>> reg_adapter = list(gsm.registeredSubscriptionAdapters())
>>> ’ng4’ in [x.info for x in reg_adapter]
True
4.9.46 registeredUtilities
Cette fonction renvoie un objet iterable listant les IUtilityRegistrations. Ces inscriptions décrivent les inscriptions
d’utilitaires actuelles.
– Emplacement : zope.component - IComponentRegistry
– Signature : registeredUtilities()
Exemple
>>> from zope.interface import Interface
>>> from zope.interface import implements
>>> class IGreeter(Interface):
...
def greet(name):
...
"say hello"
4.9. Reference
153
Plone pour les développeurs, Version 1.0.0
>>> class Greeter(object):
...
...
implements(IGreeter)
...
...
def greet(self, name):
...
print "Hello", name
>>> from zope.component import getGlobalSiteManager
>>> gsm = getGlobalSiteManager()
>>> greet = Greeter()
>>> gsm.registerUtility(greet, info=’ng5’)
>>> reg_adapter = list(gsm.registeredUtilities())
>>> ’ng5’ in [x.info for x in reg_adapter]
True
4.9.47 registerHandler
Cette fonction est utilisée pour inscrire un gestionnaire. Un gestionnaire est un abonné qui ne crée pas d’adaptateur,
mais effectue un traitement lorsqu’il est appelé.
– Emplacement : zope.component - IComponentRegistry
– Signature : registerHandler(handler, required=None, name=u’‘, info=’‘)
– Voir aussi : unregisterHandler
Note : l’implémentation actuelle de zope.component ne prend pas en charge l’attribut name.
Exemple
>>> import datetime
>>> def documentCreated(event):
...
event.doc.created = datetime.datetime.utcnow()
>>> from zope.interface import Interface
>>> from zope.interface import Attribute
>>> from zope.interface import implements
>>> class IDocumentCreated(Interface):
...
doc = Attribute("The document that was created")
>>> class DocumentCreated(object):
...
implements(IDocumentCreated)
...
...
def __init__(self, doc):
...
self.doc = doc
>>> def documentCreated(event):
...
event.doc.created = datetime.datetime.utcnow()
>>> from zope.component import adapter
>>> @adapter(IDocumentCreated)
... def documentCreated(event):
...
event.doc.created = datetime.datetime.utcnow()
>>> from zope.component import getGlobalSiteManager
>>> gsm = getGlobalSiteManager()
>>> gsm.registerHandler(documentCreated)
154
Chapitre 4. Le guide complet de l’Architecture de Composants de Zope
Plone pour les développeurs, Version 1.0.0
>>> from zope.component import handle
>>> handle(DocumentCreated(doc))
>>> doc.created.__class__.__name__
’datetime’
4.9.48 registerSubscriptionAdapter
Cette fonction est utilisée pour inscrire une fabrique d’abonnés.
– Emplacement : zope.component - IComponentRegistry
– Signature : registerSubscriptionAdapter(factory, required=None, provides=None, name=u’‘, info=’‘)
– Voir aussi : unregisterSubscriptionAdapter
Exemple
>>> from zope.interface import Interface
>>> from zope.interface import Attribute
>>> from zope.interface import implements
>>> class IValidate(Interface):
...
def validate(ob):
...
"""Determine whether the object is valid
...
...
Return a string describing a validation problem.
...
An empty string is returned to indicate that the
...
object is valid.
...
"""
>>> class IDocument(Interface):
...
summary = Attribute("Document summary")
...
body = Attribute("Document text")
>>> class Document(object):
...
implements(IDocument)
...
def __init__(self, summary, body):
...
self.summary, self.body = summary, body
>>> from zope.component import adapts
>>> class AdequateLength(object):
...
...
adapts(IDocument)
...
implements(IValidate)
...
...
def __init__(self, doc):
...
self.doc = doc
...
...
def validate(self):
...
if len(self.doc.body) < 1000:
...
return ’too short’
...
else:
...
return ’’
>>> from zope.component import getGlobalSiteManager
>>> gsm = getGlobalSiteManager()
>>> gsm.registerSubscriptionAdapter(AdequateLength)
4.9. Reference
155
Plone pour les développeurs, Version 1.0.0
4.9.49 registerUtility
Cette fonction est utilisée pour inscrire un utilitaire.
– Emplacement : zope.component - IComponentRegistry
– Signature : registerUtility(component, provided=None, name=u’‘, info=u’‘)
– Voir aussi : unregisterUtility
Exemple
>>> from zope.interface import Interface
>>> from zope.interface import implements
>>> class IGreeter(Interface):
...
def greet(name):
...
"say hello"
>>> class Greeter(object):
...
...
implements(IGreeter)
...
...
def greet(self, name):
...
print "Hello", name
>>> from zope.component import getGlobalSiteManager
>>> gsm = getGlobalSiteManager()
>>> greet = Greeter()
>>> gsm.registerUtility(greet)
4.9.50 subscribers
Cette fonction est utilisée pour récupérer les abonnés. Les abonnés qui fournissent l’interface souhaités et qui
dépendent de la séquence d’objets fournie sont renvoyés.
– Emplacement : zope.component - IComponentRegistry
– Signature : subscribers(required, provided, context=None)
Exemple
>>> from zope.interface import Interface
>>> from zope.interface import Attribute
>>> from zope.interface import implements
>>> class IValidate(Interface):
...
def validate(ob):
...
"""Determine whether the object is valid
...
...
Return a string describing a validation problem.
...
An empty string is returned to indicate that the
...
object is valid.
...
"""
>>> class IDocument(Interface):
...
summary = Attribute("Document summary")
...
body = Attribute("Document text")
>>> class Document(object):
...
implements(IDocument)
...
def __init__(self, summary, body):
...
self.summary, self.body = summary, body
>>> from zope.component import adapts
>>> class SingleLineSummary:
156
Chapitre 4. Le guide complet de l’Architecture de Composants de Zope
Plone pour les développeurs, Version 1.0.0
...
...
...
...
...
...
...
...
...
...
...
adapts(IDocument)
implements(IValidate)
def __init__(self, doc):
self.doc = doc
def validate(self):
if ’\n’ in self.doc.summary:
return ’Summary should only have one line’
else:
return ’’
>>> class AdequateLength(object):
...
adapts(IDocument)
...
implements(IValidate)
...
...
def __init__(self, doc):
...
self.doc = doc
...
...
def validate(self):
...
if len(self.doc.body) < 1000:
...
return ’too short’
...
else:
...
return ’’
>>> from zope.component import getGlobalSiteManager
>>> gsm = getGlobalSiteManager()
>>> gsm.registerSubscriptionAdapter(SingleLineSummary)
>>> gsm.registerSubscriptionAdapter(AdequateLength)
>>> from zope.component import subscribers
>>> doc = Document("A\nDocument", "blah")
>>> [adapter.validate()
... for adapter in subscribers([doc], IValidate)
... if adapter.validate()]
[’Summary should only have one line’, ’too short’]
>>> doc = Document("A\nDocument", "blah" * 1000)
>>> [adapter.validate()
... for adapter in subscribers([doc], IValidate)
... if adapter.validate()]
[’Summary should only have one line’]
>>> doc = Document("A Document", "blah")
>>> [adapter.validate()
... for adapter in subscribers([doc], IValidate)
... if adapter.validate()]
[’too short’]
4.9.51 unregisterAdapter
Cette fonction est utilisée pour désinscrire une fabrique d’adaptateur. Un booléen est renvoyé, indiquant si le
registre a été modifié. Si le composant donné est None et qu’il n’y a aucun composant inscrit, ou si le composant
donné n’est pas None mais n’est pas inscrit, alors la fonction renvoie False. Sinon True.
– Emplacement : zope.component - IComponentRegistry
– Signature : unregisterAdapter(factory=None, required=None, provided=None, name=u’‘)
– Voir aussi : registerAdapter
Exemple
4.9. Reference
157
Plone pour les développeurs, Version 1.0.0
>>> from zope.interface import Attribute
>>> from zope.interface import Interface
>>> class IDesk(Interface):
...
"""A frontdesk will register object’s details"""
...
...
def register():
...
"""Register object’s details"""
...
>>> from zope.interface import implements
>>> from zope.component import adapts
>>> class FrontDeskNG(object):
...
...
implements(IDesk)
...
adapts(IGuest)
...
...
def __init__(self, guest):
...
self.guest = guest
...
...
def register(self):
...
next_id = get_next_id()
...
bookings_db[next_id] = {
...
’name’: guest.name,
...
’place’: guest.place,
...
’phone’: guest.phone
...
}
>>> class Guest(object):
...
...
implements(IGuest)
...
...
def __init__(self, name, place):
...
self.name = name
...
self.place = place
>>> jack = Guest("Jack", "Bangalore")
>>> jack_frontdesk = FrontDeskNG(jack)
>>> IDesk.providedBy(jack_frontdesk)
True
>>> from zope.component import getGlobalSiteManager
>>> gsm = getGlobalSiteManager()
>>> gsm.registerAdapter(FrontDeskNG,
...
(IGuest,), IDesk, ’ng6’)
Vous pouvez tester de cette façon :
>>> queryAdapter(jack, IDesk, ’ng6’)
<FrontDeskNG object at ...>
Maintenant on peut faire la désinscriptin :
>>> gsm.unregisterAdapter(FrontDeskNG, name=’ng6’)
True
Après la désinscription :
>>> print queryAdapter(jack, IDesk, ’ng6’)
None
158
Chapitre 4. Le guide complet de l’Architecture de Composants de Zope
Plone pour les développeurs, Version 1.0.0
4.9.52 unregisterHandler
Cette fonction est utilisée pour désinscrire un gestionnaire. Un gestionnaire est un abonné qui ne crée pas d’adaptateur, mais effectue un traitement lors de son appel. Un booléen et renvoyé, indiquant si le registre a été modifié.
– Emplacement : zope.component - IComponentRegistry
– Signature : unregisterHandler(handler=None, required=None, name=u’‘)
– Voir aussi : registerHandler
Exemple
>>> from zope.interface import Interface
>>> from zope.interface import Attribute
>>> from zope.interface import implements
>>> class IDocument(Interface):
...
...
summary = Attribute("Document summary")
...
body = Attribute("Document text")
>>> class Document(object):
...
...
implements(IDocument)
...
def __init__(self, summary, body):
...
self.summary, self.body = summary, body
>>> doc = Document("A\nDocument", "blah")
>>> class IDocumentAccessed(Interface):
...
doc = Attribute("The document that was accessed")
>>> class DocumentAccessed(object):
...
implements(IDocumentAccessed)
...
...
def __init__(self, doc):
...
self.doc = doc
...
self.doc.count = 0
>>> from zope.component import adapter
>>> @adapter(IDocumentAccessed)
... def documentAccessed(event):
...
event.doc.count = event.doc.count + 1
>>> from zope.component import getGlobalSiteManager
>>> gsm = getGlobalSiteManager()
>>> gsm.registerHandler(documentAccessed)
>>> from zope.component import handle
>>> handle(DocumentAccessed(doc))
>>> doc.count
1
Now unregister:
>>> gsm.unregisterHandler(documentAccessed)
True
After unregistration:
>>> handle(DocumentAccessed(doc))
>>> doc.count
0
4.9. Reference
159
Plone pour les développeurs, Version 1.0.0
4.9.53 unregisterSubscriptionAdapter
Cette fonction est utilisée pour désinscrire une fabrique d’abonné. Un booléen est renvoyé, indiquant si le registre
a été modifié. Si le composant donné est None et qu’il n’y a aucun composant inscrit, ou si le composant donné
n’est pas None mais n’est pas inscrit, alors la fonction renvoie False. Sinon True.
– Emplacement : zope.component - IComponentRegistry
– Signature : unregisterSubscriptionAdapter(factory=None, required=None, provides=None, name=u’‘)
– Voir aussi : registerSubscriptionAdapter
Exemple
>>> from zope.interface import Interface
>>> from zope.interface import Attribute
>>> from zope.interface import implements
>>> class IValidate(Interface):
...
def validate(ob):
...
"""Determine whether the object is valid
...
...
Return a string describing a validation problem.
...
An empty string is returned to indicate that the
...
object is valid.
...
"""
>>> class IDocument(Interface):
...
summary = Attribute("Document summary")
...
body = Attribute("Document text")
>>> class Document(object):
...
implements(IDocument)
...
def __init__(self, summary, body):
...
self.summary, self.body = summary, body
>>> from zope.component import adapts
>>> class AdequateLength(object):
...
...
adapts(IDocument)
...
implements(IValidate)
...
...
def __init__(self, doc):
...
self.doc = doc
...
...
def validate(self):
...
if len(self.doc.body) < 1000:
...
return ’too short’
...
else:
...
return ’’
>>> from zope.component import getGlobalSiteManager
>>> gsm = getGlobalSiteManager()
>>> gsm.registerSubscriptionAdapter(AdequateLength)
>>> from zope.component import subscribers
>>> doc = Document("A\nDocument", "blah")
>>> [adapter.validate()
... for adapter in subscribers([doc], IValidate)
... if adapter.validate()]
[’too short’]
Mainenant on peut effectuer la désinscription :
160
Chapitre 4. Le guide complet de l’Architecture de Composants de Zope
Plone pour les développeurs, Version 1.0.0
>>> gsm.unregisterSubscriptionAdapter(AdequateLength)
True
Après la désinscription :
>>> [adapter.validate()
... for adapter in subscribers([doc], IValidate)
... if adapter.validate()]
[]
4.9.54 unregisterUtility
Cette fonction est utilisée pour désinscrire un utilitaire. Un booléen est renvoyé, indiquant si le registre a été
modifié. Si le composant donné est None et qu’il n’y a aucun composant inscrit, ou si le composant donné n’est
pas None mais n’est pas inscrit, alors la fonction renvoie False. Sinon True.
– Location : zope.component - IComponentRegistry
– Signature : unregisterUtility(component=None, provided=None, name=u’‘)
– See also : registerUtility
Exemple
>>> from zope.interface import Interface
>>> from zope.interface import implements
>>> class IGreeter(Interface):
...
def greet(name):
...
"say hello"
>>> class Greeter(object):
...
...
implements(IGreeter)
...
...
def greet(self, name):
...
return "Hello " + name
>>> from zope.component import getGlobalSiteManager
>>> gsm = getGlobalSiteManager()
>>> greet = Greeter()
>>> gsm.registerUtility(greet)
>>> queryUtility(IGreeter).greet(’Jack’)
’Hello Jack’
Maintenant on peut effectuer la désinscription :
>>> gsm.unregisterUtility(greet)
True
Après la désinscription :
>>> print queryUtility(IGreeter)
None
4.9. Reference
161
Plone pour les développeurs, Version 1.0.0
162
Chapitre 4. Le guide complet de l’Architecture de Composants de Zope
CHAPITRE 5
Le langage ZCML
Author Gilles Lenfant
Version 1.0.0
En explorant le code de Plone, vous avez rencontré de nombreux fichiers *.zcml. En vous demandant à quoi
celà peut bien servir.
Le ZCML ou Zope Configuration Markup Language est un langage basé sur XML qui a été élaboré pour permettre
de déclarer les liaisons entre les différents éléments logiciels utilisant la ZCA de votre application.
Relisez-donc si nécessaire Le guide complet de l’Architecture de Composants de Zope pour revoir ce qu’est un
adaptateur ou un utilitaire.
Important : La lecture de ce chapitre nécessite la connaissance de base du langage XML, et notamment des
notions d’élément, d’attribut, et de namespace. Si ces termes vous semblent abscons, je vous invite à passer une
heure sur un tutoriel XML.
5.1 Introduction au ZCML
5.1.1 Pourquoi le ZCML ?
L’utilisation du ZCML, comme indiqué plus haut, est une façon déclarative de configurer les services d’un composant que l’on pourrait écrire en Python.
Par exemple, la déclaration suivante :
<class class="foo.FooClass">
<implements interface="bar.interfaces.ISomething" />
</class>
Revient à faire la déclaration suivante en Python :
from zope.interface import classImplements
import foo
import bar.interfaces
classImplements(foo.FooClass, bar.interfaces.ISomething)
Quasiment toutes les déclarations ZCML ont leur équivalent en pur Python. Alors, me direz-vous, pourquoi donc
inventer un langage supplémentaire pour faire ce qu’on peut faire en Python ?
Plusieurs raisons à cela :
163
Plone pour les développeurs, Version 1.0.0
– La ZCA a été conçue pour faciliter la collaboration entre les composants qui n’ont pas forcément été conçus
pour fonctionner de concert.
– Il est fastidieux d’explorer le code Python d’une application complexe comme Plone pour prendre connaissance des interfaces d’adaptation disponibles, les vues de contenus. L’exploration des directives ZCML d’un
composant de Plone ou de tierce partie vous permet d’identifier plus rapidement les aspects que vous pourrez
personnaliser.
– Il est plus facile à un intégrateur peu au fait des subtilités de la programmation en Python de personnaliser une
application en ajoutant ses propres directives ZCML dans ses composants de type policy product.
5.1.2 Forme générale d’un fichier ZCML
Comme
dit
plus
haut,
un
fichier
http://namespaces.zope.org/zope
ZCML
est
un
fichier
XML
du
namespace
Le plus petit fichier ZCML possible est :
1
2
3
4
<configure
xmlns="http://namespaces.zope.org/zope"
i18n_domain="mon.composant"
/>
5
6
<!-- Vos directives -->
7
8
</configure>
Quelques commentaires :
– Ligne 1 : l’élément principal est toujours l’élément <configure>
– Ligne 2 : représente le namespace par défaut s’un fichier ZCML. Nous verrons par la suite qu’un fichier ZCML
peut nécessiter plusieurs namespaces, généralement autant de namespaces que de domaines de configuration.
– Ligne 3 : indique le domaine de traduction utilisé. En effet, certains attributs d’une directive ZCML peuvent
être traduits, tel parfois l’attribut title lorsqu’ils sont susceptibles de participer à l’interface utilisateur. Voir
à ce sujet le chapitre Internationalisation d’un composant pour plus d’informations.
– Au delà de la ligne 6, à vous de placer vos propres directives. Celles-ci seront enregistrées lors de leur traitement
dans l’ordre de leur apparition.
Important : L’ordre d’exécution effectif de l’effet produit par les directives ZCML n’est pas forcément l’ordre
d’apparition de la directive lors du traitement de celles-ci. Certaines directives pour lesquelles l’ordre d’exécution
peut revêtir une importance disposent généralement d’un attribut permettant au développeur de contrôler sa priorité
ou ordre d’exécution.
5.2 Traitement des fichiers ZCML
5.2.1 Quand les fichiers ZCML sont-ils traîtés ?
Les fichiers ZCML sont traîtés uniquement lors du démarrage de votre instance Zope, et le plus tard possible avant
le traitement de la première requête par le publisher.
Ce traitement commence par lire le fichier $INSTANCE_HOME/etc/site.zcml. Comme les commentaires
de ce fichier nous l’indiquent, celui-ci commence par traiter le package Products.Five, puis traite dans cet
ordre :
– Les fichiers meta.zcml. Ces fichiers spéciaux, sur lesquels nous reviendrons par la suite, sont dédiés aux
définitions des vocabulaires ZCML (namespaces, éléments et attributs) définis par certains composants de Zope
et Plone, voire certains composants de tierce partie tels iw.fss, collective.monkeypatcher.
– Les fichiers configure.zcml. Ces fichiers, les plus courants, utilisent les services et éléments définis dans
les divers fichiers meta.zcml pour configurer et intégrer les éléments logiciels dans les composants dans
lesquels ils figurent.
164
Chapitre 5. Le langage ZCML
Plone pour les développeurs, Version 1.0.0
– Les fichier overrides.zcml. Ces fichiers permettent de remplacer une ou plusieurs directives figurant dans
un fichier configure.zcml. Par exemple, si vous voulez dans votre policy product changer la vue par défaut
d’un contenu définie dans un composant de tierce partie par une vie personnalisée, ou modifier une viewlet
standard, la façon la plus simple de procéder est de fournir ceci dans un fichier overrides.zcml.
Attention : Pour ceux qui connaissent déjà Zope 3 / Bluebream, les fichiers ftesting.zcml ne sont pas
pris en compte par Zope 2.
5.2.2 Comment déclarer son ou ses fichiers ZCML ?
Comme nous l’avons vu plus haut, Zope, lors du démarrage, ne s’intéresse qu’au seul fichier
$INSTANCE_HOME/etc/site.zcml. Le langage ZCML inclut dans son namespace standard l’élément
<include> qui permet d’inclure - comme son nom l’indique - les directives ZCML d’un fichier ou d’un package 1 .
Pour que les directives des fichiers configure.zcml, et éventuellement overrides.zcml ou plus rarement
meta.zcml soient traitées au démarrage de votre instance, encore faut-il que ces fichiers soient référencés dans
ceux appellés depuis $INSTANCE_HOME/etc/site.zcml.
C’est fastidieux. Heureusement, les développeurs du recipe plone.recipe.zope2instance que vous utilisez forcément pour configurer votre déploiement avec buildout vous permettent de créer automatiquement une jonction
entre le fichier $INSTANCE_HOME/etc/site.zcml et vos configure.zcml, overrides.zcml et
meta.zcml. Dans la mesure bien sûr et comme recommandé, où vous placez ces fichiers dans le répertoire
principal de votre package.
Exemple :
...
[instance]
recipe = plone.recipe.zope2instance
...
zcml =
mon.composant
mon.composant-overrides
nom.composant-meta
Comme vous l’aurez certainement compris, la ligne mon.composant va permettre de traiter le fichier
configure.zcml de ce package Python, mon.composant-overrides va permettre de traiter son fichier
overrides.zcml et nom.composant-meta va permettre de traiter son fichier meta.zcml lors du prochain démarrage de votre instance.
Attention : Dans l’exemple ci-avant, mon.composant est le nom du package Python qui inclut les fichiers
*.zcml. Et non le nom de l’egg qui peut être différent dans certains cas, heureusement rares.
Mieux, depuis Plone 3.2, plus aucune mentions aux éventuels fichiers *.zcml de vos composants depuis l’adoption en standard du composant z3C.autoinclude. Sans entrer dans les détails de fonctionnement de ce composant,
il vous suffit de placer les lignes suivantes dans l’appel de la fonction setup, dans le fichier setup.py de votre
composant :
# ...
def setup(
# ...
entry_points="""
# -*- Entry points: -*[z3c.autoinclude.plugin]
target = plone
"""
# ...
)
1. Nous reviendrons plus en détail sur cette directive dans la référence ZCML en fin de ce chapitre.
5.2. Traitement des fichiers ZCML
165
Plone pour les développeurs, Version 1.0.0
À partir de ce moment, et dans la mesure où votre composant est une extension pour Plone, les intégrateurs de votre composant n’auront plus besoin de déclarer celui-ci dans la liste zcml du recipe
plone.recipe.zope2instance.
5.3 Les directives ZCML
Comme vu plus haut, les différentes directives ZCML sont rendues disponibles par divers composants de Zope
et Plone qui exposent leurs propres namespaces XML propres aux fonctionnalités que les dits composants fournissent.
5.3.1 Les namespaces standard
Les namespaces disponibles en standard avec Plone 4.1 sont les suivants :
166
Chapitre 5. Le langage ZCML
Plone pour les développeurs, Version 1.0.0
TABLE 5.1 – Namespaces ZCML
2
Préfixe
browser
cache
cmf
five
genericsetup
i18n
kss
meta
monkey
plone
pluggableauthservice
5.3.
Les directives ZCML
wicked
URI
Commentaire
http ://naLes directives de ce namespace permettent
mesde déclarer des pages, vues viewlets, et
paces.zope.org/browser
parfois des menus de vos composants.
http ://naCe namespace fournit deux directives
mespermettant d’associer des règles de cache
paces.zope.org/cache
avec des types de contenus. Ce namespace
est fourni par plone.app.caching qui
est fourni en standard avec Plone 4.1
http ://naUne directive permet de déclarer un layer de
messkin CMF. Pour rappel, les layers de skins
paces.zope.org/cmf
CMF sont une survivance facilitant le
support de composants anciens pour lesquels
une adaptation minimaliste à Plone 3 a été
effectuée. Les nouvelles extensions pour
Plone devront utiliser de préférence les
ressources du namespace browser pour
ceci.
http ://naLes directives spécifiques au composant
mesProducts.Five qui permettent de
paces.zope.org/five
configurer des ressources spécifiques à Zope
2.
http ://naLes directives fournies permettent de
mesdéclarer les profils d’installation,
paces.zope.org/genericsetup
d’exportation de configuration et de
migration fournies par
Products.GenericSetup.
http ://naL’unique directive de ce namespace permet
mesde déclarer un répertoire de fichiers de
paces.zope.org/i18n
traductions (fichiers *.mo) disposés selon le
standard “gettext”. Pour plus de précisions,
voir le chapitre Internationalisation d’un
composant.
http ://naLes directives kss permettent d’enregistrer
mesdes ressources Ajax.
paces.zope.org/kss
http ://naLes directives fournies par ce namespace
mespermettent de définir de nouveaux éléments
paces.zope.org/meta
ZCML (s’ils figurent dans un fichier
meta.zcml) ou - plus rarement - de
remplacer et personnaliser des éléments
existants (s’ils figurent dans un fichier
overrides.zcml).
http ://naCe namespace est fourni par le composant
mescollective.monkeypatcher. Ce
paces.plone.org/monkey
composant permet de mettre en place des
monkey patches de façon rationnelle et
documentée. Les monkey patches actifs
peuvent être vus dans le Control Panel Zope
à l’aide du composant
collective.monkeypatcherpanel.
http ://naLes directives fournies permettent de
mesdéclarer les portlets et les règles de contenus
paces.plone.org/plone
(content rules)
http ://naL’unique directive
mesregisterMultiPlugin permet de
paces.zope.org/pluggableauthservice
déclarer les plugins et multiplugins
utilisables depuis PAS.
http ://naLes directives de ce namespace permettent 167
mesd’identifier les champs d’un type de contenu
paces.openplans.org/wicked
Archetypes qui peuvent se comporter comme
une page Wiki.
Plone pour les développeurs, Version 1.0.0
Zope 3 ou BlueBream disposent d’autres namespaces qui ne sont pas rendus disponibles aux applications Zope
2 à travers Products.Five. Nous ne les aborderons pas ici, mais les curieux pourront toujours se reporter au
chapitre ZCML de la documentation de BlueBream (en).
Attention : Les namespaces provenant des composants de Zope 3 (plus précisément du “Zope toolkit”) sont
exposés par le biais de Products.Five. Du fait des particularités de Zope 2, les directives ZCML exposées
par les composants du Zope toolkit intégrés dans Zope 2.12 ne sont pas toutes disponibles. C’est particulièrement le cas de préfixes apidoc, mail et xmlrpc.
5.3.2 Explorer les namespaces et directives
Comme l’auteur de ces lignes est soudain atteint d’une grosse fatigue au moment de détailler les diverses directives
ZCML et leur utilisation, il a préféré réaliser un composant qui ajoute un tableau de bord Zope 2 qui affiche les
détails des différents namespaces et directives mises à votre disposition.
aws.zope2zcmldoc
Je vous invite à installer sur vos instances de développement le composant aws.zope2zcmldoc et d’explorer depuis
le tableau de bord la documentation au fur et à mesure de vos besoins.
Autre avantage : comme aws.zope2zcmldoc utilise la “réflectivité” des directives ZCML, pour ses différentes
vues, vous pourrez également l’utiliser pour lire la documentation de namespaces et directives fournis par des
composants de tierce partie comme iw.fss ou d’autres.
Quelques screenshots
5.4 Pour aller plus loin
5.4.1 Comprendre une directive ZCML
L’une des explications sur une directive ci-dessus vous parait floue, ou bien une directive fournie par un composant
de tierce partie n’est pas correctement documentée ? Voici la méthode pour pister la définition d’une directive
ZCML quelconque.
La directive est exposée à partir d’un namespace, il vous faudra donc trouver un fichier meta.zcml dans lequel
est définie cette directive.
En supposant que la directive s’appelle foo et qu’elle est exposée par le namespace
http://namespace.mydomain.tld/bar, il vous faudra trouver, à l’aide des commandes shell find
et grep un fichier meta.zcml dans lequel figure quelque-chose comme ceci :
namespace="http://namespace.mydomain.tld/bar"
...
name="foo"
Si vous ne le trouvez pas, c’est que vous avez mal cherché, car il se trouve forcément quelque part dans l’un des
packages de votre projet.
Vous trouverez donc (si, si !) un fichier meta.zcml qui contiendra quelque chose comme ceci :
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:meta="http://namespaces.zope.org/meta"
>
...
<meta:directives namespace="http://namespace.mydomain.tld/bar">
...
168
Chapitre 5. Le langage ZCML
Plone pour les développeurs, Version 1.0.0
F IGURE 5.1 – Le nouveau lien dans le Control Panel Zope 2
5.4. Pour aller plus loin
169
Plone pour les développeurs, Version 1.0.0
F IGURE 5.2 – Les namespaces disponibles dans votre instance
170
Chapitre 5. Le langage ZCML
Plone pour les développeurs, Version 1.0.0
F IGURE 5.3 – Les différentes directives d’un namespace (ici “browser”)
5.4. Pour aller plus loin
171
Plone pour les développeurs, Version 1.0.0
F IGURE 5.4 – Le détail d’une directive ou sous-directive (ici “browser :menu)
<meta:directive
name="foo"
schema="bar.zcml.IFooDirective"
handler="bar.zcml.foo" />
...
</meta:directives>
...
</configure>
Ce qui signifie que les attributs de l’élément de configuration <foo ...> sont définis dans la classe
bar.zcml.IFooDirective et que l’élément sera traité par la fonction bar.zcml.foo.
La classe bar.zcml.IFooDirective est une interface (sous-classant zope.interface.Interface) et
ses attributs sont définis par des objets des modules zope.configuration.fields ou de zope.schema
et sont généralement abondament commentés.
Le
handler
bar.zcml.foo
prend
pour
paramètres
un
objet
implémentant
zope.configuration.interfaces.IConfigurationContext ainsi que l’ensemble des attributs de l’émément <foo ..>.
Le reste, c’est la lecture du code Python, généralement assez facile, qui vous l’apprendra.
5.4.2 Autres ressources
Pour aller plus loin, cet article de blog explique en détails comment faire sa propre directive ZCML.
Quelques composants simples pour Zope et Plone qui exposent leur propre vocabulaire ZCML :
– collective.monkeypatcher permet d’effectuer des monkey patches Python d’une façon documentée et rationnelle.
– archetypes.configure évite la fastidieuse déclaration de types en Python dans les packages fournissant des types
de contenus Archetypes
172
Chapitre 5. Le langage ZCML
CHAPITRE 6
Utiliser la ZCA avec Grok
6.1 Introduction
Grok est un composant qui permet de simplifier et d’accélérer considérablement l’écriture de code utilisant la Zope
Component Architecture. C’est principalement un ensemble de directive permettant de diminuer l’utilisation du
ZCML et d’écrire davantage de choses directement dans le code python.
6.2 Installation de five.grok
Modifiez votre buildout.cfg comme ceci :
[zope2]
skip-fake-eggs =
zope.app.publisher
zope.component
zope.i18n
zope.interface
zope.testing
[instance]
eggs =
# ...
five.grok
zcml =
# ...
five.grok-meta
five.grok
Et ajoutez ces versions dans votre fichier versions.cfg :
five.grok = 1.0
grokcore.annotation = 1.1
grokcore.component = 1.7
grokcore.formlib = 1.4
grokcore.security = 1.2
grokcore.site = 1.1
grokcore.view = 1.12.2
grokcore.viewlet = 1.3
five.localsitemanager = 1.1
martian = 0.11.1
zope.app.publisher = 3.5.1
173
Plone pour les développeurs, Version 1.0.0
zope.app.zcmlfiles = 3.4.3
zope.component = 3.4.0
zope.i18n = 3.4.0
zope.interface = 3.4.1
zope.schema = 3.4.0
zope.securitypolicy = 3.4.1
zope.testing = 3.7.6
Vous pouvez retrouver les dernières versions des packages qui fonctionnent bien ensemble dans le buildout de
five.grok pour Zope 2.10/Plone 3, pour Zope 2.12/Plone 4.
6.3 Utiliser Grok
Créez un package formation.transforms avec le template paster plone :
$ cd /tmp
$ paster create -t plone formation.transforms \
--svn-repository=http://devagile/Formation/packages
$ cd formation.transforms
$ svn rm --force formation.transforms/formation.transforms.egg-info
$ svn ci -m"Added skel of formation.transforms"
Dans votre buildout, éditez le fichier sources.cfg, ajoutez formation.transforms dans l’option
auto-checkout et indiquez l’url suivante dans [sources] :
formation.transforms = svn http://devagile/Formation/packages/formation.transforms/trunk
Dans buildout.cfg, ajoutez formation.transforms dans l’option eggs.
Relancez bin/buildout pour récupérer le product dans le dossier src.
éditez src/formation.transforms/setup.py pour déclarer le produit comme plugin plone :
entry_points="""
[z3c.autoinclude.plugin]
target = plone
"""
Relancez bin/buildout pour regénérer les métadonnées de formation.transforms.
Créez un fichier formation/transforms/interfaces.py :
from zope.interface import Interface
class IReplaceLetter(Interface):
def getText(letter1, letter2):
"""Return a modified text, replace letter1 by letter2.
"""
Créez un fichier adapters.py :
from five import grok
from formation.transforms.interfaces import IReplaceLetter
from Products.ATContentTypes.interface.document import IATDocument
class ReplaceLetter(grok.Adapter):
grok.implements(IReplaceLetter)
grok.context(IATDocument)
def getText(self, letter1, letter2):
return self.context.getText().replace(letter1, letter2)
Créez un module views.py :
174
Chapitre 6. Utiliser la ZCA avec Grok
Plone pour les développeurs, Version 1.0.0
from five import grok
from formation.transforms.interfaces import IReplaceLetter
from Products.ATContentTypes.interface.document import IATDocument
grok.templatedir(’templates’)
class TransformedDocument(grok.View):
grok.name("my-view")
grok.context(IATDocument)
def update(self, letter1=None, letter2=None):
self.letter1 = letter1
self.letter2 = letter2
def getAuthenticatedUser(self):
user = self.request.AUTHENTICATED_USER
return user.getProperty(’fullname’, user.getId())
def getContent(self):
if self.letter1 is None or self.letter2 is None:
return self.context.getText()
return IReplaceLetter(self.context).getText(self.letter1, self.letter2)
Créez le répertoire templates/ et créez dedans un fichier transformeddocument.pt :
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
xmlns:tal="http://xml.zope.org/namespaces/tal"
xmlns:metal="http://xml.zope.org/namespaces/metal"
xmlns:i18n="http://xml.zope.org/namespaces/i18n"
lang="en"
metal:use-macro="context/main_template/macros/master"
i18n:domain="formation.transforms">
<div metal:fill-slot="content"
tal:content="structure view/getContent">
</div>
</html>
Dans formation/transforms/configure.zcml, ajoutez l’espace de nom grok et la ligne :
<grok:grok package=".adapters" />
<grok:grok package=".views" />
Vous pouvez également faire :
<grok:grok package="." />
qui va rechercher l’ensemble des packages et modules qui comportent des classe héritant de grok.
Dans Plone, créez un Document et accédez à la vue @@transformeddocument.
6.4 Ressources
6.4.1 Viewlets avec grok
– http ://grok.zope.org/documentation/how-to/understanding-viewlets
– Création d’une viewlet qui affiche un Avertissement lorsque
http ://vincentfretin.ecreall.com/articles/creating-a-viewlet-with-grok
6.4. Ressources
le
document
est
expiré
:
175
Plone pour les développeurs, Version 1.0.0
176
Chapitre 6. Utiliser la ZCA avec Grok
CHAPITRE 7
Modélisation UML
7.1 Objectifs
Connaitre les bases de la notation UML.
7.2 Savoir
– Les différents diagrammes,
– La représentation des classes, attributs, méthodes, associations,
– La représentation des objets, des composants, des collaborations, des flux d’informations.
Pour réaliser l’analyse et générer les composants nécessaires à Plone nous allons utiliser l’outil ArchGenXML en
version 2.
Nous réaliserons dans un premier temps un diagramme de classes, qui permettra de décrire le plan documentaire
de notre application, puis nous associerons à nos documents des “workflows” sous forme de diagramme d’états
transitions.
Mais avant cela nous allons réaliser un bref rappel des notions UML utilisées dans la suite.
7.3 Qu’est ce qu’UML
UML UML (Unified Modeling Language) est une notation graphique qui permet de traduire sous forme de vues
l’architecture d’un logiciel ou d’une application orientée objet.
UML est la fusion de différentes notations issues des méthodes de génie logiciel orienté objet antérieures aux
années 1995. La version 1.0 a officiellement été publiée en 1997 par l’OMG (Object Management Group) qui est
l’organisme responsable de sa standardisation. Depuis 2007, la version officielle est la 2.1.2.
Historiquement, son usage intervenait dans la phase de conception pour permettre de “visualiser” ce que sera et
fera le logiciel. Comme les feuillets des plans d’un architecte en bâtiment, différents diagrammes composent une
vue spécifique de la solution apportée au problème.
Le lecteur doit parcourir plusieurs vues et donc lire plusieurs diagrammes pour se créer son image mentale du
logiciel.
Cette abstraction n’est malheureusement pas évitable, puisque un logiciel est composé à la fois de parties statiques,
telle que le code exécutable, de données dynamiques telles que les entrées sorties, et de comportements.
Les vues sont des regroupements abstraits des diagrammes qui sont eux les “dessins” représentant les interactions
entre les éléments constitutifs du logiciel. Ces diagrammes permettent de visualiser l’interaction entre les éléments
177
Plone pour les développeurs, Version 1.0.0
de l’architecture qui sont regroupés dans un “modèles d’éléments” et constitués des cas d’utilisations, classes,
associations, attributs, etc.
Actuellement, les courants de l’IDM (Ingénierie Dirigée par les Modèles), font que son usage est présent tout au
long du cycle de vie du logiciel, et l’on met à jour le logiciel en modifiant le modèle puis en générant à nouveau
l’application. Les outils de génération se chargent de fusionner l’existant avec les nouvelles fonctionnalités.
Initialement, la conception orientée objet cherchait à résoudre un problème en le découpant non pas en un ensemble de fonctionnalités dont le tout forme un arbre ayant pour racine une fonction “main”, mais en boites
travaillant ensemble par l’échange de messages et dont le comportement global réalise la tâche attendue.
La description de ces boites forme un tout homogène de données et de fonctions appelée interface, c’est une
abstraction.
La définition d’un type de boite respectant une interface donnée est nommée “classe” si elle est simple, et composant si pour respecter le contrat de l’interface elle encapsule la collaboration de plusieurs classes et peut à elle
seule être considérée comme une application.
L’exécution d’une classe est appelée instance, elle possède alors ses propres valeurs d’attributs à commencer par
son occupation mémoire ce qui la distingue des autres instances de la même classe.
Mais revenons à UML.
UML se base sur trois types de briques pour permettre la modélisation d’une architecture :
– Les éléments, qui sont un peu le vocabulaire de UML ;
– Les relations, qui représentent la syntaxe du projet ;
– Les diagrammes, qui contiennent la sémantique du projet.
7.4 Les éléments
Nous n’allons détailler ici que les éléments d’UML que nous utiliserons.
7.4.1 Les éléments structurels
La classe
C’est un types d’éléments qui partagent les mêmes propriétés (attributs, méthodes, relations, sémantique).
Si les propriétés définies par la classe sont immuables et ne dépendent pas des éléments, on parle d’attributs ou de
variables de classe et de méthodes de classes, inversement si leur contenu dépend de l’élément on parle d’attribut
ou de variable d’instances et de méthodes.
Les propriétés d’une classe ont une visibilité, c’est-à-dire que l’accès aux propriétés d’une classe par une autre est
contrôlé.
L’interface
C’est un ensemble de méthodes qui définissent le comportement attendu d’une classe en laissant aux classes issues
de cette interface de choisir l’implémentation.
L’objet
C’est un élément concret actif ou passif appartenant à une classe. Si la classe de l’objet est connue, on parle alors
d’instance de cette classe. L’objet possède toutes les propriétés de sa classe et définie les valeurs des attributs
d’instances.
178
Chapitre 7. Modélisation UML
Plone pour les développeurs, Version 1.0.0
F IGURE 7.1 – Représentation des classes
F IGURE 7.2 – Représentation des interfaces
F IGURE 7.3 – Représentation des objets
7.4. Les éléments
179
Plone pour les développeurs, Version 1.0.0
L’acteur
C’est une classe externe au système agissant ou réagissant au système.
Ainsi tous les intervenants sur le système sont des acteurs.
F IGURE 7.4 – Représentation des acteurs
Le composant
C’est une classe formant un tout cohérent et suffisant permettant de réaliser un ensemble de fonctionnalités, c’est
la boite noire par excellence.
F IGURE 7.5 – Représentation des composants
La collaboration
Lorsque plusieurs instances de classes réalisent un comportement global par échange de messages, on dit qu’ils
forment une collaboration et que les objets y remplissent un rôle.
La collaboration permet donc de visualiser la réalisation des exigences fonctionnelles.
F IGURE 7.6 – Représentation des collaborations
Le cas d’utilisation
C’est un comportement du système dont est témoin un acteur, il est réalisé par des collaborations.
180
Chapitre 7. Modélisation UML
Plone pour les développeurs, Version 1.0.0
F IGURE 7.7 – Représentation des cas d’utilisations
7.4.2 Les éléments comportementaux
Les interactions
Elles sont constituées par l’envoi de messages ou d’événements provoquant des actions chez le récepteur.
L’appel d’une méthode de classe correspond à l’envoi d’un message dont le nom est suivi de parenthèses.
F IGURE 7.8 – Représentation des messages
Les états
Ils permettent de constituer des automates décrivant le comportement d’une classe ou d’une méthode.
F IGURE 7.9 – Représentation des états dans un diagramme d’états
Les activités
Les activités sont des comportements exécutables séquentiellement ou parallèlement.
7.4.3 Les éléments de regroupement
Les paquetages
Ils permettent de ranger les classes et diagrammes de façon à rendre plus clair le modèle.
7.4. Les éléments
181
Plone pour les développeurs, Version 1.0.0
F IGURE 7.10 – Représentation des activités dans un diagramme d’activités
F IGURE 7.11 – Représentation des paquetages
182
Chapitre 7. Modélisation UML
Plone pour les développeurs, Version 1.0.0
Les éléments d’annotation
Les notes permettent de donner des informations.
F IGURE 7.12 – Représentation des notes
7.5 Les relations
7.5.1 La dépendance
C’est une relation sémantique indiquant que tout changement de l’élément indépendant peut affecter l’élément
dépendant.
F IGURE 7.13 – Représentation des dépendances
7.5.2 L’association
Elle permet d’indiquer une relation entre deux éléments.
La nature de la relation est donnée par le texte situé dessus. Un lien peut être précisé aux extrémités par le nombre
et le rôle de chacun des éléments.
Un lien de contenance est symbolisé par l’agrégation.
F IGURE 7.14 – Représentation des associations
7.5.3 La généralisation
Cette relation permet de définir des niveaux d’abstraction entre les classes, le but est de permettre de manipuler
de façon homogène des ensembles d’objets qui partagent les mêmes propriétés. Cette factorisation des traitements
s’appelle le polymorphisme.
7.5. Les relations
183
Plone pour les développeurs, Version 1.0.0
F IGURE 7.15 – Représentation des généralisations
7.5.4 La réalisation
Cette relation indique que la classe implémente les comportements définis dans l’interface qu’elle réalise.
F IGURE 7.16 – Représentation des réalisations
7.5.5 La transition
Cette relation joint deux états et permet d’expliciter les conditions à respecter et les traitements à réaliser lors des
changements d’états.
7.6 Les diagrammes que nous utiliserons
Alors que UML permet de créer 13 types de diagrammes nous n’allons n’en utiliser que trois pour la génération
de notre produit. Les autres diagrammes servirons à la documentation de notre architecture.
Les diagrammes qui servirons à la génération sont :
184
Chapitre 7. Modélisation UML
Plone pour les développeurs, Version 1.0.0
– Le diagramme de classes
– Le diagramme d’états transitions
– Le diagramme d’activités
7.6.1 Le diagramme de classes
Ce diagramme permet de mettre en évidence la structure des classes c’est-à-dire les attributs et méthodes présents
dans les classes, les relations entre classes.
Ainsi, il fait apparaitre les hiérarchies de classes (héritage, abstraction, interface), les compositions et agrégations
(un instance d’une classe contenant des instances d’autres classes), les dépendances, les “packages”.
Par convention on limite le nombre de classes visibles dans un diagramme de classes. On va alors réaliser différents
diagrammes de classes qui chacun doit montrer un aspect du problème en n’affichant que les attributs, méthodes
et associations concernés par cet aspect.
F IGURE 7.17 – Diagramme de classes
7.6.2 Le diagramme d’objets
Il regroupe les objets et permet de les représenter avec leur liens à un moment donné.
F IGURE 7.18 – Diagramme d’objets
7.6. Les diagrammes que nous utiliserons
185
Plone pour les développeurs, Version 1.0.0
7.6.3 Le diagramme de cas d’utilisation
Il regroupe les cas d’utilisations et les acteurs concernés.
F IGURE 7.19 – Diagramme de cas d’utilisations
7.6.4 Le diagramme de séquence
Il permet de visualiser l’enchainement des messages entre objets. L’ordre de lecture est de haut en bas.
7.6.5 Le diagramme de collaboration
C’est une représentation symétrique du diagramme de séquence.
7.6.6 Le diagramme d’états transitions
Il est utilisé pour modéliser les changements d’états d’un élément.
Nous l’utiliserons pour modéliser les workflows de nos types de contenu.
7.6.7 Le diagramme d’activités
Il permet de voir l’enchainement des tâches du système ou des traitements déclenchés par les acteurs.
7.6.8 Le diagramme de composants
Il montre les liens entre composants.
7.6.9 Le diagramme de déploiement
Il permet de voir l’intégration.
186
Chapitre 7. Modélisation UML
Plone pour les développeurs, Version 1.0.0
F IGURE 7.20 – Diagramme de séquences
7.6. Les diagrammes que nous utiliserons
187
Plone pour les développeurs, Version 1.0.0
188
Chapitre 7. Modélisation UML
CHAPITRE 8
Génerer un composant Plone avec
ArgoUML et ArchgenXML
8.1 Définition
ArgoUML est un outil de modélisation d’application basé sur le formalisme UML.
Dans le cas de création d’application Plone/Zope, il est utilisé pour avoir une vue synthétique de l’architecture
d’un produit.
8.2 Savoir
–
–
–
–
Création des diagrammes de classes correspondant à la structure du produit,
Création des diagrammes d’états correspondant aux workflows,
Création des étiquettes permettant de gérer les noms, les permissions et les worklists,
Dérivation des classes du système.
8.3 Installation d’ArgoUML
ArgoUML est un outil de modélisation UML open source qui gère la version 1.4 d’UML.
Bien que sommaire, il suffit à modéliser notre besoin. Son format de sauvegarde sera interprété par l’outil de
génération ArchGenXML pour produire le code du module de Plone qui satisfera le besoin de notre cahier des
charges.
ArgoUML est accessible à http ://argouml.tigris.org nous téléchargeons la version 0.30 et l’installons.
Cette installation requière Java. Si aucune machine virtuelle java n’est présente sur votre poste, le logiciel d’installation correspondant à votre OS vous proposera d’installer celle de SUN.
8.4 Installation de ArchGenXML
Nous utiliserons ArchGenXML pour générer le code Python à partir du modèle créé avec ArgoUML.
De plus, ArchGenXML fournit un profil permettant d’ajouter à ArgoUML les types et les tags dont nous allons
avoir besoin pour modéliser notre module.
189
Plone pour les développeurs, Version 1.0.0
L’installation peut être faite à partir de pypi ou depuis le dépôt subversion (subversion est accessible à http ://subversion.apache.org).
L’intérêt du dépôt subversion est de disposer des dernières corrections :
C:\Program Files> cd C:\
C:\> svn export http://svn.plone.org/svn/archetypes/ArchGenXML/buildout ArchGenXML
C:\> cd ArchGenXML
C:\ArchGenXML> C:\python26\python.exe bootstrap.py
C:\ArchGenXML> bin\buildout.exe
C:\ArchGenXML> bin\test-archgenxml.exe
Running zope.testing.testrunner.layer.UnitTests tests:
Set up zope.testing.testrunner.layer.UnitTests in 0.000 seconds.
Ran 82 tests with 0 failures and 0 errors in 0.211 seconds.
Tearing down left over layers:
Tear down zope.testing.testrunner.layer.UnitTests in 0.000 seconds.
La dernière commande permet d’exécuter les tests unitaires et donc de vérifier qu’ArchGenXML fonctionne,
l’affichage montre qu’il a eu 82 tests exécutés sans erreurs.
8.5 Configuration d’ArgoUML pour utiliser le profil d’ArchGenXML
Pour ajouter le profil, allez dans le menu Édition -> Préférences... -> Profiles -> Add et saisissez le chemin vers le
dossier contenant le profil ArchGenXML C:\ArchGenXML\src\archgenxml\umltools\argouml.
F IGURE 8.1 – Ajout du dossier contenant le profil ArchGenXML
Vous devez voir normalement AGXProfile dans Available Profiles. Sur certaine version d’ArgoUML il fallait le
relancer pour que soit pris en compte le nouveau répertoire des profils.
On ouvre (à nouveau) la gestion des profils et l’on ajoute dans Default Profiles le profil ArchGenXML et on retire
tous les autres excepté UML 1.4 qu’on ne peut pas retirer à partir d’ici.
190
Chapitre 8. Génerer un composant Plone avec ArgoUML et ArchgenXML
Plone pour les développeurs, Version 1.0.0
F IGURE 8.2 – Ajout du profil ArchGenXML
8.5. Configuration d’ArgoUML pour utiliser le profil d’ArchGenXML
191
Plone pour les développeurs, Version 1.0.0
Maintenant dans le panneau qui s’ouvre lorsqu’on clique sur le lien Configure project specific settings... de la
fenêtre précédente, retirez le profil UML 1.4. Vous ne devez avoir que le profil ArchGenXML d’activé.
Voilà ArgoUML est prêt à être utilisé pour modéliser notre module.
8.6 Création du buildout de notre module
La première étape est de créer et ajouter la structure de notre nouveau module (Product) à Plone.
Pour cela plaçons nous dans le répertoire src de notre Plone (le créer s’il n’existe pas) et lançons la commande
paster comme suit :
c:\Plone4> cd src
C:\Plone4\src> paster create -t basic_namespace Products.StandArt_AQ
Selected and implied templates:
zopeskel#basic_namespace A basic Python project with a namespace package
Variables:
egg:
Products.StandArt_AQ
package: productsstandart_aq
project: Products.StandArt_AQ
Expert Mode? (What question mode would you like? (easy/expert/all)?) [’easy’]:
Version (Version number for project) [’1.0’]: 0.1
Description (One-line description of the project) [’’]: The Stand’Art company
Qualite Assurance
Creating template basic_namespace
Creating directory .\Products.StandArt_AQ
Recursing into +namespace_package+
Creating .\Products.StandArt_AQ\Products/
Recursing into +package+
L’arborescence créée est alors :
C:\Program Files\Plone4\src> tree Products.StandArt_AQ
Structure du dossier
Le numéro de série du volume est XXXXXXXX XXXX:XXXX
C:\PROGRAM FILES\PLONE4\SRC\PRODUCTS.STANDART_AQ
|-- Products
|
‘-- StandArt_AQ
|-- Products.StandArt_AQ.egg-info
‘-- docs
Nous allons ajouter un répertoire model/ dans Products.StandArt_AQ pour y stocker le modèle UML que nous
allons réaliser.
8.7 Création du plan documentaire
Notre modélisation reposera en partie sur le mécanisme des Archetypes proposé par Plone et sur celui des vues
Zope 3. Il est donc possible de consulter la documentation dédiée à ces deux technologies pour compléter ce cours.
Dans le cahier des charges nous avons vu que l’ensemble des documents sous assurance qualité allait être stocké dans un répertoire dédié et que les documents seront stockés dans un conteneur spécial possédant plusieurs
champs.
Modifier le nom du paquetage “untitledModel” en “Products.StandArt_AQ”. Pour cela mettez vous en vue
“Orienté Paquetage”, cliquer sur “untitledModel” dans la navigation puis sur l’onglet “Propriété” et dans le champ
“nom” saisissez “StandArt_AQ”.
Puis enregistrez le modèle sous le nom StandArt_AQ dans le répertoire model
192
Chapitre 8. Génerer un composant Plone avec ArgoUML et ArchgenXML
Plone pour les développeurs, Version 1.0.0
F IGURE 8.3 – Nommer le paquetage en Products.StandArt_AQ
Cliquez sur “Diagramme de classe” dans la barre de navigation, vous pouvez alors ajouter les éléments symbolisés
par les icônes de la zone de dessin.
Ajoutez y un paquetage nommé “content”, par convention nous mettons tous les types de contenu dans ce répertoire, les classes utilitaires seront mises dans un paquetage nommé “tools”.
De même, puisque généralement nos paquets ont vocation à être partagés avec la communauté Plone nous réalisons
la modélisation en Anglais.
Dans le paquetage “content”, créez un diagramme de classes “Content diagram”
Éditez le diagramme pour ajouter les classes de bases ATFolder et ATDocument qui sont les types des répertoires et des documents de Plone.
Nous les utiliserons comme classes de bases pour nos types de façon à réutiliser leur comportement.
Ajoutez y le stéréotype stub qui permettra d’indiquer à ArchGenXML qu’il ne doit pas générer ces classes car
elles existent déjà.
Pour ATFolder allez dans l’onglet Étiquettes et ajoutez le marqueur (tag) import_from ayant pour valeur
Products.ATContentTypes.content.folder.
Pour
ATDocument
ajoutez
le
marqueur
(tag)
Products.ATContentTypes.content.document.
import_from
avec
pour
valeur
On peut aussi utiliser le stéréotype <<ATFolder>> et <<ATDocument>> dans les classes dérivées au lieu de
modéliser la généralisation, mais il arrive qu’ArchGenXML ne réussisse pas à générer le code.
Nous créons le répertoire qui contiendra tous les documents sous assurance qualité en dérivant ATFolder et en
nommant cette nouvelle classe StandArt_FolderAQ.
Il est conseillé de préfixer ses types de contenu afin d’éviter toute collision avec d’autres modules Plone.
De même pour les attributs, préfixez les d’un m pour “membre” par exemple pour éviter de surcharger par accident
8.7. Création du plan documentaire
193
Plone pour les développeurs, Version 1.0.0
tout attribut ou méthode existant dans la classe de base (et avec le mécanisme de wrapper c’est près d’une centaine
de membres).
Toutefois, si vous souhaitez redéfinir un attribut déjà existant gardez le nom d’origine (exemple : title, description).
F IGURE 8.4 – Création du plan documentaire
Pour les classes, la signification des marqueurs est la suivante :
label
Le nom du type tel qu’il apparaitra dans le menu d’ajout de contenu.
creaPermission qu’il faut avoir pour créer une instance.
tion_permission
creation_roles Rôles qui ont la permission par défaut (à la racine).
use_portal_factory1 signifie que l’on travaille dans une instance temporaire tant que l’enregistrement n’est pas
fait.
content_icon
Permet d’associer un fichier d’icône aux instances.
base_class
0 signifie qu’ArchGenXML ne doit pas associer par lui même de classe de base car elle est
précisée dans le modèle par l’héritage.
description
Donne la description du type d’objet.
Nous avons dérivé nos types de ceux contenus dans Plone, mais nous aurions très bien pu sur cet exemple partir
d’un type défini dans un module. De cette façon il est possible d’étendre et particulariser des produits que l’on n’a
pas fait soit même.
Si l’on avait voulu qu’il y ait plusieurs roles pouvant créer des instances nous aurions alors donné au marqueur
creation_roles la valeur python:("Manager", "Contributor").
Nous allons remplir les informations concernant le champ documentation :
La signification des marqueurs du champs mDocument est :
194
Chapitre 8. Génerer un composant Plone avec ArgoUML et ArchgenXML
Plone pour les développeurs, Version 1.0.0
F IGURE 8.5 – Information concernant le champ mDocument
widget :label
widget :label_msgid
widget :description
widget :description_msgid
read_permission
write_permission
storage
Le nom du champ.
L’identifiant de traduction associé au nom.
La description du champ.
L’identifiant de traduction associé à la description.
Le nom de la permission à avoir pour voir le champ. On peut ainsi en créer de
nouvelle.
Le nom de la permission à avoir pour modifier le champ.
Comment le contenu du champ doit être stocké.
Les formulaires d’édition ou de consultation sont générés automatiquement par Plone à partir du type des champs.
Toutefois, un type de champ peut être affiché ou édité de façons différentes selon le widget qui lui est associé.
Ces widgets sont configurés en étiquetant le champ avec des marqueurs préfixés par widget :.
Il est également possible de créer ses propres widgets.
Voici une liste des types des champs possibles.
8.7. Création du plan documentaire
195
Plone pour les développeurs, Version 1.0.0
Nom
BackReferenceField
BooleanField
ColorField
ComputedField
Type
backreference
boolean
color
computed
copy
Widget par défaut
BackReferenceWidget
ComputedWidget
ColorWiget
ComputedWidget
DataGridField
DateTimeField
FileField
FixedPointField
FloatField
ImageField
datagrid
date
file
fixedpoint
float
image
DataGridWidget
CalendarWidget
FileWidget
FixedPointWidget
FloatWidget
ImageWidget
IntegerField
LinesField
LinesField
LinesField
ReferenceField
int
keywords
lines
multiselection
reference
TextField
StringField
richtext
selection
IntegerWidget
KeywordWidget
LinesWidget
MultiSelectionWidget
ReferenceBrowserWidget
RichWidget
SelectionWidget
StringField
string
StringWidget
TextField
string
TextAreaWidget
Description
Référence navigable.
Champ stockant vrai ou faux.
Sélection d’une couleur.
Champ calculé.
Permet de surcharger un champ d’une des classes
de base.
Des lignes de tableau.
Stocke des dates et heures.
Stocke un fichier sans réaliser de traitement.
Champ numérique à virgule fixe
Champ numérique à virgule fixe
Stocke une image et permet de la retailler
dynamiquement.
Stocke une donnée numérique entière.
Une liste de données, par exemple des mots clés.
Une liste de ligne.
Une liste de données à sélections multiples.
Permet de référencer un autre objet.
Texte avec mise en forme.
Permet de sélectionner une ligne de texte parmi
plusieurs.
Champ texte pour les chaines de moins de 100
caractères.
Champ texte pour les grandes chaînes.
Certains de ces champs reposent sur des produits. Ainsi DataGridField repose sur le produit
Products.DataGridField qu’il faut alors ajouter au buildout.
Le type copy est une astuce qui permet de surcharger les marqueurs d’un champ défini dans l’une des classes
mères.
Par exemple pour changer la description, le titre et la permission de lecture et d’écriture du champ title, il suffit
de créer un champ portant comme Nom title et de lui associer les marqueurs suivants avec les valeurs voulues :
Marqueur
widget :label
widget :label_msgid
widget :description
widget :description_msgid
read_permission
write_permission
Valeur
Le nom du champ
IdentifiantDuNomPourLesTraductions
La description du champ
IdentifiantDeLaDescriptionPourLesTraductions
View
MonProduit : le nom de la permission
Les identifiants doivent être en ASCII sans espaces.
Le nom des permissions est soit celui d’une permission déjà existante soit une chaîne de caractères ASCII qui, par
convention, doit être préfixée par le nom du produit, ce qui permettra de les regrouper dans l’onglet security
de Zope.
Parfois, ArchGenXML ne réalise pas automatiquement l’importation de la définition de certains types de champ
ou de widget. Il faut alors l’ajouter soit comme marqueur de la classe en utilisant l’étiquette import soit dans la
zone protégée du module python généré, c’est-à-dire entre les lignes :
##code-section module-header #fill in your manual code here
from archetypes.referencebrowserwidget import ReferenceBrowserWidget
##/code-section module-header
@TODO mettre à jour la liste suivante et expliquer l’usage
196
Chapitre 8. Génerer un composant Plone avec ArgoUML et ArchgenXML
Plone pour les développeurs, Version 1.0.0
Chacun de ces types de champs possède un ou plusieurs attributs qui permettent de paramétrer leur comportement :
– accessor
– default
– default_method
– edit_accessor
– enforceVocabulary
– index
– name
– mode
– multiValued
– mutator
– primary
– required
– schemata
– searchable
– validators
– vocabulary
– storage
Nous allons générer une première version de notre module :
C:\Program Files\Plone4\src\Products.StandArt_AQ\Products> c:\ArchGenXML\bin\ar
chgenxml.exe ..\model\Products.StandArt_AQ.zargo StandArt_AQ
INFO ArchGenXML Version 2.5.dev
(c) 2003-2009 BlueDynamics Alliance, Austria, GPL 2.0 or later
INFO Directories to search for profiles: [’C:\\ArchGenXML\\src\\archgenxml\\u
mltools\\argouml’]
INFO Parsing...
INFO Profile files: ’{u’archgenxml_profile.xmi’: u’C:\\ArchGenXML\\src\\archg
enxml\\umltools\\argouml\\archgenxml_profile.xmi’}’
INFO Directory in which we’re generating the files: ’StandArt_AQ’.
INFO Generating...
INFO Starting new Product: ’StandArt_AQ’.
c:\archgenxml\eggs\i18ndude-3.1.2-py2.6.egg\i18ndude\odict.py:7: DeprecationWa
rning: object.__init__() takes no parameters
dict.__init__(self, dict)
INFO
Generating package ’content’.
INFO
Generating class ’StandArt_DocumentAQ’.
INFO
Generating class ’StandArt_FolderAQ’.
INFO generator run took 0.96 sec.
archgenxml.exe ..\model\Products.StandArt_AQ.zargo StandArt_AQ a lancé l’exécution de ArchGenXML en lui demandant de prendre comme fichier d’entrée notre modèle et de générer un répertoire
StandArt_AQ contenant le code du module. Pour faire cela nous nous sommes d’abord placé dans le répertoire src\Products.StandArt_AQ\Products.
Puis, nous pouvons ajouter ce nouveau module au buildout.cfg de notre Plone pour le faire apparaitre dans
la liste des modules disponibles.
Pour cela, il suffit d’y ajouter :
eggs =
Plone
Products.StandArt_AQ
develop =
src/Products.StandArt_AQ
D’enregistrer et de lancer la commande bin\buildout.
Puis de démarrer l’instance et d’ajouter le produit.
On peut alors, lorsqu’on est administrateur, ajouter au site Plone une instance de StandArt_FolderQA.
8.7. Création du plan documentaire
197
Plone pour les développeurs, Version 1.0.0
Vous constaterez également en regardant l’onglet Security de la ZMI que nos nouvelles permissions ont été ajoutées.
Si l’on regarde le code du module StandArt_DocumentAQ.py, on constate qu’il contient la définition d’un schéma
Archetypes :
schema = Schema((
FileField(
name=’mDocument’,
widget=FileField._properties[’widget’](
label="Document",
label_msgid="StandArt_DocumentAQ_Document_label",
description="A document under Quality Assurance",
description_msgid="StandArt_DocumentAQ_Document_description",
i18n_domain=’StandArt_AQ’,
),
storage=AttributeStorage(),
read_permission="View",
write_permission="StandArt: write AQ document",
),
),
)
StandArt_DocumentAQ_schema = BaseSchema.copy() + \
getattr(ATDocument, ’schema’, Schema(())).copy() + \
schema.copy()
La variable schema est une liste de champs, qui ici contient la déclaration du champ mDocument. On peut voir
comment sont implémentés les différents marqueurs du champ et comment lui est associé son widget.
Suit la définition de la classe :
class StandArt_DocumentAQ(ATDocument, BrowserDefaultMixin):
"""
"""
security = ClassSecurityInfo()
implements(interfaces.IStandArt_DocumentAQ)
meta_type = ’StandArt_DocumentAQ’
_at_rename_after_creation = True
schema = StandArt_DocumentAQ_schema
##code-section class-header #fill in your manual code here
##/code-section class-header
# Methods
Qui utilise le schéma créé.
Lors des futures générations du code, ArchGenXML conservera les modifications qui auront été apportées aux
modules du produit, et complètera le schéma Archetypes avec les champs ajoutés à la classe dans le modèle UML.
Le modèle et le code généré sont multi-plateforme. Ainsi vous pouvez éditer le modèle sous Windows ou
GNU/Linux sans être lié à l’OS de développement ni à celui de déploiement.
Dans la suite de ce cours nous continuerons notre exposé sous Ubuntu.
En conséquence les chemins devront être adaptés, c’est-à-dire que les séparateurs de fichiers sont des slash au
lieu d’être des anti-slash.
198
Chapitre 8. Génerer un composant Plone avec ArgoUML et ArchgenXML
Plone pour les développeurs, Version 1.0.0
8.8 Création des workflows
Nous allons associer un workflow documentaire à chaque classe créée.
Pour cela sélectionnez StandArt_FolderAQ dans la barre de navigation, puis cliquez sur le bouton droit pour faire
apparaître le menu contextuel. Sélectionnez le menu “Create Diagram” et le sous menu “Diagramme d’état”.
F IGURE 8.6 – Ajout diagramme d’états-transition
Une machine d’états-transitions avec le nom “(Unnamed StateMachine)” est créée dans “StandArt_FolderAQ”.
Depuis la barre de navigation, descendez dans “StandArt_FolderAQ”, puis sélectionnez “(Unnamed StateMachine)”. Renommez le en “standart_folder_workflow”.
Descendez d’un niveau dans “standart_folder_workflow”, s’y trouve le diagramme d’états-transitions “StandArt_FolderAQ 1”, renommez le “standart_folderaq_state_machine”.
Lorsque vous sélectionnez le diagramme “standart_folderaq_state_machine” vous pouvez y créer des états et
transitions. Ainsi vous définissez le workflow de votre objet.
Pour ajouter des états il suffit de sélectionner le symbole correspondant dans la barre d’icônes, de positionner le
curseur dans la zone de dessin et de cliquer.
Pour ajouter des transitions, soit vous sélectionnez l’icône correspondante dans la barre et pouvez alors relier deux
états déjà existants, soit vous sélectionnez un état et cliquez sur l’une des flèches apparaissant à la droite et à la
gauche de l’état sélectionné.
Nous allons créer le workflow de FolderAQ, qui contient un état private et un état published, reliés par une
transition publish.
Les noms des états et transitions doivent être saisi dans le champ Nom accessible par l’onglet Propriétés sur
chaque état et transition.
Les marqueurs des états vont nous permettre de définir qu’elles seront les permissions de l’objet lorsqu’il sera
dans cet état.
ArchGenXML permet de manipuler quatre meta permissions qui sont access, view, modify et list. Ainsi,
à la génération du code, ArchGenXML remplace ces meta par le vrai nom des permissions.
8.8. Création des workflows
199
Plone pour les développeurs, Version 1.0.0
Si l’on veut directement travailler avec les permissions de Plone, il faut créer pour chaque permission une Tag
Definition en cliquant sur l’icône TD de l’onglet Étiquettes et en lui donnant le nom de la permission
voulue.
On peut alors les ajouter comme marqueurs dans l’onglet Étiquettes de l’état. En valeur on précise alors les
rôles qui auront cette permission.
C’est de cette façon que l’on peut préciser qui a les permissions de lecture ou d’écriture que l’on a affecté spécifiquement aux champs de nos objets.
Dans notre cas on crée un TD StandArt: write AQ document pour la permission d’écriture de notre
champ mDocument et l’on donne cette permission au rôle Reviewer dans l’état submited pour que seuls les
membres ayant le rôle Reviewer puissent changer le fichier associé au champ mDocument.
F IGURE 8.7 – workflow de FolderAQ
Il reste à créer de la même façon le workflow de DocumentAQ, qui contiendra trois états nommés private,
submited, published.
Puis éditez les transitions, nommez les submit, publish.
Il est possible d’ajouter aux transitions un garde qui vérifiera que l’on peut ou non réaliser cette transition. Plone
n’affichera la transition que si le garde est vérifié.
Pour créer un garde il suffit de faire un clic droit dans la la zone d’édition du champ Garde dans l’onglet
Propriété de la transition, puis de sélectionner l’item de menu Nouveau.
On entre alors dans le panneau d’édition du garde.
On peut alors expliciter comment on veut filtrer la transition en remplissant le champ Expression. Pour cela
on dispose de trois possibilités :
Prefixe du garde
guard_roles
guard_permissions
guard_expr
Valeur du garde
Une liste de rôles
Liste de permissions
Expression Tales
Exemple
guard_roles : Manager ; Owner
guard_permissions : View
guard_expr :python :object.isOk()
Les expressions peuvent être combinées, par exemple la transition avec le garde
guard_roles:Reviewer|guard_permission:Modify Portal Content ne sera déclenchable
200
Chapitre 8. Génerer un composant Plone avec ArgoUML et ArchgenXML
Plone pour les développeurs, Version 1.0.0
F IGURE 8.8 – workflow de DocumentAQ
F IGURE 8.9 – Garde de la transition publish
8.8. Création des workflows
201
Plone pour les développeurs, Version 1.0.0
que par les modérateurs ayant les droits de modification.
En plus des gardes, il est possible d’exécuter des scripts avant et après transition. Pour cela il faut ajouter une
conséquence à la transition comme on l’a fait pour le garde. Le nom de la conséquence sera le nom de la fonction
python appelée lors de la transition.
F IGURE 8.10 – Garde et conséquence
ArchGenXML va créer une arborescence Products.StandArt_AQ/Products/StandArt_AQ/profiles/default/wo
contenant un répertoire contenant la définition XML du workflow pour FolderAQ et un autre pour DocumentAQ.
Il génère aussi un fichier wfsubscribers.py contenant la définition des scripts appelés lors des transitions :
#
#
#
#
#
#
#
#
#
#
-*- coding: utf-8 -*File: wfsubscribers.py
Copyright (c) 2010 by Michael Launay <[email protected]>
Generator: ArchGenXML Version 2.5.dev
http://plone.org/products/archgenxml
GNU General Public License (GPL)
__author__ = """Michael Launay <[email protected]>"""
__docformat__ = ’plaintext’
##code-section module-header #fill in your manual code here
##/code-section module-header
def onSubmitDocumentAQ(obj, event):
"""generated workflow subscriber."""
# do only change the code section inside this function.
if not event.transition \
or event.transition.id not in [u’submit’] \
or obj != event.object:
return
##code-section onSubmitDocumentAQ #fill in your manual code here
##/code-section onSubmitDocumentAQ
##code-section module-footer #fill in your manual code here
##/code-section module-footer
202
Chapitre 8. Génerer un composant Plone avec ArgoUML et ArchgenXML
Plone pour les développeurs, Version 1.0.0
Pour ajouter du code spécifique tel que la notification par mail des principaux modérateurs, il suffit de le mettre entre la balise ##code-section onSubmitDocumentAQ et ##/code-section
onSubmitDocumentAQ, mais si le code est volumineux ou doit être partagé nous pouvons le mettre dans
un module qui sera importé dans la section ##code-section module-header et l’appel se fera dans la
section ##code-section onSubmitDocumentAQ.
L’événement event contient l’objet qui subit la transition, ainsi que la requête.
Si vous voulez faire de l’introspection et voir ce qui se passe vous pouvez mettre un pdb.set_trace () dans
la section, relancer Zope en mode foreground bin/instance fg, et déclencher la transition. Python s’arrêtera
sur le point d’arrêt et en tapant la commande h vous aurez la liste des commandes possibles :
##code-section onSubmitDocumentAQ #fill in your manual code here
import pdb
pdb.set_trace ()
##/code-section onSubmitDocumentAQ
Il est possible d’associer un workflow créé pour un type de contenu donné à un autre type de contenu avec
l’étiquette “use_workflow”, il suffit alors de donner le nom du workflow comme valeur à l’étiquette.
8.8.1 Ajout d’une worklist
Il est possible pour un état donné d’un objet de le déclarer comme devant apparaître dans une liste de modération.
Pour cela il suffit d’ajouter le marqueur worklist à la liste des étiquettes de l’état dans lequel l’objet doit être
modéré.
La valeur est alors le nom de la woklist dans laquelle devrait apparaître l’objet, mais la liste de modération de
Plone regroupe tous les objets associés à une worklist.
Le marqueur worklist:guard_roles permet de restreindre la notification de modération aux rôles donnés
en valeur au marqueur.
8.8.2 Ajout d’un validateur sur un champ
Les #validators# permettent de vérifier la cohérence des valeurs des champs saisis. Ils sont appelés à la saisie des
champs en mode d’édition online ou à l’enregistrement des modifications réalisées sur le contenu en mode édition.
Les champs de type Archetypes utilisent des objets implémentant l’interface IValidator.
Lors de la saisie du champ, les validateurs associés à un champ sont appelés successivement. Dés que l’un deux
retourne une valeur différente de True, le champ est en erreur et un message correspondant est positionné dans la
variable error.
Par défaut Plone propose plusieurs instances de validators en voici les plus utiles :
8.8. Création des workflows
203
Plone pour les développeurs, Version 1.0.0
Nom
isDecimal
Usage
L’entrée est elle un un nombre décimal ?
isInt
isPrintable
Est-ce un entier ?
Ne doit pas contenir de caractères non
imprimables
Est un numéro de téléphone international ?
isInternationalPhone
Number
isURL
Est-ce une url ?
isEmail
Est-ce une adresse mél ?
isMailTo
isUnixLikeName
isMaxSize
Est-ce une adresse mél précédée par “mailto :”
Est-ce un nom respectant le style Unix ?
isValidDate
isEmpty
isEmptyNoError
isValidId
isTidyHtml
isTidyHtmlWithCleanup
isNonEmptyFile
isTAL
Vérifie si un fichier uploadé ou un objet
supportant l’opération len() est plus petit
qu’une valeur donnée.
Est-ce que la chaîne de caractères peut être
convertie en date ?
La valeur doit être vide.
La valeur doit être vide.
La valeur doit correspondre à l’identifiant d’un
objet.
Utilise mx.Tidy pour valider l’entrée HTML.
Utilise mx.Tidy pour valider l’entrée HTML.
Détails
Autorise la notation
scientifique.
r’[a-zA-Z0-9s]+$’
Vérifie le format et ignore les
espaces, parenthèse et barres
obliques.
Reconnait la plus part des
protocoles.
Vérifiée par une expression
régulière.
r”^[A-Za-z][wd-_] {0,7}$”
Vérifie à partir de la valeur
maxsize définit sur le champ.
L’erreur ne sera pas montrée.
Échoue sur les erreurs et les
alertes.
Échoue sur les erreurs mais
nettoie le code des alertes.
Vérifie que le fichier uploader n’est pas vide.
Est-ce une expression Template Attribute
Language ?
Pour les utiliser il suffit d’ajouter “validators” comme étiquette au champ avec pour valeur le nom du ou des
validators à utiliser.
Plone offre également la possibilité de paramétrer des validators en les instanciant avec des paramètres puis en
utilisant comme validator ces instances.
Par exemple ExpressionValidator qui permet de définir un validator utilisant une expression TALES pour dire si
la valeur saisie est correcte ou non.
L’instanciation de ce validator est supportée par ArchGenXML via les étiquettes validation_expression pour l’expression et validation_expression_errormsg pour le message d’erreur associé.
RegexValidator du paquet Products.validation.validators.RegexValidator permet d’utiliser une expression régulière pour la validation, il faut l’instancier et l’enregistrer dans le code généré dans les parties réservées au code
manuel.
RangeValidator permet quant à lui de vérifier qu’une valeur est comprise entre deux bornes. Il est dans Products.validation.validators.RangeValidator.
Nous allons voir comment créer un validator qui rappelle une méthode de notre objet.
Pour cela nous commençons par créer un fichier objectcallbackvalidation.py qui contiendra un validator appelant
une méthode dont le nom sera dynamiquement calculée à partir du nom du champ validé. Par convention nous
imposons que la méthode ait un nom de la forme is_a_valid_nomduchamp sur la classe du contenu :
from Products.validation.interfaces.IValidator import IValidator
from zope.interface import implements
204
Chapitre 8. Génerer un composant Plone avec ArgoUML et ArchgenXML
Plone pour les développeurs, Version 1.0.0
class ObjectCallbackValidation:
implements(IValidator)
def __init__(self,name):
self.name = name
def __call__(self, value, *args, **kwargs):
fieldname = kwargs[’field’].getName()
instance = kwargs[’instance’]
method = "is_a_valid_"+fieldname
# callback the object vaildation methode for the field
return getattr(instance, method)(value)
object_callback_validation = ObjectCallbackValidation("object_callback_validation")
# validator registration
from Products.validation import validation
validation.register(object_callback_validation)
Pour qu’au chargement de notre module ce code soit appelé il nous suffit d’en faire un import dans la fonction
“initialize” du fichier __init__.py du produit.
Ensuite il suffit de créer une méthode “is_a_valid_nomDeMonChamp” dans notre modèle sur notre classe pour
chacun des champs à qui on a déclaré object_callback_validation pour validator.
8.8.3 Ajout d’une méthode
L’ajout d’une méthode est simple puisqu’il suffit de l’ajouter à l’une des classes de nos contenus.
Par exemple si nous voulons écrire une méthode que l’on utilisera comme validator à l’aide du code écrit précédemment pour le champ mDocument du type de contenu StandArtDocumentAQ pour vérifier que le fichier
uplaoder est un pdf.
F IGURE 8.11 – Ajout d’une méthode
La génération du code produit la méthode suivante dans le code StandArt_DocumentAQ.py :
8.8. Création des workflows
205
Plone pour les développeurs, Version 1.0.0
security.declarePublic(’is_a_valid_mDocument’)
def is_a_valid_mDocument(self):
"""
"""
pass
Important : En l’abscence de précision, la méthode générée est accessible par toute page template ou pythonscript
quelque soit le rôle de l’utilisateur.
Pour la protéger, il faut ajouter l’étiquette “permission” avec pour valeur le nom de la permission à avoir par
exemple “Modify portal content”.
Cette exposition est due à la présence de la documentation de la méthode qui est interprétée par Plone comme la
volonté à exposer la méthode.
Ajoutons au modèle le pramètre “value” à la méthode de type None (les informations de types ne sont pas utilisés
par ArchGenXML) et générons le code à nouveau.
Nous pouvons maintenant remplacer l’instruction pass par notre code :
##code-section module-header #fill in your manual code here
import pyPdf
from Products.StandArt_AQ import StandArt_AQFactory as _
##/code-section module-header
#...
class StandArt_DocumentAQ(ATDocument, BrowserDefaultMixin):
#...
security.declareProtected("Modify portal content", ’is_a_valid_mDocument’)
def is_a_valid_mDocument(self,value):
""" Test if the file is a pdf ?
"""
try:
tell = value.tell()
value.seek(0)
f = pyPdf.PdfFileReader(value) #imported at the top of the file
f.documentInfo #nothing is done yet
value.seek(tell)
return 1
except:
return _(u’file must be a pdf’)
Pour la traduction il faut ajouter StandArt_AQFactory dans le __init__.py
from zope.i18nmessageid import MessageFactory
StandArt_AQFactory = MessageFactory(’StandArt’)
Maintenant nous pouvons ajouter l’étiquette “validators” à mDocument avec pour valeur “object_callback_validation”.
8.8.4 Ajout d’une validation sur l’ensemble de l’édition
Lorsque plusieurs champs sont liés entres eux il n’est possible de tester leur validité qu’à l’enregistrement de
l’instance du contenu.
Il suffit d’ajouter la méthode post_validate(self, REQUEST, errrors) au type de contenu. Si l’on constate des
erreurs, on les remonte en remplissant le dictionnaire “errors” :
206
Chapitre 8. Génerer un composant Plone avec ArgoUML et ArchgenXML
Plone pour les développeurs, Version 1.0.0
security.declarePublic(’post_validate’)
def post_validate(self,REQUEST,errors):
""" Check if the pdf info version match the DocumentNumber
"""
if not REQUEST : return
pdf_file = REQUEST.has_key(’mDocument_file’) and \
REQUEST.form[’mDocument_file’] or None
version_number = REQUEST.has_key(’mDocumentNumber’) and \
REQUEST.form[’mDocumentNumber’] or None
m_document = self.getMDocument()
m_document_number = self.getMDocumentNumber()
if pdf_file is None and version_number is None or \
pdf_file is None and not m_document or \
version_number is None and m_document_number is None :
return
if pdf_file is None :
pdf_file = StringIO.StringIO(m_document)
if version_number is None :
version_number = m_document_number
tell = pdf_file.tell()
pdf_file.seek(0)
infos = pyPdf.PdfFileReader(pdf_file).documentInfo
keywords = infos[’/Keywords’]
pdf_file.seek(tell)
str_version = u"Version : "
posv = keywords.find(str_version)
if posv != -1 :
version = keywords[posv + len(str_version):].split()[0]
if version != version_number :
errors[’mDocumentNumber’] = _("Document numbers doesn’t match")
Il est possible aussi de surcharger la méthode pre_validate qui est appellée avant l’appel individuel des “validators”
des champs.
Important : Dans le cas d’une édition en ligne c’est à dire sans passer par l’onglet Modifier la méthode
post_validate n’est pas appelée.
8.8.5 Ajout d’une action
En stéréotypant une méthode par “Action” nous allons ajouter un onglet à la barre d’édition de l’instance d’action.
Cet onglet renvoie allors sur une page template que l’on peut définir.
Par exemple nous allons ajouter un onglet “Pdf Informations” à notre DocumentAQ :
Le nom de l’onglet, son identifiant, la permission d’accès, le nom de la template associée, et la condition d’affichage sont données comme valeurs des étiquettes correspondantes.
L’action est alors ajoutée dans la déclaration xml du type StandArt_AQ/profiles/default/types/StandArt_DocumentAQ.xml :
<action title="Pdf Informations"
i18n:attributes="title"
action_id="pdf_informations"
category="object"
condition_expr="python:object.getMDocument()"
url_expr="string:${object_url}/document_aq_pdf_informations"
visible="True">
<permission value="View"/>
Nous pouvons alors créer la template StandArt_AQ/skins/standart_aq_templates/document_aq_pdf_informations.pt
qui contient :
8.8. Création des workflows
207
Plone pour les développeurs, Version 1.0.0
F IGURE 8.12 – Ajout d’une action
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
xmlns:tal="http://xml.zope.org/namespaces/tal"
xmlns:metal="http://xml.zope.org/namespaces/metal"
xmlns:i18n="http://xml.zope.org/namespaces/i18n"
lang="en"
metal:use-macro="here/main_template/macros/master"
i18n:domain="plone">
<body>
<div metal:fill-slot="main">
<table>
<tbody tal:define="pdf_informations python:context.getPdfInformations()">
<tr tal:repeat="keyword pdf_informations">
<td tal:content="keyword">Title</td>
<td tal:content="python:pdf_informations[keyword]">Value</td>
</tr>
</tbody>
</table>
</div>
</body>
</html>
Cette template appèle une méthode du type de contenu appelée getPdfInformations qui retourne le dictionnaire
des informations. Pour la définir il nous suffit d’ajouter cette méthode comme nous avons fait pour les méthodes
précédentes.
security.declareProtected("View", ’getPdfInformations’)
def getPdfInformations(self):
""" Return the pdf document informations
"""
document = self.getMDocument()
if not document :
return {}
strfile = StringIO.StringIO(document)
documentInfo = pyPdf.PdfFileReader(strfile).documentInfo
return documentInfo.copy() #The copy is needed by security check
208
Chapitre 8. Génerer un composant Plone avec ArgoUML et ArchgenXML
Plone pour les développeurs, Version 1.0.0
Il faut ajouter l’import
8.8.6 Ajout d’un portlet d’affichage des Documents soumis
La portlet de modération est générique, il est possible de créer ses propres portlets et par exemple d’en créer des
dédiées à la modération de nos types de données à l’exclusion des autres.
Pour ajouter une portlet il suffit de créer une classe et de lui mettre le stéréotype “portlet”.
F IGURE 8.13 – Ajout d’une portlet
La génération du module avec ArchGenXML produit deux fichiers.
Le fichier StandArt_AQ/profiles/default/portlets.xml contient la déclaration de la portlet :
<portlets>
<portlet
addview="Products.StandArt_AQ.content.StandArt_WorkList.StandArt_WorkList"
title="StandArt_WorkList"
description=""
/>
<!-- ##code-section PORTLETS -->
<!-- ##/code-section PORTLETS -->
</portlets>
On y voit le nom de la portlet et la vue associée.
Si l’on ouvre le fichier de la vue générée StandArt_AQ/content/StandArt_WorkList.py on y voit
principalement :
class IStandArt_WorkList(IPortletDataProvider):
"""A portlet which renders the cart.
"""
class Assignment(base.Assignment):
implements(IStandArt_WorkList)
title = _(u’StandArt_WorkList’)
##code-section assignment-body #fill in your manual code here
8.8. Création des workflows
209
Plone pour les développeurs, Version 1.0.0
##/code-section assignment-body
class Renderer(base.Renderer):
render = ViewPageTemplateFile(’templates/StandArt_WorkList.pt’)
##code-section renderer-body #fill in your manual code here
##/code-section renderer-body
class AddForm(base.NullAddForm):
def create(self):
return Assignment()
##code-section addform-body #fill in your manual code here
##/code-section addform-body
Pour remplir et rendre fonctionnel ce code lisez le chapitre Les portlets.
Dans notre cas pour réaliser la worklist il faut faire :
#...
from Products.CMFCore.utils import getToolByName
#...
class Renderer(base.Renderer):
render = ViewPageTemplateFile(’templates/StandArt_WorkList.pt’)
##code-section renderer-body #fill in your manual code here
@memoize
def _data(self, target):
if self.anonymous:
return []
context = aq_inner(self.context)
ctool = getToolByName(context, ’portal_catalog’)
wtool = getToolByName(context, ’portal_workflow’)
ttool = getToolByName(context, ’portal_types’)
ENM_AMC = ttool[’StandArt_DocumentAQ’]
wfs = wtool.getWorkflowsFor(’StandArt_DocumentAQ’)
review_states = []
for wf in wfs:
wl = wf.worklists._objects
wld = wf.worklists._mapping[wl[0][’id’]]
review_states.extend(wld.var_matches[’review_state’])
return ctool.searchResults(meta_type = ’StandArt_DocumentAQ’,
review_state = review_states)
##/code-section renderer-body
Et la page template StandArt_AQ/content/templates/StandArt_WorkList.pt
<dl class="portlet portletStandArt_WorkList"
i18n:domain="StandArt_AQ">
<dt class="portletHeader">
<span class="portletTopLeft"></span>
<h3>
<span i18n:domain="plone" i18n:translate="box_review_list">
Review List
</span>
<span tal:content="view/data/search_filter"/>
</h3>
<span class="portletTopRight"></span>
</dt>
<tal:items tal:repeat="b view/review_items">
<dd class="portletItem"
tal:define="oddrow repeat/b/odd;
review_state b/review_state;"
210
Chapitre 8. Génerer un composant Plone avec ArgoUML et ArchgenXML
Plone pour les développeurs, Version 1.0.0
tal:attributes="class python:oddrow and ’portletItem even’ or
’portletItem odd’">
<a href="#"
tal:attributes="href string:${b/getPath}/view;
title b/Description;
">
<span tal:replace="b/Title">
Title
</span>
<span class="portletItemDetails">
<span tal:replace="b/Creator">Jim Smith</span> —
<span tal:replace="b/modified"> May 5</span>
</span>
</a>
</dd>
</tal:items>
<dd class="portletFooter">
<span class="portletBottomLeft"></span>
Footer
<span class="portletBottomRight"></span>
</dd>
</dl>
8.8.7 Modification de la vue d’édition
Plone propose un mécanisme trés simple de modification de la vue d’édition d’un type donné.
Il suffit de créer un fichier template dans le répertoire skins basé sur le nom du type mis en minucule et suivit du
postfixe _edit par exemple pour modifier la vue d’édition de StandArt_DocumentAQ il suffit de créer le fichier
StandArt_AQ/skins/standart_aq_templates/standart_documentaq_edit.pt qui contient
alors les slots de la template de base que l’on veut surcharger.
Par exemple si l’on souhaite “Faire un truc super cool” juste avant l’affichage du champ mDocumentNumero on
peut faire des choses compliquées comme :
<metal:macro define-macro="body">
<metal:use-macro use-macro="here/edit_macros/macros/body">
<metal:fill-slot fill-slot="widgets">
<tal:tabbed tal:condition="allow_tabbing | nothing">
<fieldset tal:define="sole_fieldset python:len(fieldsets) == 1"
tal:repeat="fieldset fieldsets"
tal:attributes="id string:fieldset-${fieldset}"
tal:omit-tag="sole_fieldset">
<legend id=""
tal:content="python: view.getTranslatedSchemaLabel(fieldset)"
tal:attributes="id string:fieldsetlegend-${fieldset}"
tal:condition="not:sole_fieldset" />
<tal:fields repeat="field python:schematas[fieldset].editableFields(here, visible_only=True)">
<div tal:condition="python:field.getName() == ’mDocumentNumero’">
Faire un truc super cool !
</div>
<metal:fieldMacro use-macro="python:here.widget(field.getName(), mode=’edit’)" />
</tal:fields>
</fieldset>
</tal:tabbed>
<tal:nottabbed tal:condition="not: allow_tabbing | nothing">
<tal:fields repeat="field python:schematas[fieldset].editableFields(here, visible_only=True)">
<metal:fieldMacro use-macro="python:here.widget(field.getName(), mode=’edit’)" />
</tal:fields>
8.8. Création des workflows
211
Plone pour les développeurs, Version 1.0.0
</tal:nottabbed>
</metal:fill-slot>
</metal:use-macro>
</metal:macro>
Pour savoir quoi surcharger, il faut aller voir les templates archetypes/edit_macro et archetypes/base_edit .
8.8.8 Modification de la vue de consultation
La vue de consultation repose sur le même principe sauf que la template de base doit porter le nom du type en
minuscule postfixé de _view et que la template de base est archetypes/base_view
8.9 exercice
Création des fondements du modèle de l’application “StandArt_AQ”.
212
Chapitre 8. Génerer un composant Plone avec ArgoUML et ArchgenXML
CHAPITRE 9
Nouvelles fonctionnalités de
ArchGenXML
9.1 Définition
La dernière version de ArchGenXML permet de modéliser plus de chose que dans les versions précédentes.
9.2 Savoir
– modélisation des portlets
– modélisation des vues zope 3
9.3 Modélisation d’un portlet
Créez un package portlets et créez une classe avec le stéréotype portlet_class.
9.4 Modélisation de vue zope 3
Regardez le modèle de Products.rendezvous sur pypi :
$ cd /tmp/
$ easy_install -b. -e Products.rendezvous
Ouvrez le modèle /tmp/products.rendezvous/model/rendezvous.zargo avec ArgoUML.
Procédure :
– Créez un package browser, ajoutez y une classe MyView avec le stéréotype view_class. Vous pouvez préciser
le stéréotypes name “myview” et permission “cmf.ModifyPortalContent” par exemple.
– Créez un package content avec une classe MyContent avec le stéréotype archetypes.
– Créez une dépendance de MyView vers MyContent.
Vous aurez accès à la vue @@myview seulement si le context est un document MyContent et que vous avez la
permission cmf.ModifyPortalContent.
213
Plone pour les développeurs, Version 1.0.0
9.5 Exercice
Mise en pratique sur le composant “base de connaissances”
214
Chapitre 9. Nouvelles fonctionnalités de ArchGenXML
CHAPITRE 10
Création d’un thème Plone
Ajout de nouveaux composants visuels à Plone.
10.1 Installation de Gloworm
Gloworm est un outil pour introspecter les viewlets d’une page Plone, très utile pour savoir comment customiser
telle partie de la page.
Gloworm est une interface alternative à portal_skins/custom et portal_view_customizations. On peut cliquer sur
l’endroit que l’on veut customiser et cliquer sur customiser. Le produit ne fonctionne pas avec Plone 4. Un produit
qui le remplace sur Plone 4 est probablement plone.app.themeeditor.
Créez un fichier development.cfg :
[buildout]
extends = buildout.cfg
[instance]
eggs +=
Products.Gloworm
Reexécutez bin/buildout -c development.cfg.
10.2 Création d’un theme
Dans le dossier src/, créez un theme avec la commande :
$ cd /tmp
$ paster create -t plone3_theme formation.theme \
--svn-repository=http://devagile/Formation/packages
Skin Name : Formation Theme
Empty Styles? : False
Include Documentation? : True
$ svn rm --force formation.theme/formation.theme.egg-info \
formation.theme-configure.zcml
formation/theme/skins/formation_theme_styles/base.css.dtml \
formation/theme/skins/formation_theme_styles/portlets.css.dtml \
formation/theme/skins/formation_theme_styles/public.css.dtml \
formation/theme/version.txt
formation/theme/skins/formation_theme_styles/base_properties.props
215
Plone pour les développeurs, Version 1.0.0
base_properties.props n’est plus utilisé dans Plone 4.
Éditez setup.py pour supprimer les deux lignes :
setup_requires=["PasteScript"],
paster_plugins=["ZopeSkel"]
Éditez formation/theme/profiles/default/skins.xml pour remplacer based-on=”Plone Default” par based-on=”Sunburst Theme”.
Et commitez :
$ svn ci -m"Initial theme structure"
Retournez dans votre buildout, ajoutez formation.theme dans auto-checkout du fichier sources.cfg,
ainsi que dans eggs de la section [instance] de buildout.cfg, commitez et relancez le buildout.
10.3 Exercice 1
Changer la couleur des documents dans l’état “En attente de modération” (pending). Utilisez l’extension Firebug
de firefox pour récupérer la règle css utilisé par défaut pour colorisé le lien en orange. Copiez ensuite cette règle
dans browser/stylesheets/main.css et changez la couleur :
.state-pending {
color:#FFA5FF !important;
}
Passez portal_css en mode debug comme ça vous ne devrez pas redémarrer l’instance à chaque fois que vous
modifiez les css.
10.4 Exercice 2 : Création d’une viewlet
Créez une viewlet affichant une image dans le footer.
Référez-vous au chapitre Les viewlets pour consulter la documentation complète sur cette notion.
10.4.1 Ajouter une image
Ajoutez une image dans le dossier browser/images/. Pour accéder ensuite à l’image, vous devez utiliser le
nom de la resourceDirectory : ++resource++formation.theme.images, suivit de / et du nom de votre image dans
le dossier images. Exemple : ++resource++formation.theme.images/monimage.png
Éditez le fichier browser/viewlets.py, décommentez la classe, nommez la LogoViewlet. Copiez le fichier
viewlet.pt en logoviewlet.pt.
Contenu du fichier logoviewlet.pt :
<img tal:define="portal_state context/@@plone_portal_state;
root_url portal_state/portal_url"
tal:attributes="src string:$root_url/++resource++formation.theme.images/image.png" />
Éditez le fichier configure.zcml pour déclarer votre viewlet. Vous avez un exemple en commentaire. Il suffit
de le décommenter et d’ajuster le nom de la viewlet et la classe. Cela donne :
216
Chapitre 10. Création d’un thème Plone
Plone pour les développeurs, Version 1.0.0
<browser:viewlet
name="formation.logoviewlet"
manager="plone.app.layout.viewlets.interfaces.IPortalFooter"
class=".viewlets.LogoViewlet"
layer=".interfaces.IThemeSpecific"
permission="zope2.View"
/>
Maintenant au lieu d’afficher votre LogoViewlet dans le PortalFooter, vous allez l’afficher dans portalheader en
précisant que vous le voulez après le logo de plone. Dans configure.zcml, changez l’interface du viewletmanager
en IPortalHeader :
<browser:viewlet
name="formation.logoviewlet"
manager="plone.app.layout.viewlets.interfaces.IPortalHeader"
class=".viewlets.LogoViewlet"
layer=".interfaces.IThemeSpecific"
permission="zope2.View"
/>
Éditez ensuite profiles/default/viewlets.xml :
<?xml version="1.0"?>
<object>
<order manager="plone.portalheader" skinname="formation’s theme"
based-on="Plone Default">
<viewlet name="zdevan.logoviewlet"
insert-after="plone.logo" />
</order>
</object>
Relancez l’instance et réinstallez votre produit pour prendre en compte le changement.
10.5 Portlets
– http ://plone.org/documentation/manual/portlets-developer-manual
Créez le portlet :
$ cd /tmp
$ paster create -t plone3_portlet formation.portlet.docinfo \
--svn-repository=http://devagile/Formation/packages
–
–
–
–
–
–
Supprimez le répertoire egg-info, le fichier version.txt
Ajoutez ensuite l’entry point z3c.autoinclude.plugin dans setup.py
Commitez
Ajoutez ce nouveau egg dans votre buildout.cfg et sources.cfg
Relancez le buildout
Commitez les changements fait dans le buildout
Démarrez l’instance, installez le produit et ajoutez le portlet.
Éditez le fichier docinfoportlet.py et ajoutez un champ informations au schema IDocInfoPortlet :
informations = schema.List(title=_(u"Information list"),
description=_(u"A list of information about the document"),
required=True,
value_type=schema.Choice(values=(’Creator’,’effective’))
)
Modifiez également l’implémentation :
10.5. Portlets
217
Plone pour les développeurs, Version 1.0.0
class Assignment(base.Assignment):
"""Portlet assignment.
This is what is actually managed through the portlets UI and associated
with columns.
"""
implements(IDocInfoPortlet)
informations = ()
def __init__(self, informations=()):
self.informations = informations
Dans la template, afficher les informations qui ont été choisies via le formulaire du portlet :
<dl class="portlet portletDocInfoPortlet"
i18n:domain="formation.portlet.docinfo">
<dt class="portletHeader">
<span class="portletTopLeft"></span>
Information
<span class="portletTopRight"></span>
</dt>
<dd class="portletItem odd" tal:define="informations view/data/informations;
plone_view context/@@plone;
mtool context/portal_membership" >
<strong>Created by :</strong>
<span tal:condition="python:’Creator’ in informations"
tal:replace="python:mtool.getMemberById(context.Creator()).getProperty(’fullname’,co
<strong>Date of creation :</strong>
<span i18n:translate="text_effective_date">
<span tal:condition="python:’effective’ in informations"
tal:content="python:plone_view.toLocalizedTime(context.effective())" />
</span>
<tal:block condition="python:’ExpirationDate’ in informations">
<span i18n:translate="text_expiration_date"
tal:condition="context/ExpirationDate">
<strong>Expiration :</strong>
<span i18n:name="date"
tal:content="python:plone_view.toLocalizedTime(context.ExpirationDate())" />
</span>
</tal:block>
</dd>
</dl>
Ajoutez une property available dans la classe Renderer pour afficher le portlet seulement pour les documents :
class Renderer(base.Renderer):
"""Portlet renderer.
This is registered in configure.zcml. The referenced page template is
rendered, and the implicit variable ’view’ will refer to an instance
of this class. Other methods can be added and referenced in the template.
218
Chapitre 10. Création d’un thème Plone
Plone pour les développeurs, Version 1.0.0
"""
@property
def available(self):
return self.context.meta_type == "ATDocument"
render = ViewPageTemplateFile(’docinfoportlet.pt’)
10.6 GenericSetup
– http ://plone.org/documentation/tutorial/genericsetup
– http ://www.sixfeetup.com/swag/generic-setup-quick-reference-card
10.6. GenericSetup
219
Plone pour les développeurs, Version 1.0.0
220
Chapitre 10. Création d’un thème Plone
CHAPITRE 11
Les viewlets
“L’amour c’est un bouquet de viewlets” (adapté de “Violettes impériales” par Vincent Scotto)
Author Gilles Lenfant
Contributors Thomas Desvenain
Version 1.0.0
Copyright (C) 2010 Gilles Lenfant.
Chacun est autorisé à copier, distribuer et/ou modifier ce document suivant les termes de la licence
Paternité-Pas d’Utilisation Commerciale-Partage des Conditions Initiales à l’Identique 2.0 France accessible à
http ://creativecommons.org/licenses/by-nc-sa/2.0/fr
Le code source présent dans ce document est soumis aux conditions de la “Zope Public License”, Version 2.1
(ZPL).
THE SOURCE CODE IN THIS DOCUMENT AND THE DOCUMENT ITSELF IS PROVIDED “AS IS” AND
ANY AND ALL EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT,
AND FITNESS FOR A PARTICULAR PURPOSE.
La lecture de ceci nécessite la connaissance préalable du langage ZCML, ainsi que de l’outil GenericSetup.
11.1 Introduction
Les différentes pages d’un site Plone sont publiées selon le modèle de la main_template depuis la version 1
de Plone. C’est toujours le cas, mais...
Dans les versions antérieures de Plone, cette template faisait appel à de nombreuses macros pour déployer les
différents blocs de code HTML nécessaire à l’affichage d’une page.
Même si ceci est toujours - partiellement - vrai, pour aider le support des composants prévus pour les versions
antérieures de Plone, l’utilisation des macros est remplacée par celle des content providers.
L’inclusion du HTML produit par un content provider dans la main_template ressemble à ceci :
<div id="portal-top" i18n:domain="plone">
<div tal:replace="structure provider:plone.portaltop" />
</div>
Vous remarquez la présence de l’expression TAL provider. Dans le monde Zope 3, un provider est un multi
adapter qui adapte le context, la request et la vue. Ici le provider est un viewlet manager.
Les viewlets fournissent une portion de code HTML. Plone passe par des viewlet managers - une sorte d’aggrégateur de viewlets - qui permettent d’ordonnancer les viewlets avec un maximum de flexibilité.
221
Plone pour les développeurs, Version 1.0.0
Cette souplesse permise par les viewlet managers rend de moins en moins nécessaire la surcharge de la
main_template lors de la réalisation de vos skins personnelles. Comme nous le verrons plus loin, vous pourrez
également lors d’une personnalisation graphique “extrême”, ajouter vos propres viewlet managers.
11.2 Gestion interactive des viewlets
Pour mieux illustrer les propos ci-avant, Plone fournit un outil visualisant l’articulation des viewlet managers et
l’arrangement des viewlets dans ceux-ci. Ouvrez - étant authentifié comme administrateur - un navigateur sur la
racine de vote site Plone et prolongez l’URL par /@@manage-viewlets.
F IGURE 11.1 – Vue @@manage-viewlets : ordonnancement et masquage des viewlets
Immédiatement, sous chaque cadre, vous trouvez le nom du viewlet manager, dont le plone.portaltop de
l’extrait précédent.
A l’intérieur de chaque viewlet manager, vous pouvez voir la liste des viewlets qu’il fournit. Chaque nom de
viewlet est accompagné :
– de son rang - commençant par 0 - dans le viewlet manager entre parenthèses,
– de deux flèches permettant de monter ou descendre l’ordre d’apparition de la viewlet dans le viewlet manager,
– du lien hide permettant de masquer la viewlet.
Avec cet outil, en remontant la viewlet plone.global_sections en tête en quelques clics, on obtient le
résultat suivant :
Vous aurez également remarqué l’absence des colonnes de portlets dans le gestionnaire de viewlets. Celles-ci sont
gérées dans un gestionnaire dédié, objet du chapitre sur Les portlets.
222
Chapitre 11. Les viewlets
Plone pour les développeurs, Version 1.0.0
F IGURE 11.2 – La viewlet plone.global_sections remontée en tête
11.3 L’enregistrement en ZCML
Les viewlet managers standard de Plone, ainsi que la plupart des viewlets standard sont enregistrés dans le
configure.zcml du package plone.app.layout.viewlets dont voici un extrait :
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser">
<!-- Register viewlet managers - used in plone’s main_template -->
<browser:viewletManager
name="plone.htmlhead"
provides=".interfaces.IHtmlHead"
permission="zope2.View"
/>
<browser:viewletManager
name="plone.htmlhead.links"
provides=".interfaces.IHtmlHeadLinks"
permission="zope2.View"
/>
...
<!-- Define some viewlets -->
...
<!-- Render the browser title -->
<browser:viewlet
name="plone.htmlhead.title"
11.3. L’enregistrement en ZCML
223
Plone pour les développeurs, Version 1.0.0
manager=".interfaces.IHtmlHead"
class=".common.TitleViewlet"
permission="zope2.View"
/>
<!-- Render the workflow history -->
<browser:viewlet
name="plone.belowcontentbody.workflowhistory"
manager=".interfaces.IBelowContentBody"
class=".content.WorkflowHistoryViewlet"
permission="zope2.View"
/>
...
</configure>
Vous remarquez les deux éléments viewletManager et viewlet qui enregistrent - respectivement :
– les viewlet managers utilisables dans une template comme la main_template,
– les viewlets pouvant être associées aux différents types de viewlet manager.
Les attributs d’un élément “viewletManager” :
224
Chapitre 11. Les viewlets
Plone pour les développeurs, Version 1.0.0
TABLE 11.1 – Attributs d’un élément viewletManager (* = obligatoire)
Attribut
name *
permission
*
for
Description
Définition du nom du viewlet manager. Doit être unique dans l’instance Zope.
Permission nécessaire pour voir le viewlet manager. Généralement zope2.View.
Interface des contenus pour lesquels ce viewlet manager peut être utilisé. Défaut :
zope.interface.Interface Exemple :
Products.ATContentTypes.interface.IATDocument. Vous pouvez utiliser *
pour signaler que le viewlet manager peut être utlisé dans n’importe quel contexte, ce qui
est le cas la plupart du temps.
layer
Interface de skin spécifique pour laquelle le viewlet manager peut être utilisé. Ce paramètre
n’a d’intérêt que lors de la réalisation d’une skin, comme dans le cas du composant
NuPlone. Par défaut :
zope.publisher.interfaces.browser.IDefaultBrowserLayer.
view
Interface de view pour laquelle le viewlet manager peut être utilisé. Défaut :
zope.publisher.interfaces.browser.IBrowserView. Exemple :
plone.app.content.browser.interfaces.IFolderContentsView. La
valeur par défaut convient dans l’immense majorité des cas.
provides
interface marqueur spécifique à ce viewlet manager. Bien que cet attribut soit facultatif, il
est nécessaire, comme nous le verrons plus loin, de l’associer à une interface marqueur
spécifique afin de pouvoir y associer des viewlets. Par défaut :
zope.viewlets.interfaces.IViewletManager.
class *
Éventuelle classe Python spécifique de ce viewlet manager. La classe par défaut est
suffisante dans la majorité des cas. Si vous voulez donner à un administrateur la possibilité
d’ordonner les viewlets - avec le panneau de contrôle vu plus haut - inscrites dans ce viewlet
manager, la classe devra être :
plone.app.viewletmanager.manager.OrderedViewletManager. Par
défaut : zope.viewler.manager.ViewletManagerBase.
template *
Éventuelle template devant être utilisée pour ce viewlet manager. Ceci peut être utilisé dans
certains cas en alternative à l’attribut class vu ci-avant. Pour illustrer ceci, je vous invite à
lire le code de Products.ResourceRegistries. À noter que cette façon d’opérer est
rarement utilisée. Par défaut : None. Attention, vous devez fournir l’attribut class ou
l’attribut template mais en aucun cas les deux.
allowed_interface
Interface exposant les méthodes publiques de ce viewlet manager. Généralement inutile. Par
défaut : None
allowed_attributes
Liste d’attributs et méthodes publics (séparés par des espaces) de ce viewlet manager. Par
défaut : None
Warning : Dans la définition ZCML d’un viewlet manager comme décrite ci-dessus, vous devez fournir
l’attribut class ou l’attribut template mais en aucun cas les deux. De même, il est inutile de fournir
allowed_interface et allowed_attributes.
11.3. L’enregistrement en ZCML
225
Plone pour les développeurs, Version 1.0.0
TABLE 11.2 – Attributs d’un élément viewlet (* = obligatoire)
Attribut
name *
permission
*
for
Description
Définition du nom de la viewlet. Doit être unique dans l’instance Zope.
Permission de vue de cette viewlet. Généralement zope2.View
Interface de types de contenus pour lesquels cette viewlet sera affichée. Par exemple, pour
n’afficher cette viewlet que dans les vues d’un ATDocument (ou type dérivé), vous
placerez : Products.ATContentTypes.interface.IATDocument. Par défaut :
zope.interface.Interface. Pour que la viewlet s’affiche dans tout contexte, vous
placerez *.
layer
L’utilisation de cet attribut est identique à celle prévue pour le viewlet manager.
view
L’utilisation de cet attribut est identique à celle prévue pour le viewlet manager.
manager
L’interface de viewlet manager dans laquelle cette viewlet peut être inscrite. En d’autres
termes, la valeur de cet attribut doit être identique à l’attribut “provides” de la déclaration
ZCML du viewlet manager dans lequel vous voulez inscrire la présente viewlet. Par défaut :
zope.viewlet.interfaces.IViewletManager, c’est-à-dire que par défaut, vous
pourrez placer cette viewlet dans n’importe quel viewlet manager.
class
La classe qui réalise cette viewlet. Cette classe doit avoir une méthode render qui produit
l’extrait de HTML. Par défaut : None. Exemple : browser.maviewlet.MaViewlet
attribute
La méthode de rendu de votre classe de viewlet, si celle-ci n’est pas render. Ceci n’a
d’intérêt que pour avoir plusieurs méthodes de rendu pour une seule classe de viewlet.
template
Vous pouvez fournir une template pour fournir l’extrait HTML lorsque vous ne désirez pas,
ou ne pouvez pas, faire réaliser ceci par la méthode render de la classe mentionnée plus
haut. Dans ce cas, l’objet instancié depuis cette dite classe est accessible dans votre template
personnelle à travers l’objet “view”. Cette approche est utilisée pour personnaliser des
viewlets standard ou d’un composant tiers dans vos propres réalisations, sans avoir à faire
une ligne de Python.
allowed_interface
L’utilisation de cet attribut est identique à celle prévue pour le viewlet manager
allowed_attributes
L’utilisation de cet attribut est identique à celle prévue pour le viewlet manager
foo
bar. Non, ce n’est pas un gag, vous pouvez fournir des attributs unicode supplémentaires
de cette façon à la viewlet. Ceux-ci peuvent être exploités dans le code Python de la viewlet
sous forme d’attributs simple self.foo, ou dans la template dans l’expression TALES
view/foo.
Si vous avez bien lu ce qui précède, vous savez comment invoquer un viewlet manager depuis une template
quelconque, et déclarer la compatibilité d’une viewlet avec un viewlet manager, en faisant la liaison à l’aide de
l’attribut “manager” d’une viewlet.
11.4 Le step GenericSetup
Malheureusement ceci peut ne pas suffire ! Les déclarations zcml permettent d’associer les viewlets à des viewlet
managers, mais pas de définir l’ordre des viewlets au sein du manager... Ceci est réalisé par un step “GenericSetup”
dédié, le bien nommé viewlets.xml.
11.4.1 Ordre des viewlets
La mise en place d’un profil complet GenericSetup fait l’objet d’un chapitre de la documentation intégrateur ; nous
ne nous attarderons que sur ce step spécifique :
<?xml version="1.0"?>
<object>
<order manager="monproduit.monmanager" skinname="*">
<viewlet name="monproduit.maviewlet" />
<viewlet name="monproduit.monautreviewlet" />
226
Chapitre 11. Les viewlets
Plone pour les développeurs, Version 1.0.0
</order>
</object>
L’élément <order ...> déclare le viewlet manager dans lequel la viewlet sera insérée. Notez que ceci ne crée
pas le viewlet manager, la création de celui-ci étant faite dans le fichier ZCML vu dans le paragraphe précédent.
La valeur de l’attribut manager doit correspondre à celle de l’attribut “name” du viewlet manager vu dans sa
déclaration ZCML.
De même, la valeur de l’attribut name des éléments <viewlet ...> doivent correspondre à l’attribut name
des éléments <viewlet ...> du fichier configure.zcml.
La valeur de l’attribut skinname permet de ne fournir le paramétrage des viewlets que pour une skin, en donnant
le nom de cette skin. Par exemple Plone Default, ou My Beautiful Skin. Dans l’exemple ci-avant, la
valeur * insère la viewlet pour toutes les skins.
La déclaration minimale de step telle que fournie ci-avant remplace l’ensemble des viewlets éventuellement insérées par d’autres steps viewlets.xml dans le viewlet manager monproduit.monmanager. Ceci est sans
doute l’effet voulu dans votre cas.
Dans de nombreux autres cas, il est également possible d’ajouter des viewlets dans des viewlet managers existants
fournis par Plone ou des produits tiers.
Dans ce cas, on utilisera plutôt la notation suivante :
<?xml version="1.0"?>
<object>
<order manager="plone.portaltop" skinname="*">
<viewlet name="monproduit.maviewlet" insert-after="plone.header"/>
</order>
</object>
Facile à comprendre : on ajoute pour toutes les skins, dans le viewlet manager plone.portaltop, la viewlet
monproduit.maviewlet après la viewlet de nom plone.header.
L’attribut insert-after peut également prendre la valeur * pour signifier que la viewlet est insérée en dernière
position.
Il est également possible, dans le même ordre d’idées, d’insérer l’attribut insert-before. Je n’insulterai pas
votre intelligence en décrivant les valeurs possibles de cet attribut.
11.4.2 Viewlets cachées
De même, si vous désirez masquer certaines viewlets déclarées dans du code sur lequel vous n’avez pas la main,
vous devez passer par le step : viewlets.xml.
Il vous suffira d’ajouter un élément <hidden ...>, dont la syntaxe est similaire à celle de <order ...>.
Ici, nous masquons le path_bar, appelé aussi breadcrumbs ou navigation horizontale :
<hidden manager="plone.portaltop" skinname="Formation Theme" purge="true">
<viewlet name="plone.path_bar" />
</hidden>
11.5 Exemple
Nous allons faire très simple, ici. Vous trouverez un exemple plus complet de création de viewlet dans le chapitre
Création d’un thème Plone.
Imaginons que vous vouliez que la date d’expiration d’un document apparaisse pour les modérateurs du site, sous
la ligne d’information de modification (Par xxx - Dernière modification le...) : c’est un cas spécifique qui n’est pas
fourni par défaut dans Plone, mais qui est peut-être indispensable à l’application de vos méthodes de travail.
11.5. Exemple
227
Plone pour les développeurs, Version 1.0.0
Nul besoin de modifier la template principale ou quoi que ce soit d’autre. Vous pouvez ajouter un élément en
ajoutant une viewlet sans intervenir sur le reste du code.
Ajoutez un module viewlets.py dans formation.theme, dans lequel vous ajouterez une classe ExpirationDateViewlet qui hérite de ViewletBase :
class ExpirationDateViewlet(ViewletBase):
def render(self):
expires = self.context.getExpirationDate()
return expires and ’<div class="documentByLine">Expire le : %s</div>’ % expires.Date() or
Éditez
ainsi
le
fichier
configure.zcml
de
votre
produit
formation.policy
pour
déclarer
votre
viewlet
(vous
devrez
peut
être
ajouter
le
namespace
xmlns:browser="http://namespaces.zope.org/browser").
<browser:viewlet
name="formation.expirationdate"
for="Products.ATContentTypes.interface.IATContentType"
manager="plone.app.layout.viewlets.interfaces.IBelowContentTitle"
class=".viewlets.ExpirationDateViewlet"
permission="cmf.ReviewPortalContent"
/>
Vous n’auriez probablement pas su quoi mettre dans manager. Pour le savoir, il faut consulter la vue
@@manage-viewlets, où vous pourrez trouver l’interface du ViewletManager que vous voulez compléter.
Redémarrez votre site, vous observerez le nouvel élément sur votre page d’accueil (si vous lui avez donné une
date d’expiration...).
11.6 Pour aller plus loin
Comme d’habitude, pour comprendre les détails de fonctionnement des mécanismes définis dans ce chapitre, il
est nécessaire de lire le code source des modules suivants :
TABLE 11.3 – Code à lire également
Module
Description
plone.app.layout
Les ressources responsables de la mise en page d’un site Plone
plone.app.layout.viewlets Les ressources fournissant les viewlet managers et viewlets standard de
Plone.
zope.publisher.browser
Les ressources Zope 3 de base des viewlets.
228
Chapitre 11. Les viewlets
Plone pour les développeurs, Version 1.0.0
F IGURE 11.3 – Viewlet de la date d’expiration
11.6. Pour aller plus loin
229
Plone pour les développeurs, Version 1.0.0
230
Chapitre 11. Les viewlets
CHAPITRE 12
Les portlets
Author Gilles Lenfant
Version 1.0.0
Copyright (C) 2010 Gilles Lenfant.
Les portlets sont les boites thématiques disposées dans les colonnes droite et gauche des pages d’un site Plone.
Plone fournit en standard quelques portlets, certaines quasi-indispensables telles que la portlet de navigation,
d’autres fournissant des services dont l’utilité dépend de l’orientation donnée au site, telles que la portlet calendrier.
12.1 Personnalisation
Les lecteurs connaissant les portlets des versions antérieures à Plone 3 seront déroutés lors de leur prise de connaissances de Plone 3 ou 4. En effet, les portlets de nouvelle génération s’apparentent à des contenus d’un nouveau
genre qu’un administrateur peut disposer de façon distincte en association avec les divers types de contenus, ou en
association avec les divers groupes d’utilisateur, ou encore de façon distincte en association avec chaque contenu
individuel.
12.1.1 Portlets des types de contenus
En tant qu’administrateur, vous pouvez associer tout type de contenu avec une ou plusieurs portlets qui seront
affichées lors de chaque vue d’un contenu du type sélectionné.
Rendez-vous d’abord dans le panneau de configuration des types :
Choisissez le type de contenu auquel vous voulez associer une (ou plusieurs) portlets, et cliquez le lien “Gérer les
portlets affectés à ce type de contenu”.
Par exemple, pour les “documents”, allez à l’URL http://votre-site/@@manage-content-type-portlets?key=Do
Ceci vous amène à un tableau de bord depuis lequel vous pouvez disposer les portlets de votre choix, portlets qui
seront affichées sur chaque vue d’un contenu du type sélectionné, le Document dans notre exemple.
Depuis ce tableau de bord, vous pouvez, comme dans cet exemple, ajouter la portlet des événements récents dans
la colonne gauche à tous les contenus de type “événement”.
231
Plone pour les développeurs, Version 1.0.0
F IGURE 12.1 – Gestion des portlets assignées à un type de contenus
12.1.2 Portlets des groupes
Les portlets d’un groupe seront affichées lors de la visite de l’espace du groupe, et uniquement aux visiteurs
authentifiés membre du groupe.
Pour gérer les portlets d’un groupe, rendez-vous dans le panneau de configuration des groupes. Sélectionnez l’un
des groupes, puis cliquez l’onglet “Portlets de groupe”.
De la même façon que pour les types de contenu, un tableau de bord analogue vous invite à ajouter, supprimer ou
déplacer des portlets dans les colonnes gauche et droite.
12.1.3 Portlets des contenus ou portlets contextuelles
De la même façon que pour les portlets des types de contenus et les portlets des groupes, la gestion des portlets
contextuelles est réservée aux administrateurs.
Vous noterez que, lorsque vous naviguez dans votre site, figurent sous les deux colonnes de portlets un lien “Gérer
les portlets”. Ce lien permet d’agencer les portlets affichées dans le contexte du contenu courant.
Vous trouvez dans chaque colonne, de haut en bas :
– Un widget d’ajout de portlets dans le contenu courant. En effet, chaque contenu peut avoir sa propre collection
de portlets.
– La liste des portlets actuellement associées au contenu courant - la racine de Plone dans notre cas. Chaque
portlet étant représentée dans un cadre bleu-ciel, dans la skin par défaut. Chaque représentation de portlet étant
accompagnée des widgets de déplacement, et de suppression. Si la portlet est paramétrable, le titre de la portlet
est un lien hypertexte au formulaire ad hoc. Nous y reviendrons plus loin.
– Les règles de blocage/déblocage des portlets, pour les différentes catégories, portlets de groupe et portlets de
types de contenu, vu précédemment, ainsi que les portlets contextuelles du dossier parent. Pour chaque catégorie
et sur chaque colonne, vous pouvez :
– Conserver les paramètres du dossier parent : les règles d’affichage des portlets de la catégorie considérée
sont identiques à celles appliquées au dossier parent.
– Bloquer : les éventuelles portlets de la catégorie considérée ne seront pas affichées dans ce contexte.
– Toujours montrer : les éventuelles portlets de la catégorie considérée seront toujours affichées quelque soit
le paramétrage concernant cette catégorie de portlets dans le dossier parent.
12.2 Portlets disponibles en standard
Plone fournit en standard toute une liste de portlets. Essayez-les...
232
Chapitre 12. Les portlets
Plone pour les développeurs, Version 1.0.0
F IGURE 12.2 – Gestion des portlets de contenus
12.2. Portlets disponibles en standard
233
Plone pour les développeurs, Version 1.0.0
TABLE 12.1 – Portlets standard
Nom
Login
Liste de
modération
RSS feed
Classic
portlet
Calendar
portlet
Rechercher
Éléments
récents
Actualités
Événements
Description
La classique portlet d’authentification, montrée uniquement aux visiteurs anonymes.
Fournit à un modérateur la liste des contenus en attente d’approbation (voir le chapitre sur les
workflows).
Présentation des entrées d’un flux RSS. À noter que ceci permet de présenter facilement les
premiers élément d’une collection dans une portlet.
Une “classic portlet” permet de présenter une portlet conçue pour Plone 2.1 ou Plone 2.5 dans
un site Plone 3 ou +. Attention, ce type de portlet ne peut pas bénéficier du paramétrage
contextuel comme défini plus loin.
Permet de présenter les événements dans une vue calendaire.
Permet d’escamoter le widget de recherche du bandeau supérieur et de le placer en portlet
Présente les publications les plus récente du site.
Présente les articles d’actualités les plus récents.
Présente les prochains événements publiés sur le site.
12.3 Retour sur le paramétrage d’une portlet
Contrairement aux versions antérieures à Plone 3, le paramétrage d’une portlet est local, et s’applique à l’emplacement dans lequel la portlet est placée. On peut donc considérer ces portlets comme une sorte de paramètre
attaché à un dossier ou à un document. Dans le cas des dossiers, ce paramètre est utilisé par défaut dans ses
sous-contenus.
Ce comportement peut être exploité de diverses manières. Par exemple pour proposer une autre portlet de navigation lors de la visite de certaines rubriques d’un site.
12.4 Habillage graphique
Comme pour tout habillage graphique de Plone, l’habillage graphique des porlets doit – dans la mesure du possible
– être effectué dans une feuille de styles CSS.
Une visite des colonnes de portlets avec Firebug vous indiquera mieux que je ne pourrais le faire ici – et j’ai aussi
un peu la flemme – les sélecteurs et classes CSS qui s’appliquent aux différentes colonnes et portlets.
12.5 Export/Import Generic setup du paramétrage des portlets
Il est possible d’importer via Generic Setup la configuration complète des portlets, notamment les assignations
locales et les paramètres de configuration.
12.6 Création d’une portlet personnelle
En contrepartie des nouvelles possibilités permises par l’infrastructure des portlets de Plone, la réalisation d’une
portlet personnelle nécessite dorénavant des compétences de développeur. La réalisation d’une portlet repose sur :
– Un module Python fournissant les ressources d’édition des paramètres de la portlet – ou plutôt d’une instance
de la portlet comme nous l’avons vu plus haut, ainsi que les données publiées par la portlet.
– Une template représentant l’aspect graphique de la portlet.
234
Chapitre 12. Les portlets
Plone pour les développeurs, Version 1.0.0
– Une déclaration ZCML pour inscrire la portlet dans l’outil de gestion et de publication des portlets de Plone.
– Un profil GenericSetup pour installer la portlet dans un site Plone.
Pour illustrer ceci, l’auteur de ces lignes préfère vous proposer l’exploration complète d’une portlet simple, mais
utile, présentant l’auteur d’un contenu.
12.6.1 Structure du composant
Conformément aux conventions d’organisation des composants pour Plone, notre composant est construit selon la
structure classique suivante à partir du niveau formation.portlet.author. Je vous invite maintenant pour
comprendre les explications qui suivent à installer le code de notre portlet de démo :
$ svn co https://svn.plone.org/svn/collective/collective.trainingmanual/trunk.old/fr/examples/form
TABLE 12.2 – Fichiers principaux du composant
Classique création des ressources globales du module.
Constantes propres au produit.
Les déclarations ZCML classiques du composant, incluant la directive d’inscription
de la portlet.
profiles/default/Le profil GenericSetup d’installation de la portlet dan un site.
authorportlet.py La définition Python de la portlet, c’est-à-dire l’essentiel de la substance de notre
composant.
authorportlet.pt La template de mise en page de la portlet. Celle-ci est intentionnellement simplifiée.
__init__.py
config.py
configure.zcml
Comme vous êtes un expert de Python et sans doute de Plone puisque vous avez parcouru le site jusqu’ici, vous
aurez remarqué que l’auteur de la portlet n’a fait aucun effort pour optimiser le code. En effet, celui-ci est construit
dans l’optique d’une lisibilité optimale. Nous y reviendrons plus loin à propos des décorateurs de cache.
Jetons maintenant un oeil sur les fichiers essentiels de formation.portlet.author.
12.6.2 __init__.py
Peu de choses à dire. Nous créons un “logger” utilisable globalement depuis l’ensemble du produit. Non indispensable, mais utile lors de la mise au point de produit.
Un autre classique des composants pour Plone 3+ est la création d’un MessageFactory nécessaire :
– à la traduction des différents textes dans les modules Python,
– au marquage des textes ou “msgid” à traduire, exploitables depuis i18ndude. Voir Internationalisation d’un
composant.
Consultez le chapitre sur l’internationalisation et la localisation de ce site pour plus de détails à ce sujet.
12.6.3 config.py
Encore un grand classique des produits pour Plone. Les constantes globales au produit sont définies dans ce
module.
12.6.4 configure.zcml
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:five="http://namespaces.zope.org/five"
xmlns:genericsetup="http://namespaces.zope.org/genericsetup"
xmlns:plone="http://namespaces.plone.org/plone"
i18n_domain="formation.portlet.author">
<!-- ... -->
12.6. Création d’une portlet personnelle
235
Plone pour les développeurs, Version 1.0.0
<plone:portlet
name="formation.portlet.author.AuthorPortlet"
interface=".authorportlet.IAuthorPortlet"
assignment=".authorportlet.Assignment"
view_permission="zope2.View"
edit_permission="cmf.ManagePortal"
renderer=".authorportlet.Renderer"
addview=".authorportlet.AddForm"
editview=".authorportlet.EditForm"
/>
<!-- ... -->
</configure>
Également un grand classique des composants pour Plone 3+. Dans notre cas, seules deux directives sont inscrites :
– L’inscription du profil “GenericSetup” (supprimée de l’extrait ci-dessus) sur laquelle nous ne nous attarderons
pas sachant qu’un chapitre de ce site est consacré à GenericSetup.
– L’inscription de la portlet dans l’infrastructure de Plone, qui est détaillée ci-après.
La directive <plone:portlet... est une directive spécifique à Plone. Celle-ci doit incorporer les attributs
suivants :
TABLE 12.3 – Attributs de l’élément <plone :portlet ...>.
Nom
name
Description
Le nom unique de la portlet dans le registre global de Plone. Tant pis pour vous si vous
reprenez un nom déja utilisé ;)
title
Le titre de la portlet tel qu’elle apparaitra – après éventuelle traduction – dans l’interface de
gestion des portlets vue plus haut.
interface La classe d’interface incorporant le schéma (Zope 3) des données d’option de la portlet. Il est
rappellé que ces options s’appliquent au contexte dans lequel la portet est définie,
contrairement aux portlets pour Plone 1 et Plone 2.
assignment Classe utilisée pour stocker les options de la portlet. Le renderer - voir ci-après – de la
portlet accède aux options de la portlet par son attribut data.
renderer La classe utilisée pour générer le rendu HTML de la portlet.
addview
Une classe de vue (formulaire à la Zope 3) d’ajout de la portlet.
editview Une classe de vue (formulaire à la Zope 3) d’édition des options de la portlet.
view_permission
La permission dont le visiteur doit bénéficier dans le contexte courant pour afficher la portlet.
Dans le cadre de la notre, la permission zope2.View est suffisante.
edit_permission
La permission dont le visiteur doit bénéficier pour modifier les options de la portlet dans le
contexte courant. Dans la plupart des cas – comme dans le notre – la permission
cmf.ManagePortal convient parfaitement.
Indice : editview
Cet attribut doit être bien entendu omis si votre portlet n’a pas d’option de configuration.
Comme nous le verrons plus loin, l’essentiel des ressources de cette portlet, référencées dans cette directive, sont
définies dans le module “author.py”.
12.6.5 authorportlet.py
Ce module fournit l’essentiel du corps de la portlet, et contient les éléments suivants :
236
Chapitre 12. Les portlets
Plone pour les développeurs, Version 1.0.0
TABLE 12.4 – Éléments de authorportlet.py
Classe
Description
IAuthorPortlet
L’interface définissant le schéma (Zope 3) des options d’une portlet.
AssignmentLa classe qui fournit les options de la portlet applicable dans le contexte courant. À noter que
les objets de type Assignment sont construits par les objets AddForm que nous verrons plus
loin. Dans notre cas, la classe est minimaliste, mais les objets Assignment à d’autres
portlets peuvent comporter des méthodes plus complexes. Un objet Assignment est stocké de
façon persistante dans le contexte dans lequel la portlet a été créée (objet de contenu, type de
contenu, groupe ou dashboard)
AddForm
Un formulaire “Zope 3” permettant de créer un Assignment. Comme vous pouvez le lire dans
le code, la réalisation d’un AddForm est très simple puisque s’appuyant sur le schéma de
l’interface de la portlet (IAuthorPortlet dans notre cas). Seule la surcharge de la méthode
create responsable de la construction de l’objet Assignment est requise.
EditForm Un formulaire “Zope 3” permettant de modifier l’Assignment.
Renderer Une classe responsable de la réalisation du rendu HTML de la portlet.Ce type de classe doit
obligatoirement comporter une méthode render produisant le HTML de rendu. Comme dans
notre exemple, le rendu est généralement délégué à un objet de type ViewPageTemplate.
À noter qu’un objet de type Renderer doit propager le constructeur à sa classe mère (voir
l’instruction “super(...)” au début du constructeur) et dispose de ce fait des attributs suivants :
À noter que ces deux derniers attributs sont rarement utilisés. Vous noterez également que cette
classe propose un attribut booléen available. Celui-ci est facultatif et “True” par défaut. Si
cet attribut est False, la portlet n’est pas affichée.
Pour plus de détails, nous invitons le lecteur à lire le code des classes de base de ces classes.
Notez que les différentes classes énumérées ci-dessus sont référencées dans la déclaration de portlet du
configure.zcml vu ci-avant. C’est cette déclaration qui fait la “glue” entre les différents composants de
notre portlet.
12.6.6 authorportlet.pt
Cette template met en page la portlet.
Elle est volontairement minimaliste pour en faciliter la compréhension. Je vous laisse le soin de fignoler son
habillage graphique si vous voulez l’incorporer dans vos propres réalisations.
Vous noterez que :
– Les portlets, depuis Plone 2.5, sont définies dans une structure de type <dl
class="portlet">...</dl>.
– L’objet de classe Renderer défini dans author.py est accessible dans les expressions TALES de cette portlet à travers la variable view. Les différentes données spécifiques à la portlet sont produites par des expressions
TALES view/xxx où xxx est une méthode ou un attribut de la classe Renderer vue plus haut.
12.6.7 profiles/default/portlets.xml
Fichier d’installation des portlets pour GenericSetup.
Dans notre cas nous n’avons qu’une portlet à déclarer. Il n’y a rien de particulier à dire sur les attributs title et
description.
L’attribut “addview” doit ici avoir la même valeur que l’attribut name de la déclaration de portlet figurant dans
notre configure.zcml.
12.7 Et le résultat...
Le composant formation.portlet.author a été ajouté dans l’instance Zope, puis ajouté à un site Plone
depuis le panneau de configuration d’installation des modules. Vous connaissez déjà ces étapes si vous êtes arrivé
12.7. Et le résultat...
237
Plone pour les développeurs, Version 1.0.0
à ce point du site.
Le membre Albert Einstein a réalisé une étude scientifique très intéressante qu’il a publiée dans une page Plone...
Comme tout membre d’un site Plone, Albert Einstein a rempli son formulaire de préférences personnelles, incluant
son nom complet, sa ville, son mémo perso et sa photo au format requis.
Le webmestre du site estime que les portlets classiques publiées par défaut à la droite ne rendent pas justice à
l’auteur d’un contenu d’une telle valeur, et préfère remplacer celles-ci par notre portlet de présentation de l’auteur
de façon spécifique à cette page.
F IGURE 12.3 – L’article tel que publié par Albert Einstein
Dans la gestion locale des portlets, on commence par bloquer toutes les portlets pouvant s’appliquer localement.
F IGURE 12.4 – On ajoute une “Author portlet” détaillée
Si show author details était décoché dans le formulaire d’ajout / modification, la portlet n’afficherait pas “Berlin”
ni “Je suis un savant..”.
238
Chapitre 12. Les portlets
Plone pour les développeurs, Version 1.0.0
F IGURE 12.5 – Et voilà le résultat...
12.8 Créer votre propre portlet
ZopeSkel est votre ami. Puisque vous l’avez installé comme indiqué dans votre environnement de développement,
il suffit de taper ceci pour avoir le squelette de votre portlet personnelle :
$ paster create -t plone3_portlet ma.belle.portlet
Suivez les instructions de l’assistant de création et à vous de jouer.
12.9 Pour aller plus loin
Par souci de simplification, deux aspect du développement de portlets ont été passés sous silence dans l’exemple
ci-avant :
– Les ressources d’internationalisation et traduction de la portlet, les informations relatives à l’internationalisation fournies ailleurs dans ce site étant applicables ici. Voir à ce sujet Internationalisation d’un composant
– L’utilisation des décorateurs de cache permettant d’optimiser les méthodes Python, ainsi que code HTML
spécifique à la portlet. Le cache des portlets utilise les ressources du module plone.memoize, qui s’utilise sous
la forme de décorateurs Python. Dans le cas de cette portlet, les décorateurs de vue (from plone.memoize
import view...) auraient pu être utilisés.
Pour réaliser des portlets plus complexes ou plus performantes, lisez le code des paquetages Python suivants :
TABLE 12.5 – Code source relatif
Module
plone.app.portlets.portlets
zope.formlib
zope.schema
plone.memoize
Description
Les portlets fournies en standard par Plone.
Le gestionnaire de formulaires de Zope 3.
L’alter-ego d’Archetypes pour Zope 3.
Divers types de caches Python utilisables sous forme de décorateurs.
Indice : Autres portlets
Vous pouvez également vous inspirer des nombreuses portlets à votre disposition dans le référentiel Subversion “collective” https ://svn.plone.org/svn/collective, la grande majorité des portlets étant préfixées par
collective.portlet....
12.8. Créer votre propre portlet
239
Plone pour les développeurs, Version 1.0.0
240
Chapitre 12. Les portlets
CHAPITRE 13
Les vues standard
13.1 Qu’est-ce que c’est ?
Les vues standard, comme leur nom ne l’indiquent pas, ne sont pas des vues HTML sur les contenus d’un site
Plone mais des multi-adapteurs (voir Le guide complet de l’Architecture de Composants de Zope) permettant
d’accéder aux ressources les plus fréquemment utilisées lors de la programmation de vos applications pour Plone,
pour réaliser vos templates ou dans les expressions utilisées dans les actions.
13.2 Pourquoi utiliser ces vues ?
Il y a d’autres dispositifs pour obtenir les mêmes informations et services que ceux proposés par les vues standard.
Ces API, que nous ne détailleront pas ici, sont maintenues dans Plone 3 et Plone 4 principalement pour faciliter
l’adaptation des composants d’extension conçus pour fonctionner avec les versions antérieures à Plone 3.
L’utilisation de ces vues en lieu et place des API antérieures ont deux avantages :
– L’API est simplifiée et unifiée. L’adjonction de futures fonctionnalités seront reflétées dans ces vues ou d’autres
vues pour les services futurs.
– Si l’architecture logicielle sous-jacente est amenée à changer dans le futur, l’accès aux services et informations
supportée par la dite architecture seront inchangés grâce particulièrement à ces vues.
– Les éléments retournés par ces vues sont “cachés” dans la mesure du possible. C’est-à-dire que seul le premier
accès à un objet retourné par ces vues est “coûteux” en terme de temps de réponse. Les accès successifs sont
immédiats.
13.3 Quelles vues sont proposées ?
Plone propose en standard les vues suivantes :
241
Plone pour les développeurs, Version 1.0.0
TABLE 13.1 – Liste des vues standard
Nom
Description
plone_context_state
Informations sur l’objet de contexte courant
plone_portal_state
Informations et services globaux du portail lui-même
plone_tools Accès aux tools les plus fréquemment utilisés
plone_interface_info
Permet de tester les interfaces Zope 3 depuis du code “untrusted” (une template, une
expression TALES d’action, un script de skin). Cette vue est également utile à du code
“trusted” pour tester une interface connue par son dotted name.
plone_layout Fournit l’accès à des méthodes relatives à la mise en page.
plone
Fournit l’accès aux objets qui étaient auparavant fournis en standard dans la
main_template de Plone jusqu’à sa version 3 1 .
Warning : Vous noterez que de nombreuses ressources de La vue “plone” sont en fait des reprises ou
alias de méthodes figurant dans La vue “plone_portal_state”, La vue “plone_context_state” ou La vue
“plone_layout”.
Si le composant que vous développez n’est pas supposé être compatible avec une versions de Plone antérieure
à Plone 3, vous préférerez l’utilisation des méthodes proposées par ces dernières vues.
13.4 Accéder aux ressources des vues
On utilise pour illustrer les accès la vue fictive nommée une_vue, son attribut non moins fictif un_attrib et
une méthode une_meth.
13.4.1 Dans une expression TALES d’une action
python: object.restrictedTraverse(’@@une_vue’).un_attrib
Ou, ce qui revient au même en plus court :
object/@@une_vue/un_attrib
13.4.2 Dans une template ZPT
<div tal:define="une_meth nocall:context/@@une_vue/une_meth;
foo python:une_meth(bar)">
...
</div>
13.4.3 Dans du code Python “untrusted”
une_meth = context.restrictedTraverse(’@@une_vue’).une_meth
bar = une_meth(foo)
13.4.4 Dans du code Python “trusted” dans un fichier
On peut utiliser la méthode standard restrictedTraverse(’@@une_vue’) comme dans du code “untrusted”. Cependant, et même si cette approche est plus verbeuse, elle est plus efficace en terme de performances.
242
Chapitre 13. Les vues standard
Plone pour les développeurs, Version 1.0.0
from zope.component import getMultiAdapter
# ...
# Fournir l’objet de contexte par le moyen le plus approprié
context = myOwnWayToGetIt() # Comme bon vous semble
# Fournir l’objet request. Par défaut, vous pourrez utiliser l’acquisition
# À noter que dans les classes de vues, ces deux objets sont généralement
# self.context et self.request
request = context.REQUEST
# Obtenir la vue
une_vue = getMultiAdapter((context, request), name=u’une_vue’)
# Et l’utiliser
foo = une_vue.un_attrib
bar = une_vue.une_meth(dummy)
Les différentes vues
La vue “plone_context_state”
Cette vue fournit de nombreuses méthodes et attributs fournissant des informations sur l’objet de contexte publié.
À noter que dans vos templates utilisant la macro de gabarit de page par défaut, cette vue est accessible par l’objet
context_state.
<html use-macro="context/@@standard_macros/page">
...
<div>Le titre de cette page
<span tal:content="context_state/object_title">Un titre</span>
</div>
...
</html>
Détails dans plone.app.layout.globals.interfaces.IContextState
13.4. Accéder aux ressources des vues
243
Plone pour les développeurs, Version 1.0.0
TABLE 13.2 – plone_context_state
Attribut Description
Méthode
current_page_url()
L’URL de la page courante, incluant la template et la query string (paramètres CGI) :
http://site/objet/template?foo=bar
current_base_url()
L’URL de la page courante, incluant la template mais sans la query string éventuelle
(paramètres CGI) : http://site/objet/template
canonical_object()
L’objet canonique courant, c’est à dire l’objet publié. Si l’objet publié est la page par
défaut d’un dossier, le dossier est retourné par cette méthode.
canonical_object_url()
L’URL du canonical_object vu ci-dessus.
view_url()
URL à utiliser pour voir la page présentant l’objet. Les images et fichiers sont
directement téléchargés lorsque le navigateur évoque leur canonical_object_url
mentionnée ci-avant.
view_template_id()
Le nom de la template actuellement utilisée pour cette page.
is_view_template()
True si l’URL actuelle (celle de la requête HTTP) utilise la vue standard de l’objet
publié (celle de l’onglet “Voir”)
object_url() L’URL de vue de l’objet publié. N’est pas forcément équivalent à
canonical_object_url() vu ci-dessus si l’objet publié si l’on publie un dossier
dont la page par défaut est l’un de ses contenus.
object_title()Titre ou identifiant (en absence de titre) de l’objet publié.
workflow_state()
Nom de l’état de workflow de l’objet publié.
parent()
L’objet parent de l’objet publié.
folder()
Le dossier canonique courant, c’est-à-dire la même chose que parent() ci-dessus si
l’objet courant n’est pas folderish (voir is_folderish() ci-dessous)
is_folderish()True si l’objet publié peut contenir des sous-objets enfants. Type dossier ou apparenté.
is_structural_folder()
True si l’objet publié est un dossier permettant la navigation et la structuration interne
libre.
is_default_page()
True si l’objet publié est la page par défaut de son dossier parent.
is_portal_root()
True si l’objet publié est le site plone ou sa page d’accueil.
is_editable() True si l’objet publié est modifiable par l’utilisateur courant (Permet d’afficher ou non
l’onglet Modifier).
is_locked()
True si un verrou WebDAV est posé sur l’objet publié.
actions()
Les actions filtrées sur le contexte, dépendant des droits de l’utilisateur courant. Toutes
les catégories d’actions sont fournies sous la forme {catégorie: [{action},
{action}, ...], ...}.
portlet_assignable()
True si l’objet publié peut se voir assigner des portlets. Assigner des portlets signifie
dans ce cas qu’une disposition de portlets spécifique à cet objet peut être envisagée
(affichage du lien de gestion des portlets). Et non que des portlets peuvent être affichées,
ce qui est toujours le cas.
La vue “plone_portal_state”
Les informations globales sur le portail sont accessibles à travers les méthodes et attributs de cette vue.
À noter que dans vos templates utilisant la macro de gabarit de page par défaut, cette vue est accessible par l’objet
portal_state.
<html use-macro="context/@@standard_macros/page">
...
<div>Le titre du portail
<span tal:content="portal_state/portal_title">Un titre</span>
</div>
...
</html>
Détails dans plone.app.layout.globals.interfaces.IPortalState
244
Chapitre 13. Les vues standard
Plone pour les développeurs, Version 1.0.0
TABLE 13.3 – plone_portal_state
Attribut - Méthode
Description
portal()
L’objet site Plone (voir également à la fin de ce tableau)
portal_title()
Le titre du portail.
portal_url()
L’URL de la racine du portail (voir navigation_root_url).
navigation_root_path()
Le path relatif de la racine de navigation du site (la page d’accueil).
navigation_root_url()
L’URL de la page d’accueil du site (celle du lien du logo du site ou du lien
“Accueil” du breadcrumb / fil d’Ariane)
default_language()L’abréviation IANA de la langue par défaut du site, comme fr pour le Français.
language()
L’abréviation IANA de la langue utilisée pour la publication de la page courante.
locale()
La locale utlisée pour la publication de la page courante (voir le module
standard Python locale).
is_rtl()
La langue courante (voir language() plus haut) s’écrit de droite à gauche
comme l’Arabe ou l’Hébreu.
member()
L’objet représentant le membre courant. None si anonyme.
anonymous()
True si l’utilisateur courant est anonyme.
friendly_types() Liste de portal_types compatibles avec la recherche.
Indice : Objet portail
Grâce à la ZCA décrite dans Le guide complet de l’Architecture de Composants de Zope, une méthode plus rapide
permet d’obtenir l’objet portail de façon plus efficace :
from zope.component.hooks import getSite
# ...
portal = getSite()
La vue “plone_tools”
Cette vue permet d’accéder de façon efficace aux tools Plone les plus fréquemment utilisés.
Détails dans plone.app.layout.globals.interfaces.ITools
TABLE 13.4 – plone_tools
Attribut - Méthode
actions()
catalog()
membership()
properties()
syndication()
types()
url()
workflow
Description
Le tool portal_actions.
Le tool portal_catalog.
Le tool portal_membership.
Le tool portal_properties.
Le tool portal_syndication.
Le tool portal_types.
Le tool portal_url
Le tool portal_workflow
La vue “plone_interface_info”
Cette vue permet de vérifier la conformité d’un objet ou d’une classe avec une interface dont on ne connait que le
dotted name.
Détails dans plone.app.layout.globals.interfaces.IInterfaceInformation
13.4. Accéder aux ressources des vues
245
Plone pour les développeurs, Version 1.0.0
TABLE 13.5 – plone_interface_info
Attribut - Méthode
provides(dotted_name)
Description
True si l’objet de contexte fournit l’interface donnée par le
dotted_name
class_provides(dotted_name)
True si la classe de l’objet de contexte fournit l’interface donnée par le
dotted_name
Attention : Une interface marqueur peut être assignée à un objet particulier sans que la classe du dit objet
n’implémente cette interface. Donc on peut très bien avoir cette situation :
>>> pii = context.restrictedTraverse(’@@plone_interface_info’)
>>> pii.class_provides(’some.great.Interface’)
True
>>> # Si la classe implémente, alors l’objet aussi (forcément)
>>> pii.provides(’some.great.Interface’)
True
>>> pii.class_provides(’another.great.Interface’)
False
>>> # Si la classe n’implémente pas l’objet lui le peut.
>>> pii.provides(’another.great.Interface’)
True
Voir Le guide complet de l’Architecture de Composants de Zope pour de plus amples explications.
La vue “plone_layout”
Cette vue fournit des méthodes relatives à la mise en page.
Détails dans plone.app.layout.globals.interfaces.ILayoutPolicy
TABLE 13.6 – plone_layout
Attribut - Méthode
mark_view(view)
Description
Ajoute une interface marqueur à la vue si celle-ci est “la” vue pour l’objet de
contexte. Cette conction ne devrait être appellée que depuis une template.
hide_columns(column_left,
Fournit la classe CSS pour masquer les colonnes de portlets vides. Vous aurez
column_right)
rarement à utiliser ce genre de méthode.
have_portlets(manager_name,
Détermine si une colonne de portlets est affichée. La colonne de gauche est
view=None)
appellée plone.leftcolumn, celle de droite plone.rightcolumn.
icons_visible()
True si les icônes doivent être affichées. Voir getIcon(item) ci-dessous.
getIcon(item)
Retourne un objet qui implémente l’interface IContentIcon qui fournit les
informations et services nécessaires au rendu d’une icône. L’élément de
contexte doit être adaptable à IContentIcon. Les icônes peuvent être
désactivés globalement ou seulement aux anonymes avec la propriété
icon_visibility de l’objet
portal_properties/site_properties.
renderBase()
L’URL devant figurer dans la balise <base...> du <head> HTML. Vous
aurez rarement l’occasion d’utiliser cette méthode.
bodyClass(template, Retourne la classe CSS à utiliser sur le tag <body> lors de la publication de la
view)
page courante. Comme la plupart des templates de base sont déjà fournies par
Plone, vous n’aurez dans les faits que très rarement l’occasion d’utiliser cette
méthode.
La vue “plone”
Cette vue fournit nombre d’informations particulièrement utiles dans la construction de vos templates.
246
Chapitre 13. Les vues standard
Plone pour les développeurs, Version 1.0.0
À noter que dans vos templates utilisant la macro de gabarit de page par défaut, cette vue est accessible par l’objet
plone_view.
<html use-macro="context/@@standard_macros/page">
...
<div>Cette URL est
<span tal:content="plone_view/getCurrentUrl">http://foo</span>
</div>
...
</html>
Détails dans Products.CMFPlone.browser.interfaces.IPlone
Note : Vous noterez à la lecture des méthodes proposées que nombre d’entre elles sont reprises de La vue
“plone_context_state”, La vue “plone_portal_state” ou La vue “plone_layout”. Celles-ci sont des raccourcis
facilitant la compatibilité ascendante avec les versions antérieures de Plone. Pour programmer un composant qu’il
est inutile de rendre compatible avec ces anciennes version, on préfèrera l’utilisations des méthodes équivalentes
de La vue “plone_context_state”, La vue “plone_portal_state” ou La vue “plone_layout”.
13.4. Accéder aux ressources des vues
247
Plone pour les développeurs, Version 1.0.0
TABLE 13.7 – plone
Attribut - Méthode
getCurrentUrl()
visibleIdsEnabled()
uniqueItemIndex(pos=0)
toLocalizedTime(time,
long_format=None,
time_only=None)
normalizeString(text)
isDefaultPageInFolder()
isStructuralFolder()
hide_columns(self,
column_left,
column_right)
navigationRootPath
Description
URL courante incluant la query string (paramètres CGI)
True si la modification des id des éléments de contenus sont permis au
niveau du portail comme au niveau de l’utilisateur.
Retourne un itérateur utilisable pour énumérer des éléments dans une
template avec la méthode next. Exemple : tal:define="foo
plone_view/uniqueItemIndex; un foo/next; deux
foo/next"
Convertit un objet DateTime ou une date sous forme de chaîne
normalisée ISO 8601 en une date et heure localisées sous forme de
chaîne de caractères.
Transforme un texte quelconque éventuellement unicode en une chaîne
ASCII compatible avec les identifiants Zope.
Equivalent à la méthode is_default_page de La vue
“plone_context_state”.
Equivalent à la méthode is_structural_folder de La vue
“plone_context_state”.
Equivalent de la méthode hide_columns(self,
column_left, column_right) de La vue “plone_layout”.
Equivalent de la méthode navigation_root_path de La vue
“plone_portal_state”.
navigationRootUrl()
Equivalent de la méthode navigation_root_url de La vue
“plone_portal_state”.
getParentObject()
Equivalent de la méthode parent de La vue “plone_context_state”.
getCurrentFolder()
Equivalent de la méthode folder de La vue “plone_context_state”.
getCurrentFolderUrl()
L’URL du dossier retourné par getCurrentFolder ci-dessus.
getCurrentObjectUrl()
Equivalent de la méthode canonical_object_url de La vue
“plone_context_state”.
isFolderOrFolderDefaultPage()
Equivalent de la méthode is_default_page de La vue
“plone_context_state”.
isPortalOrPortalDefaultPage()
Equivalent de la méthode is_portal_root de La vue
“plone_context_state”.
getViewTemplateId()
Equivalent de la méthode view_template_id de La vue
“plone_context_state”.
showEditableBorder()
Détermine si le cadre d’édition (contenant les onglets “Contenu”,
“Modifier”, ...) doit être affiché.
displayContentsTab()
Détermine si les onglets d’édition doivent être affichés.
getIcon(item)
Equivalent de la méthode getIcon(item) de La vue
“plone_layout”.
cropText(text, length,
Tronque un texte brut jusqu’à length caractères tout en omettant
ellipsis)
l’éventuel mot coupé.
have_portlets(manager_name,
Equivalent de la méthode have_portlets(manager_name,
view=None)
view=None) de La vue “plone_layout”.
mark_view(view)
Equivalent de la méthode mark_view(view) de La vue
“plone_layout”.
site_encoding()
L’encodage du texte de ce site. Typiquement utf-8.
bodyClass(template,
Equivalent de la méthode bodyClass(template, view) de La
view)
vue “plone_layout”.
248
Chapitre 13. Les vues standard
CHAPITRE 14
Internationalisation d’un composant
Author Vincent Fretin
Version 1.0.0
Contributors Thomas Desvenain
Copyright (C) 2010, Vincent Fretin <vincentfretin AT gmail.com>.
Plone est multilingue. Cette partie explique comment rendre votre composant disponible en plusieurs langues.
Internationalisation et localisation
Nous distinguerons l’internationalisation (i18n), qui consiste à utiliser l’API de production de textes dans le code
Python et les templates, de la localisation (l10n), qui consiste à fournir la traduction des labels produits lors de
l’internationalisation.
14.1 Exemples de la formation
Les exemples de cette formation sont dans le package collective.trainingmanual.i18nexamples. Pour l’installer,
ajoutez à votre buildout :
[buildout]
eggs +=
# ...
collective.trainingmanual.i18nexamples
auto-checkout +=
# ...
collective.trainingmanual.i18nexamples
[instance]
zcml +=
# ...
collective.trainingmanual.i18nexamples
[sources]
collective.trainingmanual.i18nexamples = svn https://svn.plone.org/svn/collective/collective.train
Vous pourrez observer les exemples de cette formation en allant à l’adresse /i18nexamples depuis la racine de
votre site.
249
Plone pour les développeurs, Version 1.0.0
14.2 Internationaliser un composant
14.2.1 Connaissances requises
Nous recommandons aux lecteurs de prendre préalablement connaissance des mécanismes standards d’internationalisation des applications, basé sur le standard “GNU-gettext” http ://fr.wikipedia.org/wiki/Gettext
Les amateurs de détails pourront se reporter à la documentation complète en anglais ici :
http ://www.gnu.org/software/gettext/manual/gettext.html
14.2.2 Notion de domaine
Plone et plus généralement Zope (puisque nous parlons d’internationalisation Zope) sont des écosystèmes générant
des centaines de composants d’extension sur l’utilité desquels nous ne reviendrons pas ici. Un intégrateur est
susceptible de mixer nombre de ces composants.
Ceci risque donc d’amener nombre de conflits de labels, le même label pouvant être traduit différemment selon
l’emploi qui en est fait.
Pour éviter ceci, le standard “gettext” définit la notion de “domaine” qui est un espace de nommage pour les
traductions de labels. Ainsi le label “foo” peut être traduit différemment dans autant de domaines distincts.
Dès lors qu’un composant propose une interface utilisateurs comportant du texte, il doit déclarer son domaine et
le répertoire racine des traductions (traditionnellement, le répertoire locales/ de votre egg). Vous allez donc
ajouter au fichier configure.zcml principal de votre composant les ligne suivantes :
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:i18n="http://namespaces.zope.org/i18n"
i18n_domain="i18nexamples">
...
<i18n:registerTranslations directory="locales" />
...
</configure>
14.2.3 Code Python
Vous placez ensuite dans le __init__.py principal de votre composant les lignes suivantes :
from zope.i18nmessageid import MessageFactory
MyMessageFactory = MessageFactory(’i18nexamples’)
Écrit en C
Afin d’optimiser les performances, les primitives des MessageFactory sont écrites en C.
Lorsque vous voulez placer un texte susceptible d’être traduit dans votre code, vous devez procéder ainsi.
from collective.trainingmanual.i18nexamples import MyMessageFactory as _
# ...
def message(bar):
message = _(u’my_message’, default=u"Some words")
# ...
250
Chapitre 14. Internationalisation d’un composant
Plone pour les développeurs, Version 1.0.0
Utiliser les labels du domaine “plone”
Pour des raisons de cohérence globale de votre site, il est recommandé, dans la mesure du possible de
reprendre les traductions déjà fournies par Plone. Pour effectuer ceci dans du code Python :
from Products.CMFPlone import PloneMessageFactory as PMF
# ...
def save(value):
# ...
message = PMF(u’Save’) # Traduit en français par "Enregistrer"
Dans ce cas particulier, il n’est pas utile de fournir le paramètre nommé default à la messagefactory.
Quelques explications :
– Le symbole _ est un identificateur Python légal. Comme nous le verrons par la suite, i18ndude cherche dans
tout le code Python de votre composant l’utilisation de cette fonction pour élaborer les modèles de dictionnaires
(fichier *.pot).
– my_message est le label de traduction qui figurera, après traitement par i18ndude, dans le modèle de dictionnaire *.pot de votre composant.
– Some words est le texte par défaut, en anglais, du label my_message qui sera fourni aux visiteurs du site
en l’absence de traduction spécifique pour son langage. Bien que, comme la syntaxe l’indique, le paramètre
nommé default n’est pas obligatoire (votre code ne plantera pas) son utilisation est fortement recommandée.
Messages avec variables
Il est parfois nécessaire d’introduire des parties variables dans les messages à traduire. Par exemple : “Ce dossier
contient 5 image(s)”. Utilisez pour ceci le paramètre nommé mapping ayant pour valeur un dictionnaire (ou
objet quelconque ayant le même protocole) fournissant toutes les clés prévues par les emplacements réservés aux
variables dans le message. Ces emplacements variables sont marqués dans le message sous la forme ${clé}.
def map(value):
# ...
msg_map = {’count’: value}
message = _(u’images_in_folder’,
default=u"This folder has ${count} images",
mapping=msg_map)
14.2.4 Templates ZPT
De la même façon que le code Python, les templates ZPT peuvent être internationalisée.
Le marquage d’internationalisation est effectué en utilisant l’espace de nommage XML i18n.
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:tal="http://xml.zope.org/namespaces/tal"
xmlns:metal="http://xml.zope.org/namespaces/metal"
xmlns:i18n="http://xml.zope.org/namespaces/i18n"
>
...
</html>
i18n :domain : Déclaration du domaine
Pour marquer le domaine de traduction d’une template (ou d’un élément quelconque figurant dans une template)
la déclaration préalable de ce dernier est nécessaire dans un élément englobant.
14.2. Internationaliser un composant
251
Plone pour les développeurs, Version 1.0.0
...
<div i18n:domain="machin">
<!-- Les textes marqués pour traductions utiliseront les
messages des dictionnaires du domaine "machin"
-->
...
<table i18n:domain="truc">
<!-- Les textes marqués de ce tableau utiliseront par
contre les dictionnaires du domaine "truc"
-->
...
</table>
</div>
i18n :translate : Traduction du contenu d’un élément HTML
Le contenu de cette <div> sera traduit selon la clé label_truc du domaine de traduction courant.
...
<div i18n:translate="label_truc">Some stuff</div>
...
Le contenu de cette <div> sera traduit selon la clé Some stuff du domaine de traduction courant. En effet,
lorsque l’attribut i18n:translate est vide, le contenu de la balise est utilisé comme clé de traduction.
...
<div i18n:translate="">Some stuff</div>
...
Ceci permet de traduire des textes dont le contenu ne peut être connu qu’au moment de la publication de la
template.
...
<!-- On traduit le titre lorsque c’est possible -->
<span i18n:translate="" tal:content="context/title_or_id">Ze title</span>
i18n :attributes : Traduction des attributs d’un élément
Les attributs title et alt sont traduits selon la clé fournie par leurs valeurs (statique ou calculée)
...
<img title="img_title" alt=""
tal:attributes="alt image/title"
i18n:attributes="alt title" />
...
i18n :name placer des données variables dans une traduction.
Il existe un système permettant de faire le mapping de messages ‘à trous’ dans les templates. Reprenons notre
exemple précédent : This folder has ${count} images. Si on ne veut pas générer le message dans le code mais dans
la template, on écrira :
...
<p>Hi</p>
Note
Utilisez tout le temps i18n :translate=”“ dans vos templates, même si vous appelez une fonction qui retourne
un objet Message (une chaine commencant par _( dans votre code Python). Bien que la traduction marcherait
252
Chapitre 14. Internationalisation d’un composant
Plone pour les développeurs, Version 1.0.0
sans le préciser avec le moteur de template d’origine, cela ne marcherait pas avec le moteur de template alternatif
Chameleon qui peut être utilisé dans Plone 4.
14.2.5 Traduire directement un message
Vous pouvez avoir besoin de traduire un message directement dans le code python, si pour une raison ou une autre
vous ne pouvez pas retourner un message à votre template.
Dans ce cas, utilisez directement l’API de traduction de zope.i18n. Vous devez passer en paramètre la request, qui
contient les informations permettant de retrouver la langue de l’utilisateur.
def translated(self):
return "Some words"
#message = _(u’my_message’, default=u"Some words")
#return translate(message, context=self.request)
14.3 Localiser un composant
Créez un environnement avec i18ndude installé :
$ mkvirtualenv i18n
$ easy_install i18ndude
Attention
N’installez jamais i18ndude dans l’environnement Python standard ou l’environnement virtuel spécifique à un
projet mais bien dans un environnement virtuel dédié.
Dans collective/trainingmanual/i18nexamples/ créez un script update-l10n.sh :
$ i18ndude rebuild-pot --pot i18nexamples.pot --create i18nexamples ..
$ i18ndude sync --pot i18nexamples.pot */LC_MESSAGES/i18nexamples.po
Le nom des fichiers po doit correspondre au domaine.
Exécutez i18ndude -h pour savoir à quoi servent les différentes commandes.
Installez le package Ubuntu gettext qui contient notamment la commande msginit. Installez également poedit (ou
kbabel) pour traduire facilement :
$ sudo apt-get install gettext poedit
Exécutez le script update-l10n.sh, pour cela ajoutez le mode exécution :
$ chmod u+x update-l10n.sh
$ ./update-l10n.sh
La première exécution du script échouera pour les commandes i18ndude sync car il n’y a encore aucune
traduction.
Pour le domaine i18nexamples, il vous faut tout d’abord créer le dossier dans locales :
$ mkdir -p fr/LC_MESSAGES
Générez le fichier i18nexamples.pot avec le script :
$ ./update-l10n.sh
Ensuite créez les fichiers de traduction :
14.3. Localiser un composant
253
Plone pour les développeurs, Version 1.0.0
$ msginit -i i18nexamples.pot -o fr/LC_MESSAGES/i18nexamples.po
éditez les headers du fichier po comme ceci :
"Language-Code: fr\n"
"Language-Name: French\n"
"Domain: i18nexamples\n"
Traduisez ensuite avec poedit.
Pour que vos traductions soient prises au démarrage de l’instance, vous devez également avoir la directive i18n :registerTranslations dans votre configure.zcml, par exemple :
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:grok="http://namespaces.zope.org/grok"
xmlns:i18n="http://namespaces.zope.org/i18n"
i18n_domain="i18nexamples">
<i18n:registerTranslations directory="locales" />
<grok:grok package="." />
</configure>
14.4 La syntaxe des po en quelques mots
"Header: valeur du header\n"
#: src/name.c:36
msgid "identifiant_du_message"
msgstr "La traduction du message"
14.5 Modifier les traductions de Plone
14.5.1 Créer un fichier de traduction du domaine plone
Si vous voulez modifier des traductions de Plone pour les remplacer par vos propres messages créez à la main
dans locales un fichier plone.pot.
Ajoutez cette ligne à update-l10n.sh :
i18ndude sync --pot plone.pot */LC_MESSAGES/plone.po
Ajoutez le fichier de traduction française avec la commande :
msginit -i plone.pot -o fr/LC_MESSAGES/plone.po
Puis éditez ce fichier pour indiquer la langue, comme vu plus haut.
14.5.2 Modifier une traduction du domaine plone
Si vous voulez modifier une traduction, recherchez d’abord le msgid, qui sera probablement dans
plone.app.locales.
Recopiez l’entrée concernée dans le fichier plone.pot de votre package. Le msgstr doit bien sûr être vide.
254
Chapitre 14. Internationalisation d’un composant
Plone pour les développeurs, Version 1.0.0
#. Default: "Log out"
#: CMFPlone/profiles/default/actions.xml
msgid "Log out"
msgstr ""
Relancez le script de mise à jour.
Vous pouvez maintenant éditer le .po de version française. Mettez “Me déconnecter” (au lieu de “Se...”), puis
redémarrez le site.
14.6 Mise à jour des traductions
Quand vous ajoutez des nouveaux messages dans votre composant, allez dans le répertoire locales et relancez le
script ./update-l10n.sh.
Les nouveaux messages du domaine i18nexamples seront ajoutés dans votre pot, puis synchronisés dans
fr/LC_MESSAGES/i18nexamples.po.
Vous n’aurez plus qu’à compléter les traductions.
Les fichiers .po sont compilés en fichiers .mo. À chaque redémarrage du serveur, zope détecte les .po qui ont été
modifiés et les recompile.
14.7 Ressources
–
–
–
–
–
–
http ://plone.org/documentation/how-to/product-skin-localization
http ://plone.org/documentation/how-to/i18n-for-developers
http ://www.mattdorn.com/content/plone-i18n-a-brief-tutorial/
http ://maurits.vanrees.org/weblog/archive/2007/09/i18n-locales-and-plone-3.0
http ://dev.plone.org/plone/wiki/TranslationGuidelines
http ://n2.nabble.com/Recipe-for-overriding-translations-td3045492ef221724.html
Voir http ://dev.plone.org/plone/ticket/9090 pour l’état de la fusion des documents ci-dessus.
14.6. Mise à jour des traductions
255
Plone pour les développeurs, Version 1.0.0
256
Chapitre 14. Internationalisation d’un composant
CHAPITRE 15
Gestion des utilisateurs avec
PlonePAS
15.1 Présentation
Comme vu dans le petit précis sur Zope, les utilisateurs, mots de passes et leurs rôles associés sont gérés dans des
“User folders”. Ces objets ont l’identificateur réservé acl_users.
Si l’outil fourni en standard avec Zope est amplement suffisant pour une authentification simple et l’octroi de
droits à quelques utilisateurs, il est insuffisant au regard des besoins d’un CMS professionnel tel que Plone.
Depuis sa version 2.5, la gestion des utilisateurs est motorisée par un service plus complet et flexible que le simple
BasicuserFolder. Il s’agit de PlonePAS, basé sur le composant PluggableAuthService (PAS) de
Zope corp. 1
A la gestion des login / mot de passe et des rôles des utilisateurs, PlonePAS ajoute :
– La distinction entre le login et le user id
– La gestion des propriétés des utilisateurs (adresse mail, nom complet, ...)
– La gestion des groupes d’utilisateurs
– L’attribution de rôles globaux et contextuels
De plus, et comme son nom l’indique, les mécanismes de base de PlonePAS sont fournis par des composants
dits plugin qui permettent une grande souplesse pour personnaliser ses propres règles :
– D’extraction des données d’authentification
– D’authentification
– D’attribution de rôles
– De classement des utilisateurs dans des groupes
PlonePAS est muni d’origine d’un ensemble de plugins standard pré-paramétrés lors de la construction du site
qui fournissent les services par défaut. De nombreux plugins de tierce partie permettent de personnaliser tout ceci
pour intégrer au mieux votre site Plone dans votre système d’informations et selon votre politique de sécurité.
Par exemple et avec une simple intégration de composants tiers et paramétrage ad hoc vous pouvez tout à la fois :
– Intégrer Plone dans l’infrastructure SSO (NTLM, CAS) de votre entreprise
– Assigner automatiquement l’adresse mail <[email protected]> à chaque utilisateur
– Obtenir le nom complet et le département d’un utilisateur dans un serveur LDAP ou ActiveDirectory
– Grouper les utilisateurs par fonction (direction, ingénierie, commercial, production, marketing par exemple) et
par entités (fromages, apéritifs par exemple) à l’aide d’une requête dans un SGBDR externe classique (MySQL,
Oracle...)
Ceci n’est qu’un exemple parmi tant d’autres envisageables et plus ou moins complexes.
1. Notons que PAS n’est pas un composant strictement Plone et peut être utilisé dans n’importe quelle application Zope.
257
Plone pour les développeurs, Version 1.0.0
15.2 Architecture fonctionnelle
Lorsque l’un des services de PlonePAS est sollicité par l’application - Plone ou l’un de ses composants d’extension – celui-ci sollicite l’un après l’autre chaque plugin compatible avec l’opération demandée.
L’ordre de sollicitation des plugins pour chaque service est paramétrable en ZMI par l’intégrateur.
Le schéma générique ci-dessous résume les opérations effectuées lors de l’utilisation par une application de l’un
des services de PlonePAS.
Quelques commentaires sur ce schéma :
– L’utilisation du cache n’est pas systématique. Dans certains cas, les plugins eux-même peuvent être appellés à
gérer leur propre cache.
– Pour certains services de PAS, plusieurs catégories de plugins peuvent être utilisées. Le bloc “Sélection des
plugins ...” et “Traitement des données ...” étant alors réitéré pour une autre catégorie de plugins.
258
Chapitre 15. Gestion des utilisateurs avec PlonePAS
Plone pour les développeurs, Version 1.0.0
– Pour certains services, par exemple l’authentification d’un utilisateur, PlonePAS ne retient les résultats que
d’un seul plugin, le premier qui a fourni un résultat positif. Pour d’autres services, tel que l’obtention des
propriétés d’un utilisateur, les résultats fournis par l’ensemble des plugins sélectionnés sont agglomérés.
15.3 PlonePAS en ZMI
Un petit tour en ZMI vous permettra de mieux comprendre tout ceci...
La vue principale d’un PlonePAS expose l’ensemble des plugins - ici ne sont exposés que les plugins installés par
défaut sur un site Plone 4 - ainsi que l’objet plugins, chef d’orchestre des règles de fonctionnement de PlonePAS.
Intéressons-nous à ce dernier en premier. 2
15.3.1 Catégories de plugins
Ouvrons maintenant l’objet acl_users/plugins qui est le “chef d’orchestre” de l’utilisation des plugin.
2. Ceci est un oxymore, je le sais.
15.3. PlonePAS en ZMI
259
Plone pour les développeurs, Version 1.0.0
Cette vue vous propose les différentes catégories de plugins de PlonePAS. Comme vous pouvez le constater,
chaque catégorie de plugin est expliquée brièvement.
Une petite traduction commentée :
260
Chapitre 15. Gestion des utilisateurs avec PlonePAS
Plone pour les développeurs, Version 1.0.0
Anonymoususerfactory Plugins
Les plugins de ce type sont des factories d’utilisateurs anonymes. Plone en standard n’en utilise pas, sachant qu’il
ne propose pas de fonctionnalité nécessitant un objet évolué, contrairement aux utilisateurs authentifiés.
Interface : Products.PluggableAuthService.interfaces.plugins.IAnonymousUserFactoryPlugin
Authentication Plugins
Les plugins de ce type sont responsable de vérifier la validité d’une accréditation, généralement un couple identité,
mot de passe.
Interface : Products.PluggableAuthService.interfaces.plugins.IAuthenticationPlugin
Challenge Plugins
Ces plugins sont chargés de retourner directement ou indirectement une ressource à partir de laquelle l’utilisateur
pourra fournir ses accréditations - par exemple la page d’authentification. Ce genre de plugin est sollicité lorsqu’un
utilisateur anonyme tente d’accéder directement ou indirectement à une ressource protégée.
Interface : Products.PluggableAuthService.interfaces.plugins.IChallengePlugin
Challenge Protocol Chooser Plugins
Ces plugins sont chargés de fournir une liste de protocoles (HTTP, FTP, ...) pour lesquels un challenge d’authentification peut être lancé. Vous n’aurez généralement pas besoin de changer le paramétrage du chooser standard
fourni avec Plone, encore moins de réaliser votre propre plugin de ce type.
Interface : Products.PluggableAuthService.interfaces.plugins.IChallengeProtocolChooser
Reset Credentials Plugins
Les plugins de ce type sont chargés de supprimer le jeton d’authentification de l’utilisateur. Ils sont sollicités
lorsque l’utilisateur de déconnecte du site. Comme le jeton d’authentification est récupéré par un plugin de type
Extraction Plugins, et est placé par un plugin de type Update Credentials Plugins, ces trois rôles sont généralement
assurés par le même plugin qui est donc un multiplugin.
Interface : Products.PluggableAuthService.interfaces.plugins.ICredentialsResetPlugin
Update Credentials Plugins
Les plugins de ce type sont chargés de fournir au client le jeton d’authentification, après une authentification
réussie à travers un Authentication Plugins ou un changement de mot de passe.
Interface : Products.PluggableAuthService.interfaces.plugins.ICredentialsUpdatePlugin
Extraction Plugins
Les plugins de ce type ont pour rôle d’extraire l’accréditation du request. C’est-à-dire le jeton d’authentification de
l’utilisateur authentifié. Dans le cas du protocole HTTP, celui-ci est généralement placé dans un header (typiquement WWW-Authenticate) ou dans un cookie. C’est typiquement ce type de plugin que vous devrez réaliser
pour intégrer votre site Plone dans une infrastructure SSO.
Interface : Products.PluggableAuthService.interfaces.plugins.IExtractionPlugin
15.3. PlonePAS en ZMI
261
Plone pour les développeurs, Version 1.0.0
Group_Enumeration Plugins
Les plugins de ce type permettent de retrouver des informations sur des groupes en fonction de critères de recherche.
Interface : Products.PluggableAuthService.interfaces.plugins.IGroupEnumerationPlugin
Group_Introspection Plugins
Les plugins de ce type sont sollicités pour fournir des informations avancées sur les groupes (informations étendues
d’un groupe, membres d’un groupe, liste de groupes, ...). En général, ce genre de plugin est un multiplugin qui est
également un Group_Enumeration Plugins
Interface : Products.PlonePAS.interfaces.group.IGroupIntrospection
Group_Management Plugins
Les plugins de ce type gèrent des groupes dits “mutables”, c’est à dire dont les caractéristiques et les membres
sont gérés à travers Zope et Plone comme c’est le cas du gestionnaire de groupes fourni en standard par Plone. Un
multiplugin de ce type est généralement également de type Groups Plugins.
Interface : Products.PlonePAS.interfaces.group.IGroupManagement
Groups Plugins
Les plugins de ce type fournissent les noms des groupes auxquels appartient un principal.
Interface : Products.PluggableAuthService.interfaces.plugins.IGroupsPlugin
Local_Roles Plugins
Les plugins de ce type fournissent les rôles dont dispose un principal dans un contexte donné.
Interface : Products.PlonePAS.interfaces.plugins.ILocalRolesPlugin
Properties Plugins
Les plugins de ce types sont chargés de fournir des propriétés à un principal (exemple : nom comlet, adresse mail,
etc.). Ces propriétés sont fournies sous forme de. Lors de la collection des propriétés d’un principal, les propriétés
fournies par l’ensemble des Properties Plugins activés sont cumulées.
Interface : Products.PluggableAuthService.interfaces.plugins.IPropertiesPlugin
À
noter
que
les
plugins
de
ce
type
peuvent
également
implémenter
l’interface
Products.PlonePAS.interfaces.plugins.IMutablePropertiesPlugin lorsqu’ils offrent
à Plone la possibilité de modifier les propriétés de l’utilisateur.
Request_Type_Sniffer Plugins
Les plugins de ce type ont pour rôle de déterminer le type de requête de l’application cliente (WebDav, XML-RPC,
FTP ou navigateur). Sauf en cas de besoins “spéciaux” pour un type de client particulier, vous n’aurez jamais à
réaliser de plugin de ce type, ni même à modifier le paramétrage du plugin de ce type installé en standard.
Interface : Products.PluggableAuthService.interfaces.plugins.IRequestTypeSniffer
262
Chapitre 15. Gestion des utilisateurs avec PlonePAS
Plone pour les développeurs, Version 1.0.0
Role_Assigner Plugins
Les plugins de ce type permettent d’ajouter ou de supprimer de façon persistente un rôle à un principal.
Interface : Products.PluggableAuthService.interfaces.plugins.IRoleAssignerPlugin
Role_Enumeration Plugins
Les plugins de ce type permettent d’obtenir une liste de rôles (sous forme de mapping incluant des informations
supplémentaires) répondant à des critères de recherche.
Interface : Products.PluggableAuthService.interfaces.plugins.IRoleEnumerationPlugin
Roles Plugins
Les plugins de ce type permettent d’assigner des rôles à un principal. L’ensemble des plugins activés sont sollicités
lors de chaque transaction pour déterminer les rôles dont dispose un utilisateur.
Interface : Products.PluggableAuthService.interfaces.plugins.IRolesPlugin
Update Plugins
Cette interface n’est utilisée nulle part.
Interface : Products.PluggableAuthService.interfaces.plugins.IUpdatePlugin
User_Adder Plugins
Les plugins implémentant cette interface ont l’aptitude d’ajouter des utilisateurs de façon persistente. Attention,
on utilise plus souvent l’interface User_Management Plugins plus complète qui hérite de la présente interface.
Interface : Products.PluggableAuthService.interfaces.plugins.IUserAdderPlugin
User_Enumeration Plugins
Les plugins implémentant cette interface sont utilisés pour énumérer les utilisateurs selon des critères particuliers.
Par exemple pour rechercher les utilisateurs dont le nom contient “dup”.
Interface : Products.PluggableAuthService.interfaces.plugins.IUserEnumerationPlugin
Userfactory Plugins
Les plugins implémentant cette interface sont susceptibles de construire des objets de type “utilisateur”. Ce type
d’objet, conforme aux utilisateurs Zope standard (sécurité, etc.) fournit également les propriétés de l’utilisateur
(adresse mail, nom complet, ...). Dans le cadre de l’utilisation dans Plone, un plugin de ce type est déjà fourni, et
sauf exception spécifique, il sera très rarement nécessaire de fournir un plugin spécialisé.
Interface : Products.PluggableAuthService.interfaces.plugins.IUserFactoryPlugin
User_Introspection Plugins
Les plugins implémentant cette interface permettent de lister sous différentes formes tous les utilisateurs qu’ils
“contiennent”. Généralement, les plugins de ce type sont également des Authentication Plugins.
Interface : Products.PlonePAS.interfaces.plugins.IUserIntrospection
15.3. PlonePAS en ZMI
263
Plone pour les développeurs, Version 1.0.0
User_Management Plugins
Les plugins implémentant cette interface fournissent les outils pour changer le mot de passe d’un utilisateur et
pour supprimer un utilisateur.
Interface : Products.PlonePAS.interfaces.plugins.IUserManagement
Validation Plugins
Les plugins implémentant cette interface valident les modifications de propriétés d’un utilisateur.
Interface : Products.PluggableAuthService.interfaces.plugins.IValidationPlugin
15.4 Les plugins standard
PlonePAS est fourni en standard comme nous l’avons vu d’un certain nombre de plugins. En voici la description
et les recommandations d’utilisation.
15.5 Les plugins de tierce partie
Vous ne trouvez pas dans Les plugins standard celui ou ceux dont vous avez besoin pour votre politique sécurité et
l’infrastructure des données d’entreprise existante. Vous trouverez certainement ce qu’il vous faut dans les plugins
de tierce partie.
Nous vous en présentons quelques-uns ici.
15.6 Votre plugin
Hélas rien ne convient à votre cas particulier dans la collection de plugins PlonePAS que nous avons présentés
dans Les plugins standard et Les plugins de tierce partie. Eh bien il va falloir vous cracher dans les mains et
réaliser vous-même votre propre plugin.
264
Chapitre 15. Gestion des utilisateurs avec PlonePAS
CHAPITRE 16
L’environnement de développement
Le but est de présenter et permettre l’acquisition des outils utilisés pour le développement Plone 3.
16.1 Installation de Python 2.4
Sur Ubuntu :
apt-get install python2.4 python2.4-dev build-essential libxml2-dev libxslt1-dev libjpeg62-dev zli
build-essential permet d’installer le compilateur gcc et make entre autres.
16.2 Configuration de l’éditeur vim
Installez gvim :
apt-get install vim-gnome
Créez un fichier ~/.vimrc avec la configuration suivante :
" http://www.vex.net/~x/python_and_vim.html
" for Python
ab pdbs import pdb; pdb.set_trace ()
" (Ne mettez pas l’espace avant les parenthèses)
set pastetoggle=<F8>
syntax on
autocmd BufRead *.py set smartindent cinwords=if,elif,else,for,while,try,except,finally,def,class
autocmd BufRead *.xml,*pt set smartindent tabstop=2 shiftwidth=2 smarttab expandtab softtabstop=2
autocmd BufRead *.rst set smartindent tabstop=4 shiftwidth=4 smarttab expandtab softtabstop=4
Cette configuration permet de configurer l’indentation à 4 espaces pour les fichiers Python et reST, 2 espaces pour
les fichiers XML et PT. Elle permet aussi d’activer la coloration syntaxique et passer en mode “collage” à l’aide
de la touche F8.
16.3 Configuration de l’autocomplétion dans Python
Créer un fichier ~/.pythonstartup avec le contenu suivant :
265
Plone pour les développeurs, Version 1.0.0
# python startup file
import readline
import rlcompleter
import atexit
import os
# tab completion
readline.parse_and_bind(’tab: complete’)
# history file
histfile = os.path.join(os.environ[’HOME’], ’.pythonhistory’)
try:
readline.read_history_file(histfile)
except IOError:
pass
atexit.register(readline.write_history_file, histfile)
del os, histfile, readline, rlcompleter
Éditez ~/.bashrc et décommentez les 3 lignes qui incluent ~/.bash_aliases.
Créez le fichier ~/.bash_aliases avec le contenu :
export PYTHONSTARTUP=~/.pythonstartup
alias archgenxml=~/workspace/archgenxml_buildout/bin/archgenxml
alias argouml="cd ~/opt/argouml-0.28/ && ./argouml.sh"
export EDITOR=vim
# https://wiki.ubuntu.com/Spec/EnhancedBash
shopt -s histappend
PROMPT_COMMAND="history -a; $PROMPT_COMMAND"
export HISTSIZE=1000
export HISTFILESIZE=1000
export GREP_OPTIONS=’--color=auto --exclude=*.svn-base --exclude=*.pyc’
Ici nous avons en plus les alias pour archgenxml et argouml que vous installerez par la suite. La dernière section
est pour partager l’historique à travers tous les terminaux et mettre de la couleur pour la commande grep.
16.4 Activation de la recherche dans l’historique
Dans le fichier /etc/inputrc décommenter ces lignes :
"\e[5~": history-search-backward
"\e[6~": history-search-forward
Ces lignes permettent de rechercher dans l’historique de bash avec les touches Page Up/Page Down. Par exemple,
tapez ssh<PageUp>.
16.5 Installation de ArgoUML et ArchGenXML
Suivre la documentation de ArchGenXML.
Pour vite installer ArgoUML :
sudo apt-get install sun-java6-jdk
mkdir ~/opt
mkdir ~/downloads
cd ~/downloads
wget http://argouml-downloads.tigris.org/nonav/argouml-0.28.1/ArgoUML-0.28.1.tar.gz
cd ~/opt
tar xvf /home/vincentfretin/downloads/ArgoUML-0.28.1.tar.gzmkdir ~/opt
266
Chapitre 16. L’environnement de développement
Plone pour les développeurs, Version 1.0.0
16.6 Checkout svn.zope.org
Si vous avez un accès subversion au dépôt de Zope, créez votre fichier ~/.ssh/config comme ceci :
Host svn.zope.org
User votre_login
Comme cela, si votre compte unix est différent de votre compte svn, vous n’aurez pas besoin de spécifier votre
login lors d’un checkout.
Pour réaliser un checkout :
svn co svn+ssh://svn.zope.org/repos/main/grok
16.6. Checkout svn.zope.org
267
Plone pour les développeurs, Version 1.0.0
268
Chapitre 16. L’environnement de développement
CHAPITRE 17
La gestion des sources avec
subversion
Le but est d’acquérir les connaissances de gestionnaire de code source subversion utilisé par la communauté Plone.
17.1 Savoir
–
–
–
–
Création d’un dépôt
Dump d’un dépôt
Utilisation de subversion en ligne de commande
Commandes : add cat changelist (cl) checkout (co) cleanup commit (ci) copy (cp) delete (del, remove, rm) diff
(di) export help ( ?, h) import info list (ls) lock log merge mergeinfo mkdir move (mv, rename, ren) propdel
(pdel, pd) propedit (pedit, pe) propget (pget, pg) proplist (plist, pl) propset (pset, ps) resolve resolved revert
status (stat, st) switch (sw) unlock update (up)
– Introduction à subversion
– Un livre de référence : svnbook Red Bean
17.2 Création d’un dépôt via Apache
http ://doc.ubuntu-fr.org/subversion
Création et configuraton d’un dépôt à travers Apache :
$ sudo apt-get install apache2 libapache2-svn subversion
$ sudo vim /etc/apache2/mods-enabled/dav_svn.conf
<Location /Formation>
DAV svn
SVNPath /home/svn/Formation
AuthType Basic
AuthName "Subversion Repository"
AuthUserFile /etc/apache2/dav_svn.passwd
Require valid-user
</Location>
$
$
$
$
/etc/init.d/apache2 restart
htpasswd -cs /etc/apache2/dav_svn.passwd anthony
htpasswd -s /etc/apache2/dav_svn.passwd vincent
htpasswd -s /etc/apache2/dav_svn.passwd stephane
269
Plone pour les développeurs, Version 1.0.0
$ mkdir /home/svn
$ svnadmin create /home/svn/Formation
$ chown -R www-data:www-data /home/svn/Formation
17.3 Installation du client subversion
Sous Linux :
$ apt-get install subversion
Sous Windows, installez SlickSVN à partir du site http ://subversion.tigris.org
17.4 Administration d’un dépôt subversion
17.4.1 Faire un dump d’un dépôt existant
Exécutez :
$ svnadmin dump /home/svn/Formation > /tmp/svn_agile.dump
17.4.2 Importer les révisions dans un autre dépôt
Commandes :
$ svnadmin create /home/svn/autre
$ svnadmin load /home/svn/autre < /tmp/svn_agile.dump
On vérifie :
$ svn co file:///home/svn/autre
17.5 Mettre à jour la branche de production
Vous avez commité un changement dans le trunk, il faut le backporter dans la branche production.
Le commit sur le trunk est la révision 1023, pour merger ce commit sur la branche de production :
$ cd path/to/trunk/workingcopy
$ svn info
URL : <url_to_repository>/trunk
trunk$ cd ../branches/production
branches/production$ svn merge -c 1023 <url_to_repository>/trunk .
Puis il faut commiter le résultat en précisant dans le message les numéros de versions et leur origine :
$ cd path/to/branches/production
$ svn info
...
Révision : 1025
...
branches/production$ svn ci -m"Merged -r1023:1025 from trunk"
270
Chapitre 17. La gestion des sources avec subversion
Plone pour les développeurs, Version 1.0.0
17.5.1 Connaitre les révisions mergées
Sur un serveur subversion >= 1.5 seulement :
$ cd path/to/branches/production
$ svn mergeinfo <url_to_repository>/trunk .
17.5.2 Connaitre les révisions à merger
Sur un serveur subversion >= 1.5 seulement :
$ cd path/to/branches/production
$ svn mergeinfo --show-revs eligible <url_to_repository>/trunk .
17.5. Mettre à jour la branche de production
271
Plone pour les développeurs, Version 1.0.0
272
Chapitre 17. La gestion des sources avec subversion
CHAPITRE 18
Migration des composants
développés pour Plone 2.5
18.1 Définition
La façon d’écrire un produit Plone a complètement changé entre la version 2.5 et 3. Les workflows ne sont plus
écrit en Python, mais en XML. Plone 3 privilégie plus un développement à la Zope 3, amplifiant une tendance qui
avait fait son apparition avec Plone 2.5 (utilisation de Five et de la notation ZCML)
18.2 Savoir
– Migration des anciens modèles UML vers la dernière version de ArgoUML
– Génération de code avec la dernière version de ArchGenXML
– Migration du code de l’ancien composant vers le nouveau composant
18.3 Outils
Sous Ubuntu pour comparer deux dossiers, on peut utiliser kompare (non éditable) ou meld (éditable) :
$ sudo apt-get install kompare meld
Pour utiliser meld par exemple :
$ meld dossier1 dossier2
Sous MacOSX, les éditeurs BBEdit et TextWrangler proposent également une comparaison de deux dossiers.
18.4 Outils pour la preview des documents
Pour le support de la prévisualisation des documentations avec ARFilePreview et AROfficeTransforms, avec les
systèmes Ubuntu ou Debian :
$ sudo apt-get install wv poppler-utils ppthtml xlhtml pstotext unrtf
273
Plone pour les développeurs, Version 1.0.0
18.5 Installation du produit AttachmentField
Éditez le fichier buildout.cfg comme ceci :
[buildout]
parts = ... productdistros
[instance]
products = ...
${productdistros:location}
[productdistros]
# For more information on this step and configuration options see:
# http://pypi.python.org/pypi/plone.recipe.distros
recipe = plone.recipe.distros
urls = http://plone.org/products/attachmentfield/releases/1.4.4/attachmentfield-1-4-4.tgz
nested-packages =
version-suffix-packages =
18.6 Génération du produit AgileKnowledgeBase
Tout d’abord générez le produit AgileKnowledgeBase avec la dernière version de ArchGenXML.
Vincent fait un checkout :
$ svn co --username vincent http://devagile/Agile
$ cd Agile
Anthony fait un checkout, crée et commit un dossier packages :
$
$
$
$
svn co --username anthony http://devagile/Agile
cd Agile
svn mkdir packages
svn ci -m "Add new packages directory"
Vincent fait un update :
$ svn log -v
-----------------------------------------------------------------------r1 | anthony | 2009-06-04 14:51:20 +0200 (jeu 04 jun 2009) | 1 line
Chemins modifiés :
A /packages
add new packages directory
------------------------------------------------------------------------
Anthony fait :
$ cd /tmp
$ paster create -t basic_namespace Products.AgileKnowledgeBase \
--svn-repository=http://devagile/Agile/packages/
$ cd Products.AgileKnowledgeBase
$ svn stat
$ svn rm --force Products.AgileKnowledgeBase.egg-info
$ svn ci -m "Add skeleton"
$ cd projet/packages
$ svn up
$ cd Products.AgileKnowledgeBase/trunk
$ svn mkdir model
$ svn ci -m "Added a model directory"
274
Chapitre 18. Migration des composants développés pour Plone 2.5
Plone pour les développeurs, Version 1.0.0
Copiez-collez le fichier zargo dans ce dossier model et ajoutez le au référentiel svn :
$ svn add model/AgileKB.zargo
$ svn ci -m"Added zargo model"
$ cd Products
$ archgenxml ../model/AgileKB.zargo
Note : sous Windows les images sont générées bizarrement.
Éditez le fichier profiles/default/types.xml pour changer le meta_type du type d’agile_document :
<object
name="agile_document"
meta_type="Factory-based Type Information for Plone Articles content types"/>
Vous devrez faire attention à garder ce changement à chaque fois que vous regénèrerez le modèle.
Mettez 1.0 pour la version dans profiles/default/metadata.xml :
$ svn add AgileKnowledgeBase/*
$ svn ci -m"Add generated code"
Ajoutez Products.PloneArticle dans install_requires de setup.py.
Dans profiles/default/metadata.xml :
<?xml version="1.0"?>
<metadata>
<version>1</version>
<dependencies>
<dependency>profile-Products.PloneArticle:default</dependency>
</dependencies>
</metadata>
Ajoutez Products.PloneArticle dans l’option eggs de la section [instance] sinon il ne sera pas dans
le sys.path du script bin/instance
18.7 Configuration du calendrier
Il faut ajouter le type agile_event dans le tool portal_calendar pour que les évènements s’affichent dans le
calendrier.
18.8 Création d’un nouveau Plone Site
Créez un nouveau Plone Site, installez les produits.
18.9 Migration du contenu
Sur le serveur en production :
– Passer le storage de FSS en AttributeStorage
– Faire un export au format zexp de acl_users, portal_memberdata, portal_groupdata, aide, dossiers des espaces
– Copier les logs
Sur le nouveau serveur :
– copier tous les zexp dans le dossier parts/instance/import/
– copier les logs en les renommant
18.7. Configuration du calendrier
275
Plone pour les développeurs, Version 1.0.0
– importer les utilisateurs :
1. Go to your new site and Delete the following objects (all relative to the Plone site root) :
– acl_users -> Contents -> local_roles
– acl_users -> Contents -> mutable_properties
– acl_users -> Contents -> portal_role_manager
– acl_users -> Contents -> source_groups
– acl_users -> Contents -> source_users
– portal_groupdata
– portal_memberdata
2. Copy the corresponding objects from the old site and paste it into the new one at the same location.
3. For the 5 new objects in acl_users go to their Activate tab and activate them (check all checkboxes and
click on Update).
4. Cut & paste Members’ content
– importer les espaces
– Aller dans plonearticle_tool et faire la migration en sélectionnant la version courante
18.10 Ajout nouvelles fonctionnalités
18.10.1 Prévisualisation des documents Word et PDF
Hommage à Jean-Nicolas Jnut-Bès
Jean-Nicolas, auteur principal de ARFilePreview et AROfficeTransforms nous a quitté à la suite d’une
longue maladie qu’il a courageusement combattu. Nous rendons ici hommage à l’homme et au créateur qu’il fut.
ARFilePreview est un produit qui ajoute la prévisualisation des documents Word et PDF.
Vous pouvez installer le produit AROfficeTransforms pour installer des transformations pour Excel et OpenOffice.org.
La dernière release de ARFilePreview sur plone.org est assez vieille, donc vous allez télécharger la dernière version de la branche v2. (La branche v2 fonctionne avec portal_transforms, le trunk (v3) fonctionne avec
plone.transforms.) Pour cela, vous allez utiliser la recipe infrae.subversion pour faire un checkout du produit :
[buildout]
parts =
...
development-products
[development-products]
recipe = infrae.subversion
urls =
http://svn.plone.org/svn/collective/ARFilePreview/branches/ARFilePreview-v2@88162 ARFilePrevie
location = products
Il faut que vous ayez la section productdistros activée pour que le produit soit pris en compte au démarrage.
Pour installer AROfficeTransforms, ajoutez l’URL de l’archive dans la section productdistros :
[productdistros]
...
urls =
...
http://plone.org/products/arofficetransforms/releases/0.9.2/arofficetransforms-0-9-2.tgz
276
Chapitre 18. Migration des composants développés pour Plone 2.5
Plone pour les développeurs, Version 1.0.0
18.10.2 Génération de pdf
Installez Products.SmartPrintNG.
Pour générer les PDFs, SmartPrintNG a besoin de fop :
$ apt-get install fop
Dans la section [instance], ajoutez :
environment-vars =
FOP_HOME /usr/bin
fop cherche la librairie Java servlet-api.jar. Vous pouvez rechercher dans quel package Ubuntu il se trouve avec
apt-file search.
Tout d’abord installez la commande apt-file et mettez à jour la base de données :
$ sudo apt-get install apt-file
$ sudo apt-file update
Cherchez où se trouve ce fichier :
$ apt-file search servlet-api.jar
eclipse-platform: /usr/lib/eclipse/plugins/org.eclipse.tomcat_5.5.17/lib/servlet-api.jar
groovy: /usr/share/groovy/lib/servlet-api.jar
libservlet2.4-java: /usr/share/java/servlet-api.jar
libtomcat5.5-java: /usr/share/tomcat5.5/common/lib/servlet-api.jar
tomcat6: /var/lib/tomcat6/lib/servlet-api.jar
tomcat6-common: /usr/share/tomcat6/lib/servlet-api.jar
Installez le package voulu :
$ sudo apt-get install libservlet2.4-java
18.11 Exercice
Migration du composant “base de connaissances” comme cas pratique.
18.11. Exercice
277
Plone pour les développeurs, Version 1.0.0
278
Chapitre 18. Migration des composants développés pour Plone 2.5
CHAPITRE 19
Programmation dirigée par les tests
19.1 Définition
Apprentissage des techniques qui permettent d’assurer la qualité du logiciel. Apprendre à écrire des tests unitaires,
d’intégration et fonctionnels.
19.2 Savoir
– Tests unitaires avec le module unittest
– Tests d’intégration
– Tests fonctionnels avec selenium IDE
19.3 Test unitaire
Les tests unitaires sont écrit à l’aide du module unittest.
Dans un packages Python, la convention est de créer soit un module tests.py, soit un package tests avec plusieurs
fichiers commencant par test_ dans ce packages. Il est en général conseillé de commencer tout de suite avec
un package tests, plutôt qu’un module si l’on sait que le projet contiendra de nombreux modules. En effet la
convention est de créer un fichier test_${module}.py pour chacun des modules de votre application.
Donc dans votre package, créez un packages tests :
mkdir tests
touch tests/__init__.py
Lancer les tests :
bin/instance test -s collective.groupdelegation
Vous pouvez utiliser la recipe zc.recipe.testrunner pour créer un script bin/test qui teste plusieurs eggs :
[test]
recipe = zc.recipe.testrunner
eggs =
collective.groupdelegation
formation.portlet.docinfo
279
Plone pour les développeurs, Version 1.0.0
19.4 Exercice
Écriture des différents types de tests sur le composant “base de connaissances”
280
Chapitre 19. Programmation dirigée par les tests
CHAPITRE 20
Programmation dirigée par la
documentation
20.1 Définition
Faciliter la lecture de son code par une documentation exhaustive et exacte est une exigence du travail collaboratif
des projets open source comme Plone. Nous verrons ici les techniques et normes à maîtriser et utiliser.
20.2 Savoir
– Module doctest
– Format reStructuredText
20.3 Exercice
Création d’une documentation avec tests sur le composant “base de connaissance”.
281
Plone pour les développeurs, Version 1.0.0
282
Chapitre 20. Programmation dirigée par la documentation
CHAPITRE 21
Sensibilisation aux méthodes agiles
Les méthodes agiles telles que SCRUM sont parfaitement adaptées à la gestion des projets open source comme le
développement de portails basés sur Plone.
21.1 Savoir
–
–
–
–
–
–
–
–
–
itérations/sprints, releases
radiateur d’informations
stand up meeting
calcul de la vélocité
planning poker
eXtreme Programming
code review
refactoring
pair programming
21.2 Ressources
–
–
–
–
–
–
–
L’organisation d’une équipe de développement logiciel, Emmanuel CHENU
Article de Martin Aspeli sur scrum pour gérer un projet GSOC
Scrum sur Wikipedia
Blog de Claude Aubry sur scrum et l’agilité
Blog Agilex : Agilité et Expertise
Livre Scrum de Claude Aubry
Livre Kanban et Scrum
21.3 Exercice
Mise en pratique de certains concepts tout au long de la formation.
283
Plone pour les développeurs, Version 1.0.0
284
Chapitre 21. Sensibilisation aux méthodes agiles
CHAPITRE 22
Glossaire
Définition des termes et abréviations utilisés dans ce document.
adapter Classe Python fournissant aux objets exposant une certaine interface des services supplémentaires, sans
utiliser directement la technologie d’héritage Python. Les adapteurs font partie des concepts amenés par la
ZCA (Zope Component Architecture).
adaptateur Traduction libre de adapter.
affectation On affecte une variable par une valeur en utilisant le signe = (qui n’a rien à voir avec l’égalité en
math !). Dans une affectation, le membre de gauche reçoit le membre de droite ce qui nécessite d’évaluer la
valeur correspondant au membre de droite avant de l’affecter au membre de gauche.
a = 2 # le symbole "a" est une référence à un objet entier valant "2".
b = 7.2 * math.log(math.e / 45.12) - 2*math.pi
c = b ** a
Les affectations relient les identificateurs aux données : si une donnée en mémoire n’est plus reliée, le
ramasse-miettes (garbage collector) de Python la supprime automatiquement
AT Abréviation courante d’Archetypes utilisée dans les documentations, blogs, mailing-lists, forums ayant trait
à Plone.
bool Classe de base Python pour les objets booléens. Ces derniers ne pouvant être que True ou False.
brain Toute requête au catalogue fournit une itération de brains. Chaque brain étant une référence à un élément
de contenu, et incorpore la copie de certains attributs de celui-ci (titre, description, ...). cheeseshop voir pypi.
buildout Buildout – ou zc.buildout – est un outil multi-plateformes conçu pour installer et paramétrer les différents composants logiciels d’un système d’informations. Plone est installable par ce mode depuis sa version
2.5. zc.buildout permet en outre de personnaliser l’installation des composants qui concourent à l’installation de “votre” site Plone en modifiant son fichier de configuration. Le packaging sous forme d’eggs est
favorisé.
C Langage compilé de bas niveau utilisé pour coder - entre autres - Linux et Python. Un compilateur C est
nécessaire pour installer Zope ainsi que de nombreuses extensions Python.
component Package Python fournissant des fonctionnalités supplémentaires à Zope ou Plone. Les composants
sont installés le plus souvent dans $INSTANCE_HOME/lib/python, et plus généralement dans les répertoires du système de fichiers désignés par la directive path du fichier de configuration de votre instance
Zope. L’extension de Zope ou Plone par composants est la méthode recommandée pour faciliter la future
migration vers le pur Zope 3. Attention, si un composant comporte des directives ZCML, celui-ci doit être
déclaré dans la configuration de de votre instance Zope.
composant Traduction libre de component.
configlet Panneau de configuration de Plone permettant de paramétrer de façon globale certains aspects de Plone.
Par exemple, le panneau de configuration des utilisateurs et des groupes ou le thème graphique. Certains
composants d’extension de tierce partie ajoutent leur panneau de configuration.
content type Un type de contenu est le moule avec lequel tous les élément d’un certain type sont gérés. Un type
de contenu incorpore les dispositifs suivant :
285
Plone pour les développeurs, Version 1.0.0
–
–
–
–
factory de création de l’élément
vues HTML de l’élément schéma d’informations portées par l’élément, incluant ses méta-données
formulaires de création/modification de ce schéma d’informations
services “métier” proposés par l’élément.
distribute Distribute est une refonte et une alternative à setuptools élaborée à l’initiative de Tarek Ziadé.
distribute fournit les outils de packaging, de distribution et d’installation de packages Python sous
forme d’egg.
Nous recommandons vivement l’utilisation de distribute dont les contributeurs sont bien plus réactifs
que ceux de setuptools. Bien entendu, distribute est packagé sous forme d’egg.
distutils distutils est un package fourni en standard avec Python fournissant les outils de packaging. distribute s’appuie sur distutils et on préfèrera l’utilisation de ce dernier qui fournit des outils plus avancés.
dotted name Il n’y a pas de traduction en français facile et concise pour ce terme. Un dotted name permet
- généralement dans un fichier ZCML - de désigner un symbole Python quelconque (objet, classe, fonction,
...) sous la forme d’une chaîne de caractères proche de celle utilisée par le mot clé Python import. Par
exemple, le dotted name foo.bar.stuff représente l’équivalent en python from foo.bar import
stuff.
En outre, la notation dotted name admet la notation de package relatif par l’utilisation du préfixe ..
Par exemple .foo.bar dans un fichier ZCML permet de référencer l’objet (classe, fonction, ...) bar du
module foo.py lorsque ce module figure dans le même répertoire que le fichier ZCML. Un préfixe de
deux points (..) permet d’accéder au répertoire parent de celui incluant le fichier ZCML et ainsi de suite.
egg Packaging d’un module Python incluant ses méta-données telles que sa version, sa documentation et les
éventuelles dépendances d’autres eggs. La grande majorité des eggs publics sont distribués sur le site pypi.
Reportez-vous à la documentation ‘intégrateur’ pour plus de détails.
eggs Voir egg.
élément (de contenu) ou “content item”. Unité atomique de contenu fournie par un auteur, tel qu’un document,
une image, un fichier. Certains types d’éléments, tel que le dossier ou la collection sont dits également conteneurs ou “folderish” de par leur aptitude à contenir d’autres éléments de la même façon qu’un répertoire
peut inclure d’autres fichiers et répertoires dans un disque dur.
expression Python : Une expression est une portion de code que l’interpréteur Python peut évaluer pour obtenir
une valeur. Les expressions peuvent être simples ou complexes. Elles sont formées d’une combinaison de
littéraux, d’identifiants et d’opérateurs.
TALES : Expression utilisées dans les templates ZPT pour calculer les attributs et les contenus des éléments.
groupe (d’utilisateurs). Un groupe d’utilisateurs permet d’associer un ensemble d’utilisateurs à une fonction
dans un site Plone. Ceci permet - principalement - d’octroyer des droits particuliers, élément par élément,
à tout un ensemble d’individus par le biais de l’onglet partage. Les groupes d’utilisateurs sont gérés par
PlonePAS à travers le configlet Utilisateurs et groupes. Un utilisateur peut appartenir à autant de groupes
que nécessaire, voire aucun groupe. Un groupe peut appartenir à un autre groupe (imbrication).
i18n Abréviation commune du terme “internationalisation” (il y a 18 lettres entre le “i” et le “n” de “internatonalisation”). Opération consistant à marquer le code (Python, templates, ...) pour lui permettre de fournir les
textes de l’interface utilisateur en différents langages. Voir l10n.
identifiant Un identifiant Python valide est une suite non vide de caractères, de longueur quelconque, formée
d’un caractère de début et de zéro ou plusieurs caractères de continuation.
int Classe de base Python pour les objets représentant un nombre entier.
interface (en Python) : Une interface est une classe spéciale décrivant l’API publique de toute autre classe
l’implémentant. L’utilisation des interfaces est à la base de la ZCA.
interface marqueur une interface vide (sans attribut ni méthode) permettant de placer un tag sur un objet. À
noter qu’une interface est associée à un classe alors qu’une interface marqueur est attribuée objet par objet.
invite de commandes Logiciel vous invitant à contrôler votre système en fournissant des commandes et en lisant
le compte-rendu des dites commandes. Jusque dans les années 80, l’invite de commandes était l’unique
moyen de contrôler un ordinateur. Sous Windows, l’invite de commandes est fournie par le logiciel cmd.exe‘
alors que sous Unix - le choix est plus vaste - le couple xterm et bash est généralement utilisé.
l10n Abréviation commune de “localisation” (il y a 10 lettres entre le “l” et le “n” de “localisation”). Opération
consistant à fournir les textes de l’interface utilisateur d’un logiciel (d’un composant Plone) dans un langage
particulier. Voir i18n.
286
Chapitre 22. Glossaire
Plone pour les développeurs, Version 1.0.0
msi installer Format de bundle d’installation du monde Windows. Les fichiers de ce type ont l’extension .msi.
multiplugin Un plugin ayant l’aptitude de remplir plusieurs rôles, donc, généralement implémentant plus d’une
interface. Un plugin pour PAS est d’ailleurs généralement un multiplugin.
part Une part est une section d’un fichier buildout contenant une variable recipe définissant le rôle de la part
dans le processus d’installation global défini par la configuration buildout. Une part n’est exécutée que si
elle est référencée dans la liste parts = ... de la section [buildout].
parts voir part.
permission Protection d’accès à un objet dont la sécurité est gérée par Zope.
permissions voir permission.
PAS Abréviation de PluggableAuthService, la base du service de gestion des utilisateurs, rôles et groupes de
Plone. Voir la documentation intégrateur.
paster Couteau suisse du développeur d’applications Python. Voir http ://pythonpaste.org/index.html
PATH Variable d’environnement utilisée par votre système d’exploitation pour trouver les logiciels invoqués
par l’invite de commandes. Cette variable d’environnement est constituée d’une liste de répertoires, séparés
par : pour Unix et ; sous Windows.
plugin Se dit d’un composant logiciel (fonction, classe, ou objet) s’intégrant dans une infrastructure existante et
ayant une interface et un rôle pré-défini vis-à-vis de cette infrastructure (le rôle est généralement défini par
une interface), mais implémentant le rôle d’une façon qui lui est propre. Les logiciels de type “plugin” sont
légion dans le monde de Plone, et plus notamment dans le service de gestion des utilisateurs PAS. Voir la
documentation intégrateur.
PMI Plone Management Interface : Ensemble des formulaires fournis par Plone et ses éventuelles extensions
permettant de gérer et paramétrer le contenu et les services.
portlet Petite boite d’interface utilisateur située dans la colonne gauche d’un portail Plone. Les conditions d’apparition et les interfaces des portlets est automatiquement calculé en fonction du contexte (droits de l’utilisateur, et emplacement de navigation). L’outil de navigation et le calendrier sont des exemples de portlets.
Dans la grande majorité des cas, les portlets ne font pas strictement partie du contenu.
policy product Un policy product est un composant pour Plone défininissant la politique éditoriale du site,
et - éventuellement - sa logique métier spécifique. un policy product a également pour rôle d’installer et
paramétrer de façon spécifique Plone et les différents composants de tierce partie ajoutés.
principal Agent du système Zope exécutant une action. Un agent dispose de permissions sur les objets Zope pour
exécuter ces actions. Dans les applications Zope ou Plone, vous ne verrez que deux types de principals :
les utilisateurs (ou membres) et les groupes.
product Package Python fournissant des fonctionnalités supplémentaires à Zope ou Plone. Les produits sont
installés le plus souvent dans $INSTANCE_HOME/Products, et plus généralement dans les répertoires
du système de fichiers désignés par la directive “products” du fichier de configuration de votre instance
Zope. L’extension de Zope et Plone par produits est un héritage des anciennes versions de Zope, facilitant
ainsi l’adaptation d’extensions Zope ou Plone conçues pour des versions antérieures. Il est en conséquence
recommandé de réaliser dorénavant des composants (voir plus haut) pour ajouter des fonctionnalités à Zope
ou Plone.
produit Traduction libre de product.
profil Traduction libre de profile.
profile (GenericSetup) : un profil désigne un lot de fichiers XML définissant le paramétrage appliqué à un site
Plone permettant l’utilisation dans le dit site des ressources d’un composant ou d’un produit. Les fichiers
d’un profil GenericSetup sont – généralement – placés dans le sous-répertoire profiles/default d’un
composant ou produit.
propertysheet En français : feuille de propriétés. Dans Plone, il y a deux types de feuilles de propriétés :
– Les feuilles de propriétés servant à conserver des propriétés globales du site, celles-ci se trouvant dans le
tool portal_properties
– Les feuilles de propriétés dynamiques associées aux utilisateurs et aux groupes.
pypi
PYthon Packages Index, anciennement appellé cheeseshop, est le site dans lequel la grande majorité des packages open source Python sont mis à la disposition du public. Sa page d’accueil est
http ://pypi.python.org/pypi
287
Plone pour les développeurs, Version 1.0.0
pywin32 Package Python pour Windows - disponible pour les architectures 32 et 64 bits - fournissant l’accès
aux services système spécifiques à ce système d’exploitation : contrôle des services, de la base de registres,
accès COM ou DCOM aux applications, accès à Exchange et Active Directory, extensions et filtres ISAPI,
etc, etc.
recipe Peut être traduit en français par “recette”. Une recipe définit le rôle d’une part d’une configuration buildout
(exemples : installation/paramétrage d’Apache, instalation d’un client ZEO, fichier de comfiguration défini
par une template, ...). La plupart des recipes disponibles sont disponible dans le site Pypi à cette adresse.
rôle Un rôle regroupe un ensemble de permissions. Tout visiteur dispose d’un ou plusieurs rôles, soit de façon
globale, soit de façon contextuelle.
schéma zope.schema : Classe héritant de zope.interface.Interface, donc une interface dont les attributs sont définis par des objets exposés par le module zope.schema. Ces schémas sont généralement
utilisés pour modéliser des formulaires (de type zope.formlib ou z3c.form) .
Archetypes : Liste de champs représentant les données unitaires d’un type de contenu construit avec les
ressources de Products.Archetypes.
SSO Single Sign On ou Authentification unique. Agent permettant de n’effectuer qu’une seule authentification
pour accéder à plusieurs sites. Comme par exemple pour Google apps (mail, agendas, documents). Voir
http ://fr.wikipedia.org/wiki/SSO
structural folder Type de contenu permettant à un contributeur d’y inclure les contenus de son choix. A contrario, le “non structural folder” permet pas à un contributeur de structurer librement son contenu. Les composants de tierce partie Products.PloneArticle et Products.Collage fournissent des exemples
de “non structural folder”.
TAL Template Attribute Language
TALES Template Attribute Language Expression Syntax
tool Dans un contexte CMF, donc Plone, un “tool” est un objet unique dans un site fournissant un ensemble de
services et incorporant ses propres données persistantes de configuration. Les tools se trouvent à la racine de
Plone et leurs noms sont généralement préfixés par portal_. portal_properties abrite les feuilles
de propriétés de Plone et de la plupart des composants d’extension de tierce partie. portal_catalog
indexe les contenus du site...
TTW Ou “Through The Web”. Qualifie généralement le paramétrage ou la programmation d’une application
Zope depuis des formulaires accessibles uniquemant à un administrateur ou un auteur authentifié. La ZMI
est le principal outil Zope de programmation ou de paramétrage TTW.
type de contenu Traduction libre de content type.
unified installer Bundle d’installation complète de Plone, incluant Python, Zope, Plone ainsi que tous les modules requis (PIL, elementtree, ...). L’unified installer est disponible pour Windows, Mac OSX et Linux.
utilitaire Traduction française de utility.
utility Les utilitaires sont simplement des objets qui fournissent une interface et qui sont récupérés à partir
de cette interface et d’un nom. Cette approche permet de créer un registre global dans lequel des instances
peuvent être inscrites et récupérées à différents endroits de votre application, sans avoir besoin de transmettre
les instances comme paramètres. Les utilitaires sont détaillées dans Le guide complet de l’Architecture de
Composants de Zope.
variable Une variable est un identifiant associé à une valeur. Informatiquement, c’est une référence d’objet
situé à une adresse mémoire.
variables Voir variable.
viewlet Une viewlet est responsable du rendu d’un composant de page, donc de la réalisation d’un bloc - generalement - HTML. Les viewlets sont généralement assemblées par un viewlet manager.
viewlet manager Un viewlet manager permet d’assembler les viewlets. L’utilisation de viewlets et de viewlet
managers permet une disposition plus souple des composants d’une page que les classiques macros ZPT
ainsi que des performances de publication améliorées.
virtualenv
virtualenv est un outil permettant de créer des installations virtuelles de Python (presque) indépendantes du Python installé de façon globale dans votre système, de sorte que tout egg ajouté dans une
installation virtuelle ne vient pas “polluer” votre installation globale. Par extension, on appelle un “virtualenv” tout environnement virtuel créé avec cet outil. Reportez-vous à la documentation intégrateur
pour plus de détails. Ou bien sur la page d’accueil de virtualenv
288
Chapitre 22. Glossaire
Plone pour les développeurs, Version 1.0.0
widget Élément unitaire d’une page permettant au serveur de solliciter une information à l’utilisateur. Les widgets sont généralement assemblés dans un formulaire. Plone fournit bien évidemment tous les widgets standard du langage HTML (cases à cocher, boutons, listes déroulantes, champ de saisie de texte, ...). Ses divers
composants, notamment la bibliothèque zope.formlib, KSS et Archetypes proposent des widgets plus
évolués tel que des calendriers, éditeur WYSIWYG, arbres d’exploration, ...
workflow Outil permettant de définir les différents états d’un élément, et les transitions permettant de passer
d’un état à un autre. Pour chaque état, un sous-ensemble de permissions de cet élément fixe la limite les
différents types d’accès à celui-ci. Par exemple pour interdire la vue à un utilisateur anonyme. L’exécution
de chaque transitions est protégée de façon spécifique. Par exemple, seul un modérateur peut exécuter la
transition “publier” Dans Pone, les workflows sont définis en ZMI à l’aide de l’outil DCWorkflow. Certains
types d’éléments, comme l’image, ne sont pas associés à un workflow.
ZCA Zope Component Architecture : Ensemble de services intégrés dans Zope 3, et adaptés à Zope 2 depuis
sa version 2.9 par l’entremise de Five. La ZCA fournit les outils facilitant la collaboration entre objets et
services de natures différentes concourant à la réalisation de fonctions applicatives, tout en évitant les pièges
d’un héritage complexe de classes. Par exemple, vous disposez d’un composant (A) d’exploration AJAX
d’arbre virtuel, d’un autre composant (B) gérant un contenu pouvant être représenté de façon arborescente,
il vous suffira de fournir les adapteurs permettant d’explorer le contenu des objets fournis par B à l’aide
des widgets fournis par A. Ceci ne peut bien entendu être possible que si ces deux composants exposent
les ressources ZCA (généralement des interfaces) permettant ceci. L’utilisation de la ZCA, entamée par
Plone depuis sa version 2.5, se généralise depuis sa version 3. Plus de détails dans Le guide complet de
l’Architecture de Composants de Zope.
zc.buildout voir buildout.
ZCML Zope Configuration Markup Language : Langage basé sur XML permettant de configurer des composants
ou produits pour Zope ou Plone dans des fichiers configure.zcml ou overrides.zcml.
zexp Format binaire d’exportation et d’importation d’un objet (et éventuel sous-objets) de la ZODB. Pour exporter ou importer un fichier au format zexp, cliquez le bouton Import/Export et suivez les instructions.
ZODB Zope Object Data Base : base de données objets native de Zope.
ZMI Zope Management Interface : Interface Web de paramétrage et contrôle d’une instance Zope, permettant
l’exploration de la ZODB.
ZPT Zope Page Templates : Langage de templates pour produire - généralement - du texte XML ou HTML. Les
templates ZPT utilisent les attributs spéciaux préfixés par tal, metal et i18n qui définissent les règles
de transformation.
289
Plone pour les développeurs, Version 1.0.0
290
Chapitre 22. Glossaire
CHAPITRE 23
Indexes et tables
– genindex
– search
291
Plone pour les développeurs, Version 1.0.0
292
Chapitre 23. Indexes et tables
CHAPITRE 24
Contributeurs
–
–
–
–
–
–
Vincent Fretin
Robert Cordeau
Gilles Lenfant
Thomas Desvenain
Michaël Launay
Christophe Combelles
293
Plone pour les développeurs, Version 1.0.0
294
Chapitre 24. Contributeurs
CHAPITRE 25
Evolutions planifiées de ce document
295
Plone pour les développeurs, Version 1.0.0
296
Chapitre 25. Evolutions planifiées de ce document
Index
Symbols
G
élément, 286
groupe, 286
A
H
acl_users, 257, 259
adaptateur, 100, 285
adapter, 285
affectation, 285
Algorithme, 14
AT, 285
Héritage, 64
I
bool, 285
brain, 285
buildout, 285
i18n, 286
i18ndude, 253
identifiant, 286
Instruction composée, 31
int, 286
interface, 286
interface marqueur, 286
invite de commandes, 286
C
L
C, 285
Chaîne de caractères, 23
Collection, 36
component, 285
composant, 285
Composition, 66
configlet, 285
content type, 285
l10n, 286
Les types binaires, 27
Liste en intension, 38
D
P
Dérivation, 67
distribute, 286
distutils, 286
dotted name, 286
Package, 59
part, 287
parts, 287
PAS, 287
paster, 287
PATH, 287
permission, 287
permissions, 287
PlonePAS, 257, 259
architecture, 257
plugin, 287
PlonePAS, 257, 259
PMI, 287
policy product, 287
B
E
egg, 286
eggs, 286
expression, 286
Expression rationnelle, 69
F
Fonction, 49
Fonction récursive, 69
M
Module, 54
msi installer, 287
multiplugin, 287
297
Plone pour les développeurs, Version 1.0.0
Polymorphisme, 64
portlet, 287
principal, 287
product, 287
produit, 287
profil, 287
profile, 287
Programme, 14
propertysheet, 287
pypi, 287
pywin32, 288
R
rôle, 288
recipe, 288
S
schéma, 288
Sequence, 36
set, 43
SSO, 288
structural folder, 288
T
Tableau associatif, 42
TAL, 288
TALES, 288
tool, 288
TTW, 288
Tuple, 39
type de contenu, 288
U
UML, 177
unified installer, 288
utilitaire, 288
utility, 288
V
variable, 288
variables, 288
viewlet, 288
viewlet manager, 288
virtualenv, 288
W
widget, 289
workflow, 289
Z
zc.buildout, 289
ZCA, 289
ZCML, 289
zexp, 289
ZMI, 289
ZODB, 289
ZPT, 289
298
Index
Téléchargement