pixelvide
 Tutorial Django 1.3
 Introduction a Ubuntu
 Tutorial Django 1.0
 Framework Python
 Django Apps
 Videos : Django & App Engine



Identifiant :
Mot de passe :

S 'inscrire
Mot de passe oublié ?
pixelvide
pixelvide
pixelvide
Accueil / fr / django / tutorial-django-1.0

Formation Django

Formation Django

Contact: contact@emencia.com
Auteur:Julien Fache
Date: 2008-10-27

Abstract

Document à usage personnel.

Résumant les étapes de la formation

Django du 21 au 24 Octobre au sein des locaux d'Emencia.

Sommaire

1   Jour 1

1.1   Introduction à Django

Django est un framework, il était au départ développé pour un environnement de revue de presse électronique appartenant à World Online. Puis en Juillet 2005 il a été abstrait et publié comme framework web libre. Cet héritage est apparent dans l'ensemble du paquetage : dans la documentation, dans l'interface d'admin, et dans la bibliothèque standard, par exemple les types de champs gérés.

Django inclut tout les composants nécessaires au développement de n'importe quelle application web de base :

  • Correspondance objet-relationnel pour la base de données
  • Un système de squelette séparant le code Python du code HTML

Django n'utilise pas de serveur d'application tierce partie comme CherryPy. Il fournit un script gestionnaire qui démarre un de développement simple. Il s'appuie cependant sur un serveur web avec mod_python ou FastCGI pour le déploiement en production.

1.1.1   Ses forces

  • Mapping relationnel objet, grande API, efficace, remarqué par Guido van Rosum.
  • Système de cache.
  • Backoffice généré.
  • Design des URL.
  • Système de templates simple, complet et rapide.
  • Modules de contributions étendant les fonctionnalités
  • Documentation.
  • Bonne communauté.
  • Facilité d'apprentissage.
  • Découpage intelligent des projets.
  • Internationalisation
  • Extensibilité
  • Rapidité de développement
  • Outils d'administration de bases de données
  • Compatibilité WSCGI
  • Philosophie DRY
  • Robuste

1.2   Philosophie de code

1.2.1   Vue d'ensemble

Couplage faible
Les différentes couches du framework ne devraient pas se connaître les unes les autres à moins que cela soit absolument nécessaire.
Moins de code
Les applications Django devraient utiliser aussi peu de code que possible; elles devraient rester agiles. Django devrait profiter pleinement des possibilités dynamiques de Python, tel que l'introspection.
Développement rapide
L'intérêt d'un framework du 21ème siècle est de rendre rapide les tâches fastidieuses du développement Web. Django devrait permettre un développement Web incroyablement rapide.
Ne vous répétez pas (Don't Repeat Yourself)
Chaque concept/portion de données distincts devrait résider en un, et un seul, endroit. La redondance est mal. La normalisation est bien.
Explicite est meilleur qu'implicite
Ceci, un principe au cœur de Python, veut dire que Django ne devrait pas faire de choses trop magiques. La magie ne devrait pas apparaître tant qu'il n'y a pas de raison valable pour cela. La magie vaut la peine d'être employée seulement si elle amène un grand confort, inaccessible d'une autre manière, et si elle n'est pas implémentée de manière à rendre confus les développeurs qui essayent d'apprendre à utiliser la fonctionnalité.
Cohérence
Le framework devrait être cohérent à tout niveau. La cohérence s'applique à tout élément depuis le bas niveau jusqu'au haut niveau.

1.2.2   Modèles

Inclure toute la logique appropriée
Les modèles devraient encapsuler chaque aspect d'un objet, selon le patron de conception Active Record de Martin Fowler. C'est pour cette raison que les options d'administration spécifiques au modèle sont incluses dans le modèle lui même; les données relatives au modèle devraient être stockées dans le modèle.

1.2.3   API de la base de données

Efficacité SQL
Elle devrait exécuter des requêtes SQL le moins souvent possible, et elle devrait optimiser les requêtes en interne.
Syntaxe succincte mais puissante
L'API de base de données devrait autoriser de riches et consistantes requêtes dans un minimum de syntaxe possible. Elle ne devrait pas s'appuyer sur l'import d'autres modules ou d'objets d'aide à la mise en forme. Les jointures devraient être réalisées automatiquement, en coulisse, lorsque nécessaire.
Une option pour exécuter une requête SQL facilement
L'API de base de données devrait réaliser qu'elle est un raccourci mais pas nécessairement un tout-en-un. Le framework devrait rendre facile l'écriture de SQL sur mesure -- des requêtes complètes, ou juste des clauses WHERE personnalisées en tant que paramètres pour les appels à l'API.

1.2.4   Conception des URLs

Couplage faible
Le système d'URL Django devrait permettre aux URLs provenant de la même application d'être différentes dans des contextes différents. Par exemple, un site peut mettre les articles à /articles/, alors qu'un autre peut utiliser /nouvelles/.
Flexibilité infinie
Les URLs devraient aussi flexibles que possible. Toute conception d'URL imaginable devrait être permise.
Encourager les bonnes pratiques
Le framework devrait faire en sorte qu'il soit facile (ou plus facile) pour un développeur de concevoir de jolies URLs que des mauvaises. Les extensions de fichier dans les URLs des pages Web devraient être évitées.
URLs permanentes
Techniquement, foo.com/bar et foo.com/bar/ sont deux URLs différentes, et les robots des moteurs de recherche (et quelques outils d'analyse du traffic) pourraient les traiter de manière séparée. Django devrait faire un effort pour "normaliser" les URLs afin ne pas permettre la confusion aux robots des moteurs de recherche.

1.2.5   Système de template

Séparer la logique de la présentation
Nous voyons un système de template comme un outil contrôlant la présentation et la logique relative à la présentation -- et c'est tout. Le système de template ne devrait pas supporter de fonctionnalités qui vont au delà de ces principes de base.
Décourager la redondance
L'héritage au sein des templates, permet de se passer de la duplication de certains éléments du contenu, tel le footer ou le header.
Etre dissocié du HTML
Le système de template ne devraient pas être conçu pour seulement produire du HTML. Il devrait être également bon à générer d'autres formats basés sur du texte, ou juste du texte.
Sûreté et sécurité
Le système de template, nativement, devrait interdire l'inclusion de code malicieux -- tel que des commandes supprimant les données en base.
Extensibilité
Le système de template devrait prendre en compte le fait que les auteurs de templates de niveau avancé peuvent vouloir étendre cette technologie. C'est le concept derrière les marqueurs et les filtres de template personnalisés.

1.2.6   Vues

Simplicité
L'écriture d'une vue devrait être aussi simple que d'écrire une fonction Python. Les développeurs ne devraient pas avoir besoin d'instancier une classe quand une fonction suffit.
Couplage faible
Une vue ne devrait pas se soucier du système de template que le développeur utilise -- ou même si un système de template est utilisé ou non.
Différencier entre GET et POST
GET et POST sont distincts; les développeurs devraient explicitement utiliser l'un ou l'autre. Le framework devrait facilement distinguer les données GET et POST.

1.3   Création d'un projet Django

1.3.1   Installation de Django

Pour cela il est nécessaire de télécharger le code source de Django. Nous allons nous baser sur la dernière version stable publiée, la 1.0

Dans un terminal exécutons ces commandes

$> wget http://www.djangoproject.com/download/1.0/tarball/
$> tar -xvzf Django-1.0.tar.gz
$> cd Django-1.0
$> sudo python setup.py install

1.3.2   Création du projet en local

Maintenant il est nécessaire de créer un répertoire qui stockera vos projets, comme cela les développements seront centralisés. Une fois dans ce répertoire, exécutez la commande suivante

$> django-admin.py startproject monprojet

Votre projet est désormais créé.

1.3.3   Qu'est ce qu'un projet Django

Au final qu'est ce qu'un projet Django ?

Si nous regardons le contenu du répertoire de notre projet, nous y retrouvons plusieurs choses :

__init__.py
Ce fichier permet de dire à l'interpréteur python que ce répertoire est un module python. C'est la norme.
manage.py
Script python reprenant les fonctionnalités de django-admin.py et qui servira aux tâches d'administration de notre projet
settings.py
Ce fichier va contenir toute la configuration de notre projet. Il sert de point d'entrée à notre projet. Ecrit en python, ce fichier de configuration est très souple à manipuler.
urls.py
Comme son nom l'indique, ce fichier contiendra les urls associées à notre projet. Il est le squelette du projet, faisant la liaison entre le code et les urls.

Au final nous pouvons donc dire qu'un projet Django est un module python permettant l'administration et le contrôle des différents composants intervenant dans le projet. Composants appelés Applications

1.4   Création d'une application

Un projet étant constitué d'applications, Django fournit un utilitaire pour créer rapidement la structure d'une application au sein du projet, grâce à cette commande

$> python manage.py startapp monapplication

On remarque que nous utilisons désormais le script manage.py, mais cette action est aussi valable grâce au script django-admin.py.

1.4.1   Qu'est ce qu'une application

Une application est un simple module python. La meilleure preuve est de regarder le contenu du répertoire monapplication. Les fichiers suivants sont présents :

__init__.py
Ceci est le fichier qui prouve que le répertoire est un module python, car sa présence l'indique à l'interpréteur.
models.py
Module python contenant les classes python relatives aux models de l'application. Utiliser ce fichier pour écrire ses models est la norme.
views.py
Idem que pour models sauf qu'ici seront écrites les vues de l'application.

Une application Django est donc un simple module python définissant un ensemble de fonctions et de classes, qui seront utilisées au sein de notre projet.

1.4.2   Standalone versus Coupled applications

Il est important de savoir qu'il existe 2 manières de développer une application :

Coupled

Une application dite couplée est typiquement une application créée par la commande startapp au sein de notre projet. Elle va lier l'application avec notre projet, ce qui aura l'inconvénient de devoir spécifier précisément les chemins d'importation de l'application et d'en limiter grandement la réutilisabilité.

Dans le cas d'un développement spécifique, ceci ne pose pas de problème, mais il n'est recommandé de procéder ainsi.

Standalone

Une application Standalone est une application indépendante du projet, qui pourra résider n'importe où dans $PYTHON_PATH. Dans le cas d'une application réutilisable et/ou distribué, il est nécessaire de procéder ainsi.

Nous utiliserons cette méthode tout au long des travaux pratiques.

1.5   Création des modèles

1.5.1   Qu'est ce qu'un model ?

Un model est un objet dans Django servant à interagir avec une base de données. Il doit contenir toutes les propriétés et méthodes de notre objet représenté. Un model pouvant avoir des relations avec d'autres models, ces relations devront êtres aussi décrites.

Les models serviront de structure à notre application, et en définiront une bonne partie de la logique.

C'est pour cette raison qu'il est nécessaire de commencer nos applications par une conception solide de nos models.

1.5.2   Notre première application

Pour se baser sur un exemple concret, nous allons créer une application de répertoire téléphonique.

Dans votre répertoire de développement tapez les commandes suivantes

$> mkdir repertoire_telephonique
$> touch repertoire_telephonique/__init__.py
$> touch repertoire_telephonique/models.py

Etant donné que cette application sera standalone il est nécessaire qu'elle soit accessible grâce à la variable d'environnement $PYTHON_PATH.

Pour cela, vous pouvez créer par exemple votre application dans un répertoire indiqué par $PYTHON_PATH

$> echo $PYTHON_PATH

Vous pouvez aussi modifier votre variable d'environnement en rajoutant votre répertoire de développement au $PYTHON_PATH.

Ou bien créer un lien symbolique depuis un répertoire accessible vers votre module.

1.5.3   Nos premiers models

Nous devons maintenant construire nos models qui vont donc définir la logique de notre application. Notre application sera constituée de 2 models, l'un représentant un contact et un autre représentant ses numéros de téléphones.

Editons le fichier repertoire_telephonique/models.py pour qu'il ressemble à cela :

>>> from django.db import models
>>>
>>> class Contact(models.Model):
>>>
>>>   first_name = models.CharField(max_length=50)

>>>   last_name = models.CharField(max_length=50)
>>>
>>>   def __unicode__(self):
>>>     return '%s %s' % (self.first_name, self.last_name)
>>>
>>>  class Phone(models.Model)
>>>
>>>     PHONE_CHOICES = (('pro', 'Professionel'),
>>>                      ('mobile', 'Mobile'),
>>>                      ('fixe', 'Fixe'))

>>>
>>>     contact = models.ForeignKey(Contact, null=True, blank=True)
>>>     type = models.CharField(max_length=10, choices=PHONE_CHOICES)
>>>     value = models.CharField(max_length=20)
>>>
>>>   def __unicode__(self):
>>>     return '%s %s : %s' % (self.contact, self.type, self.value)
>>>

1.5.4   Installation des models

Maintenant que nous possédons, nos propres models, il est nécessaire de les installer.

Installer les models d'une application, revient à installer l'application. Pour cela, il faut éditer les ficher settings.py de votre projet, et y rajouter 'repertoire_telephonique', dans la section INSTALLED_APPS

Maintenant nous allons établir une synchronisation entre les models et notre base de données. Cette synchronisation servira à créer les tables SQL décrites par vos models.

$> python manage.py syncdb

1.5.5   Ce que nous voyons

Les models écrits vont servir d'interface avec la base de données. Les attributs de classe définis au sein du model serviront de colonnes et définiront le type. Ce sont les Fields

Il existe des Fields pour à peut prêt chaque utilisation, il est même possible de créer ses propres Fields.

1.5.6   Manipulations des models

Il possible désormais d'utiliser les models comme interface avec notre base de données.

$> python manage.py dbshell

>>> from repertoire_telephonique.models import Contact, Phone

>>> Ccntact.objects.get_or_create(first_name='Julien', last_name='Fantomas')
>>> Contact.objects.all().count()
... 1

1.6   Introduction aux urls

Le système d'url au sein de Django est une des possibilités les plus intéressantes de Django pour les raisons suivantes :

Motif
Les urls sont constituées de motif permettant la variation et l'écriture de n'importe quelles urls et le passage de paramètres de manière élégante.
Rapidité
Les urls sont compilées au démarrage du serveur pour un gain de temps.
Loose Coupling
Les urls sont décentralisées, et permettent l'association entre le motif de l'url et une vue.

Il est bon de savoir que les urls peuvent être nommées, pour un souci de clarté et aussi pour éviter d'avoir un couplage fort au sein de vos templates et vues. Nous verrons cela en détail plus tard.

1.6.1   Exemples

Voici un exemple simple de vue en liste et detaillée:

>>> urlpatterns = patterns('django.views.generic.list_detail',
>>>                        url(r'^$', 'object_list',
>>>                            name='my_object_list'),
>>>                        url(r'^(?P<object_id>\d+)/$', 'object_detail',
>>>                            name='my_object_detail'),
>>>                        )

Ce qui donnera à l'utilisation dans votre navigateur :

http://www.votredomaine.com/
http://www.votredomaine.com/42/

1.7   Activation de l'administration

Une des fonctionnalités les plus utiles au sein de Django, et qui a permis au framework de se diffuser rapidement est sans conteste l'application d'administration, qui est rapide et flexible pour une mise en place.

L'application d'administration est un module python fourni de base dans la distrbution de Django, se situant dans le répertoire contrib de notre installation de Django. Nous pouvons voir dans ce dossier d'autres applications fournies avec les sources de Django.

1.7.1   Administration simple

Une des premières choses à effectuer est d'activer le module d'administration. Pour cela dans votre fichier settings.py de votre projet ajouter 'django.contrib.admin' à l'intérieur de la section INSTALLED_APPS.

Pour continuer son installation il est nécessaire d'associer une url aux vues du module d'administration. Le fichier urls.py situé dans votre projet contient déjà un exemple d'urls pour accéder à l'administration, mais il est commenté. Décommentez les lignes suivantes, pour avoir ceci :

>>> from django.contrib import admin
>>> admin.autodiscover()

>>> (r'^admin/(.*)', admin.site.root),

Ensuite exécutez la commande suivante, pour synchroniser les models contenus dans l'application admin afin d'en assurer son fonctionnement.

$> python manage.py syncdb

Désormais, si on lance le serveur de développement contenu dans Django, par la présente commande,

$> python manage.py runserver

on peut accéder à l'interface d'administration grâce à cette url :

http://localhost:8000/admin

Une fois identifié vous pouvez voir une liste des différentes applications installées sur le serveur et qui sont administrables.

Par contre, nous ne voyons pas notre application au sein de l'administration, ce qui est plutôt dommage, donc nous allons remédier à cela.

1.7.2   Administration de nos applications

Pour activer nos applications aux seins de l'administration c'est très simple, il suffit d'enregistrer nos models dans le module d'administration.

Pour cela nous allons créer un module admin.py au sein de notre application de répertoire téléphonique.

$> cd repertoire_telephonique/
$> touch admin.py

Editons le pour qu'il ressemble à cela :

>>> from django.contrib import admin
>>>
>>> from repertoire_telephonique.models import Contact
>>> from repertoire_telephonique.models import Phone
>>>
>>> admin.site.register(Contact)
>>> admin.site.register(Phone)

Désormais si on relance le serveur de développement, et que l'on recharge la page d'administration, nous y voyons apparaitre notre application avec ses tables prêtes à être administrées.

Le résultat est plus basique, mais il est possible d'aller plus loin dans la configuration de l'administration de nos models.

1.7.3   Administration évoluée

Pour atteindre un résultat où les vues d'administration soient efficaces et faciles à administrer, il est souvent nécessaire de les configurer. Il nous faut créer différentes classes définissant le comportement de chacun de nos models dans l'administration. Ajoutons ce code dans notre fichier admin.py.

>>> class ContactAdmin(admin.ModelAdmin):
>>>   list_display = ('first_name', 'last_name')
>>>

>>> admin.site.register(Contact, ContactAdmin)
>>>
>>> class PhoneAdmin(admin.ModelAdmin):
>>>   list_display = ('contact', 'type', 'value')
>>>
>>> admin.site.register(Phone, PhoneAdmin)

Nous avons juste modifié l'affichage de la vue en liste, mais il parait déjà plus efficace.

Désormais si on regarde la documentation du module d'administration nous voyons qu'il existe pleins de possibilités d'administration. Essayez un peu.

2   Jour 2

Pour illustrer la suite de mes propos, nous allons avoir besoin de créer une application plus conséquente, car le répertoire téléphonique est déjà complet grâce au module d'administration qui remplit toutes les fonctionnalités souhaitées d'un répertoire téléphonique.

Nous allons donc créer un mini moteur de publication de nouvelles sur le site en frontal. Cette application portera le nom de npublisher.

Nous nous imposerons cette liste de fonctionnalités :

  • gestion des catégories
  • gestion des auteurs
  • gestion de la publication, visible ou non
  • facilité de publication et de référencement

Le reste des données du model est libre.

Créons donc les models, et activons l'interface d'administration.

Une fois que nous avons une interface d'admin efficace, nous allons nous attaquer à la vue en frontal.

2.1   Introduction aux vues

Une vue dans django, est une fonction, ou bien une méthode si on aime se compliquer la vie, qui recevra en premier argument, un objet représentant la requête HTTP envoyée par le client. Une vue peut recevoir plusieurs paramètres, pour en assurer la réutilisabilité.

Une vue doit toujours retourner une réponse de type HTTP.

Les vues représentent par exemple une vue en liste d'un model, ou sa vue détaillée, elle peut aussi très bien représenter le résultat d'une recherche, ou des formulaires d'éditions, des fichiers à télécharger, tout ce qui permet par le protocole HTTP.

2.2   Utiliser les vue génériques

Ecrire des vues avec l'habitude devient une tâche longue et répétitive, par exemple, il est souvent nécessaire de représenter des vues en liste puis une vue détaillée.

Heureusement Django peut palier à ce problème grâce à un système de vues génériques incorporé dans sa distribution.

Ses vues permettent dans la majorité des cas de ne pas taper de code python, avantage certain.

2.2.1   Configuration des urls

Pour utiliser les vues génériques, tout va se passer aux niveaux des urls. Dans notre répertoire de notre application npublisher nous allons créer un fichier nommé urls.py contenant ce code :

>>> """Urls of npublisher"""
>>>
>>> from django.conf.urls.defaults import *
>>>
>>> from npublisher.models import Entry
>>>
>>> entry_conf = {'queryset': Entry.objects.all()}

>>>
>>> urlpatterns = patterns('django.views.generic.list_detail',
>>>                        url(r'^$', 'object_list',
>>>                            entry_conf, 'npublisher_entry_list'),
>>>                        url(r'^(?P<slug>[-\w]+)/$', 'object_detail',
>>>                            entry_conf, 'npublisher_entry_detail'),
>>>                        )

A partir de là plusieurs choses sont à noter :

  • Ce fichier d'urls peut être inclus dans le fichier d'urls de votre projet, grâce à la fonction include, par exemple :

    >>> (r'^informations/', include('npublisher.urls')),
    
  • Désormais grâce au nommage des urls, il est possible d'avoir un couplage faible au niveau du models, tout en lui définissant une url absolue, utile elle aussi pour la notion de couplage faible. Seul le fichier urls.py de votre projet se maitre de chaque url utilisée dans votre projet, grâce à un mécanisme d'inversement. Utilisons cela sur notre model Entry et ajoutons cette méthode :

    >>> class Entry(models.Model):
    >>>  ...
    >>> @models.permalink
    
    >>> def get_absolute_url(self):
    >>>   return ('npublisher_entry_detail', (), {'slug': self.slug})
    
  • Un seul fichier, peu de code, des urls réutilisables.

  • queryset est le seul argument obligatoire pour ces vues, il

    définit de manière figée sur quelles instances du model il peut regarder.

2.3   Les templates

Actuellement nous avons le code, c'est à dire la logique de l'application, mais pas le rendu. Si nous testons les urls que nous avons créées, elles retournent une erreur spécifiant que le template npublisher/entry_list.html n'existe pas.

Le nom du template recherché est défini par le comportement des vues génériques, qui vont prendre le nom de l'application suivi d'un '/' puis du nom du model utilisé pour finir _detail.html ou _list.html en fonction de la vue appelée.

Nous allons donc créer les templates qui manquent. Au sein de notre application, nous allons créer un dossier nommé templates, puis créer un autre dossier nommé lui npublisher. Une fois dans le répertoire npublisher du dossier templates, on va créer les templates manquant.

$> cd npublisher
$> mkdir -p templates/npublisher
$> cd templates/npublisher
$> touch entry_list.html
$> touch entry_detail.html

Si nous actualisons notre navigateur, l'erreur n'apparait plus, mais rien n'apparait, car il n’y a rien dans les fichiers templates.

2.3.1   Remarques

  • Django possède plusieurs systèmes d'acquisition de templates, le premier étant celui qui va regarder dans la liste de dossiers définis dans la section TEMPLATE_DIRS de votre fichier settings.py. Un autre moyen est de vérifier dans le dossier de vos applications installées si un dossier templates existe pour l'inclure dans les recherches. C'est ici la technique utilisée.
  • Grâce à ces différentes techniques d'acquisition, il est possible de modifier le rendu d'une application par défaut, en dupliquant le template visé pour le modifier et le mettre dans un chemin d'acquisition prioritaire.
  • Il est donc aisé de fournir un jeu de templates à une application réutilisable, pour ensuite les remplacer en fonction des besoins de personnalisation.

2.3.2   Philosophie des templates

Un template, sert juste à mettre en forme des données. C'est pourquoi le système de template est tout aussi bien capable de produire du HTML, du CSV ou bien encore du PDF.

Les templates ne doivent pas incorporer trop de logique, sauf celle de la présentation, le reste doit être délégué aux vues ou aux models en eux mêmes.

Maintenant réalisons nos templates.

2.3.3   Centralisation et héritage dans les templates

Le système de template dans Django est assez puissant pour permettre l'héritage entre templates. Cette fonctionnalité évite la redondance de certains éléments communs à différents templates.

Dans le cas d'un site internet, ces éléments, seront les métas de publication, la navigation, le footer. Inutile de copier coller, ou d'inclure, il suffit d'hériter.

2.3.4   Technique à 3 niveaux

Il est possible de faire à peut prêt ce que l'on veut, avec le système de template, mais une technique robuste et souple au niveau de la personnalisation est retenue. Celle de la technique à 3 niveaux.

Dans la section TEMPLATE_DIRS du fichier settings.py de votre projet, il faut spécifier au moins un dossier. Il est d'usage de faire un dossier templates au sein de votre projet, et de spécifier ce dernier.

Dans ce dossier nous allons créer un fichier nommé base.html, celui ci contiendra le design html de notre charte graphique.

Autours des zones qui seront dynamiques, nous allons établir des blocks. Ces blocks sont souvent les mêmes :

  • la zone de css
  • la zone de javascript
  • la balise title
  • la zone de contenu
  • la navigation

Exemple

<title>Mon site {% block title%}{% endblock %}</title>

Ensuite nous allons créer dans le dossier templates/npublisher de notre application un fichier nommé aussi base.html ce fichier héritera de notre fichier base.html créer précédemment. Ensuite il pourra surcharger les blocks hérités de son parent.

{% extends "base.html" %}

{% block title %}NPublisher{% endblock %}

Désormais dans chacun des templates de notre application, nous hériterons de application/base.html qui nous fournira une structure de template déjà configurée. Par exemple dans entry_list.html :

{% extends "npublisher/base.html" %}

{% block title %}{{ block.super }} Entrées{% endblock %}

Il suffit après de définir la zone de contenu à afficher dans le block représentant le contenu dans le template principal.

Si une modification doit être apportée sur tout le module au niveau du design, il suffit juste de modifier le fichier base.html de l'application.

2.4   Les vues génériques wrappées

Il peut arriver des cas, où les vues génériques ne répondent pas toute à fait à notre utilisation. Par exemple si je veux faire une vue filtrant les entrées par catégorie, je vais toujours retourner des entrées, mais la queryset ne sera pas bonne car la notion de filtre ne sera pas appliquée.

Dans ce cas il est possible d'écrire une vue générique wrappée. Nous allons donc créer un fichier views.py dans le répertoire de notre application. Il devrait ressembler à cela :

>>> """Views of npublisher"""

>>> from django.shortcuts import get_object_or_404
>>> from django.shortcuts import render_to_response
>>> from django.views.generic.list_detail import object_list
>>>
>>> from npublisher.models import Category
>>>
>>> def category_view_entry(request, slug):
>>>     """Return the view for entry of a category"""
>>>     category = get_object_or_404(Category, slug=slug)

>>>     return object_list(request, queryset=category.entry_set.all(),
>>>                        extra_context={'slug': slug})

Cette vue sélectionne la catégorie dont le nom est passé dans l'url, et ensuite nous retournons une vue générique correctement formatée avec la queryset correspondante, c'est à dire les entrées associées à la catégorie.

Utiliser le système de vue générique permet de s'abstenir de beaucoup de codes redondant, et permet de rester constant au sein de notre application.

Cette vue doit maintenant être associée à une url, donc dans notre fichier urls.py de l'application npublisher nous rajoutons ceci :

>>> urlpatterns += patterns('',

>>>                        url(r'^category/(?P<slug>[-\w]+)/$', 'npublisher.views.category_view_entry',
>>>                            name='npublisher_category_view_entry'),
>>>                        )

Comme nous le voyons il est possible d'additionner des urls.

2.5   Création de managers

Maintenant que nous avons nos vues, nous remarquons un problème, même les vues marquées du tag visible à False sont affichées dans la vue en liste.

Une solution serait de modifier la queryset passée en paramètre dans les vues génériques, mais cela ne serait pas très pratique dans l'utilisation, car cela fait trainer du code qui devrait faire parti de la logique de l'objet.

L'autre solution serait de modifier le manager de notre models Entry. Chaque models possède un manager par défaut, il se nomme objects, mais il possible d'en ajouter d'autres pour répondre à différents besoins.

Nous allons donc créer un manager pour notre model Entry, qui permettra de retrouver rapidement les publiés et seulement celle la. Dans notre fichier models, il faut rajouter cette classe, pour que cela fonctionne.

>>> class EntryPublishedManager(models.Manager):
>>>     """Manager to retrieve published entries"""
>>>

>>>     def get_query_set(self):
>>>         return super(EntryPublishedManager, self).get_query_set().filter(visible=True)

Cette classe hérite de models.Manager, et surcharge le comportement de la méthode get_query_set qui définit le moyen dont un model retrouve ses données.

Dans notre classe Entry il faut désormais rajouter ces lignes :

>>> objects = models.Manager()

>>> published = EntryPublishedManager()

Et désormais dans le fichier urls.py de notre application, il faut changer le dictionnaire entry_conf par ceci :

>>> entry_conf = {'queryset': Entry.published.all()}

Ce qui permettra d'afficher que les entrées publiées.

2.5.1   Remarque

Nous voyons que nous avons rajouté à la première ligne, l'attribut objects. Ce n'est pas obligatoire, mais il est nécessaire de l'utiliser pour assurer une compatibilité avec les autres modules.

Pourquoi le déclarer alors ?
Car dans le fonctionnement des models, la première classe héritée de models.Manager, va devenir le manager par défaut de notre models. C'est une feature à prendre en compte lors de la conception de manager.

2.6   Créer ses propres tags

Il est parfois nécessaire de créer ses propres tags pour une utilisation au sein des templates. Par exemple, il serait utile sur la page d'accueil du site d'afficher les 5 dernières informations postées. Puis sur une partie de la page, les catégories disponibles.

Cette logique dépend dans ce cas du template, et non de la vue, qui elle dans notre cas, ne devrait que retourner un template.

Comment procéder à cela ? Il suffit de créer un tag utilisable au sein de nos templates.

Dans notre application nous allons créer un module python appelé templatetags et qui va contenir nos tags. C'est Django qui s'occupe de charger en mémoire nos tags lors du démarrage du serveur en regardant le répertoire templatetags de chaque application installée.

$> cd npublisher/
$> mkdir templatetags
$> cd templatetags/
$> touch __init__.py
$> touch getter.py

getter.py contiendra le tag qui nous permettra de récupérer n'importe quel objet au sein de nos templates.

>>> from django.template import Library, Node
>>> from django.db.models import get_model
>>>
>>> register = Library()
>>>
>>> class LatestContentNode(Node):
>>>   def __init__(self, model, num, varname):
>>>     self.num, self.varname = int(num), varname

>>>     self.model = get_model(*model.split('.'))
>>>
>>>   def render(self, context):
>>>     if self.num == -1:
>>>         context[self.varname] = self.model._default_manager.all()
>>>     else:
>>>         context[self.varname] = self.model._default_manager.all()[:self.num]
>>>     return ''
>>>
>>> def get_latest(parser, token):

>>>   bits = token.contents.split()
>>>   if len(bits) != 5:
>>>       raise TemplateSyntaxError, "get_latest tag takes exactly four arguments"
>>>   if bits[3] != 'as':
>>>       raise TemplateSyntaxError, "third argument to get_latest tag must be 'as'"
>>>   return LatestContentNode(bits[1], bits[2], bits[4])
>>>

>>> get_latest = register.tag(get_latest)

Ce tag va charger dans le contexte du template, les derniers objets de n'importe quelle application. Voici un exemple de code pour un fichier template.

{% load getter %}

{% get_latest npublisher.entry 5 as entry_list %}
{% for entry in entry_list %}
  <a href="{{ entry.get_absolute_url }}">{{ entry }}</a>
{% endfor %}

2.7   Créer ses propres contextes

Parfois, il est nécessaire de créer ses propres contextes, dans le cas où des informations sont redondantes à travers le site. Par exemple, un fil d'ariane.

Nous allons donc voir comment utiliser nos propres contextes, appelés context template processor.

Nous allons donc dans notre application créer un fichier context_processors.py contenant le code suivant :

>>> def breadcrumbs(request):
>>>   """Build a bread crumbs"""
>>>   url = '/'
>>>   navigation = request.path.split('/')

>>>   breadcrumbs = '<a href="/" title="Home">Home</a> '
>>>
>>>   for path_ in navigation:
>>>     if path_ and not path_.isdigit():
>>>       url += '%s/' % path_

>>>       breadcrumbs += '&gt; <a href="%s" title="%s">%s</a> ' % (url, path_.capitalize(),
>>>                                                                path_.capitalize())
>>>   return {'breadcrumbs_list': navigation,
>>>           'breadcrumbs_html': breadcrumbs}

Maintenant dans le fichier settings.py de notre application, il est nécessaire d'ajouter cette section, car elle surcharge la configuration par défaut et doit donc la rappeler :

>>> TEMPLATE_CONTEXT_PROCESSORS = (
>>>     'django.core.context_processors.auth',
>>>     'django.core.context_processors.debug',
>>>     'django.core.context_processors.i18n',
>>>     'django.core.context_processors.media',
>>>     'npublisher.context_processors.breadcrumbs',
>>> )

Désormais dans notre template base.html nous pouvons utiliser ce code par exemple:

<span class="breadcrumbs">{{ breadcrumbs_html|safe }}</span>

Les templates context processors sont utiles dès que des informations ont besoin d'êtres affichées de manière redondante et en fonction de la requête HTTP.

3   Jour 3

3.1   Internationalisation de votre application

Internationaliser son application est une problèmatique que l'on retrouve souvent lors de la conception d'un projet. Il est nécessaire de savoir quelles parties d'une application devront être internationalisées, et dans quelles langues.

Ceci peut rapidement être fastidieux, mais heureusement Django fournit tous les outils pour rendre cette tâche plus aisée.

Un des autres avantages principaux de l'internationalisation, est d'éviter de coder des fichiers python avec des caractères accentués, par exemple au niveau des models, car cela peut devenir vite un problème lors de la publication des données.

3.1.1   Internationalisation du code

Internationnaliser son code est souvent la première étape lors de l'internationalisation de votre application.

Il vous suffit de marquer chaque chaine qui devra être traduite, lors de l'écriture de votre code. En fonction du contexte la méthode a employé peut varier. Dans les models nous utiliserons des traductions à la volée, alors que dans les vues, les traductions peuvent être faites au lancement du serveur.

Prenons un exemple d'un model avant internationalisation :

>>> class News(models.Model):
>>>   reference = models.CharField(max_length=30)
>>>   categories = models.ForeignKey(Category)

Et maintenant après internationalisation :


>>> from django.utils.translation import ugettext, ugettext_lazy as _
>>>
>>> class News(models.Model):
>>>   reference = models.CharField(_('reference'), max_length=30)
>>>   categories = models.ForeignKey(Category, verbose_name=_('categories'))
>>>
>>>   class Meta:
>>>     verbose_name = _('news')

Il est important de se dire, qu'à cette étape nous marquons juste les strings devant être traduites.

3.1.2   Internationalisation des templates

Internationaliser le code n'est souvent que la première étape, car vos templates devront être aussi internationalisées.

Pour cela, il va falloir charger la machine de traduction à l'intérieur des templates, et comme pour l'étape précèdente, marquer les strings qui seront traduites.

{% load i18n %}
<h1>{% trans "news" %}</h1>

<p>{% trans "reference" %} : {{ object.reference }}</p>

3.1.3   Internationalisation du contenu en base de données

Parfois, il arrive que le contenu de la base de données ait besoin d'être lui même internationalisé. Prenons par exemple le cas de catégories insérées en base de données. Les catégories elles aussi ont besoin d'être traduites, mais Django ne fournit pas en standard un moyen de modéliser ce besoin en base de données.

Mais la communauté veille, et plusieurs applications existent pour répondre à ce besoin. Par exemple Multilingual

3.1.4   Détection de la langue

Django est bien pensé, le système d'acquisition de la langue de l'utilisateur est plutôt efficace. Par défaut il va vérifier s'il existe une valeur en session définissant la langue de l'utilisateur, le cas échéant, il va vérifier la présence d'un cookie qui définit les préférences de langue de l'utilisateur.

Si ces étapes ne retournent rien, il va regarder s'il existe une traduction pour la valeur Accept-Language de votre navigateur.

Arrivé jusque là, si il n'y a toujours pas de traduction, Django va afficher la traduction définie par la valeur LANGUAGE_CODE de votre fichier settings.py.

Ce système permet de toujours trouver une traduction, laissant le choix aux préférences de l'utilisateur.

3.1.5   Sélection de la langue

Il est facile de proposer à l'utilisateur le choix de sa langue. Le tout se fait par un formulaire HTML, et l'association d'une url.

Pour l'url :

>>>  (r'^i18n/', include('django.conf.urls.i18n')),

Pour le formulaire voici le code HTML :

<form action="/i18n/setlang/" method="post">
  <input name="next" type="hidden" value="/next/page/" />

  <select name="language">
  {% for lang in LANGUAGES %}
    <option value="{{ la
        ng.0 }}">{{ lang.1 }}</option>
  {% endfor %}
  </select>
  <input type="submit" value="Change" />

</form>

L'appel de cette vue va créer un cookie spécifiant la préférence de l'utilisateur et le rediriger vers la vue précèdente si la valeur next n'est pas présente.

3.1.6   Rédaction des traductions

Désormais nous avons tout le système mis en place pour avoir un site internationnalisé, mais il manque le plus important, les traductions.

Pour cela, nous allons créer un dossier locale soit au niveau de notre projet, soit de notre application, en fonction du besoin.

$> mkdir locale

Ensuite nous allons exécuter une commande qui va collecter tous les élements à traduire pour en faire un fichier PO, qui est la norme pour tout ce qui touche a l'internationalisation.

$> django-admin makemessages -l fr

Cette commande va créer les répertoires et le fichier PO qui contiendra la traduction française de notre projet ou application.

Si nous allons dans le dossier locale/fr/LC_MESSAGES, là se trouve notre fichier de traduction.

Nous devons l'éditer pour fournir les traductions, un simple éditeur de texte, peut faire l'affaire. Mais je conseille par exemple GTranslator qui fournit une interface efficace pour rapidement construire son fichier de traduction.

Maintenant que nous avons notre fichier PO préparé, il va être nécessaire de le compiler pour pouvoir l'utiliser dans notre application.

$> django-admin compilemessages

Il est aussi envisageable de le faire à la main avec la commande :

$> msgfmt django.po -o django.mo

Désormais nous avons un site entièrement traduit.

3.2   Utilisation des formulaires

La génération de formulaires est une tâche fastidieuse, tant au niveau de l'écriture, qu'au niveau des contrôles. Heureusement Django contient un module très utile pour la génération et l'utilisation de formulaires au sein de pages webs.

3.2.1   Modélisation des formulaires

La première étape est de modéliser un formulaire de façon pythonique pour pouvoir ensuite s'en servir comme objet de manipulation. Créons un formulaire qui va servir d'enregistrement sur le site.

>>> from django import forms
>>>
>>> class RegisterForm(forms.Form):
>>>   login = forms.CharField(max_length=50)
>>>   password_1 = forms.CharField(max_length=50, widget=forms.PasswordInput)
>>>   password_2 = forms.CharField(max_length=50, widget=forms.PasswordInput)

Désormais nous pouvons utiliser ce formulaire dans nos vues.

3.2.2   Rendus HTML

Bien entendu nous pouvons utiliser notre classe de formulaire pour en générer le rendu HTML au sein de nos templates.

<form method="post" action=".">
  {{ form }}
  <input type="submit" value="Connection" />

</form>

Mais il est aussi possible d'écrire soit même son formulaire en HTML, pour pouvoir en modifier le rendu visuel. Libre à vous, mais il faudra penser à inclure les cas où certains champs seront erronés.

3.2.3   Validation et contrôle

Nous avons donc un rendu de notre formulaire, mais il n'y a aucun contrôle effectué sur le contenu du formulaire. Il serait sain d'établir des contrôles tels que le login soit disponible et non enregistré, et que les 2 mots de passe, soient identiques.

Pour cela rien de plus simple, il suffit juste d'écrire certaines méthodes dans notre classe de formulaire.

>>> class RegisterForm(forms.Form):

>>>   ...
>>>   def clean_login(self):
>>>     try:
>>>       #mon code de test
>>>       pass
>>>     except:
>>>       forms.ValidationError(_("Your login is already in use !))
>>>     return self.cleaned_data['login']

>>>
>>>   def clean(self):
>>>     if self.cleaned_data.get('password_1') != self.cleaned_data.get('password_2'):
>>>       raise forms.ValidationError(_("Your passwords didn't match !"))
>>>     return self.cleaned_data

Lors de la validation du formulaire, Django va effectuer une vérification préliminaire sur les types de champs utilisés, puis si on écrit une méthode clean_FIELD, la méthode sera appliquée sur le champ pour faire un contrôle.

Si tout se déroule bien la méthode clean est appelée, et effectue une validation sur tous les champs.

Comme nous le voyons, la structure des formulaires est assez souple pour permettre beaucoup de personnalisation.

3.2.4   Vue associée

La dernière étape consiste à écrire une vue qui pourra traiter le formulaire. Plusieurs méthodes sont envisageables, mais il y a la bonne méthode et la mauvaise. Voici un exemple de bonne méthode :

>>> def subscribe(request):
>>>   if request.POST:
>>>     form = RegisterForm(request.POST, label_suffix=' :')
>>>     if form.is_valid():

>>>       form.save()
>>>       return HttpResponseRedirect('/roadshow/thankyou/')
>>>   else:
>>>     form = RegisterForm(label_suffix=' :')
>>>   return render_to_response('subscribe.html', {'form': form})

La bonne méthode comporte les point suivants :

  • Vérifier que le contenu fournis est bien en POST
  • Créer et utiliser la méthode save au sein des formulaires, par convention.
  • Toujours rediriger avec une page une fois les données traitées pour éviter les doubles injections et autres problèmes de sécurité.

3.3   Déploiement de l'application sur Apache

Le serveur de développement n'est pas une bonne initiative pour déployer son application. Comme son nom l'indique il sert au développement. En effet il n'est pas conçu pour servir à grande échelle les fichiers, sans compter en terme sécurité les lacunes existantes. Apache ou Lighttp remplissent très bien ce rôle.

Nous allons voir le cas avec Apache et ModPython. Dans votre répertoire sites-available nous allons écrire une configuration Apache.


<VirtualHost *:80>
      ServerName monserveur.domain.com

      DocumentRoot /repertoire/de/votre/projet

      <Location "/">
        SetHandler python-program
        PythonHandler django.core.handlers.modpython
        SetEnv DJANGO_SETTINGS_MODULE projet.settings
        PythonDebug On
        PythonPath "['repertoire/de/votre/projet', 'repertoire/de/votre/projet/application'] + sys.path"
      </Location>
</VirtualHost>

Avec ceci, votre projet est prêt à etre servis.

3.3.1   Fichiers statiques

Il reste cependant une chose à régler, comment servir les fichiers statiques de votre projet. Django peut très bien faire cela, mais une fois de plus, cette solution n'est ni adaptée ni optimisée, car Django sert à exécuter du code python et non à servir des fichiers.

Donc pour régler ce problème nous allons modifier la configuration Apache pour que ce soit Apache qui serve les fichiers statiques. Dans la section VirtualHost de votre configuration, il suffit de rajouter ceci pour chaque répertoire à servir.

Alias /css /repertoire/de/fichiers/static/css
<Location "/css">
   SetHandler None

</Location>

Le tour est joué.

3.4   Travaux pratiques

Vous allez maintenant créer une application réutilisable.

Le but est de créer une application de gestion de documents associés.

Il sera possible de regrouper les documents par thème et d'établir des fiches de suivi des divers documents.

4   Jour 4

4.1   Documenter son application

Documenter son application est vital. Cette méthode permet aux utilisateurs du code ou aux contributeurs de pouvoir rentrer plus facilement dans le projet. Le mieux est de documenter en même temps que l'on code, car faire une documentation une fois le projet fini est souvent fastidieux voire oublié.

Heureusement Python est là.

4.1.1   docstring

Les docstrings en Python sont entourées de triple quote, ce qui permet un formatage libre au niveau des retours à la ligne et des doubles ou simple quotes.

Tout ce qui se trouve entre les triples quotes fait parti de la docstring de la fonction, qui décrit ce que fait la fonction. Une docstring, si elle existe, doit être la première chose déclarée dans une fonction (la première chose après les deux points). Techniquement parlant, vous n'êtes pas obligés de donner une docstring à votre fonction, mais vous devriez toujours le faire.

Beaucoup d'IDE Python utilisent les docstrings pour fournir une documentation contextuelle, ainsi lorsque vous tapez le nom d'une fonction, sa docstring apparaît dans une bulle d'aide. Cela peut être incroyablement utile, mais cette utilité est liée à la qualité de votre docstring.

Un exemple :

>>> def mafonction(a, b):
>>>   """Docstring de mafonction
>>>   Cette fonction ne fait rien"""
>>>   pass

4.1.2   Module doc

Django intègre dans sa version 1.0 un module lié au module d'administration permettant une review des models, des tags, des vues et des filtres. Ce module s'appelle django.contrib.admindocs, il est nécessaire de le rajouter dans la section INSTALLED_APPS, et de lui lier une url.

>>> (r'^admin/doc/', include('django.contrib.admindocs.urls')),

Ce module va se servir des docstring rédigées au sein de votre application pour générer une documentation globale de votre projet.

Il existe aussi d'autres modules indépendant de Django se basant sur la technique des docstring pour établir une documentation. Par exemple doctutils.

4.1.3   Conclusion

Documenter son application, c'est l'ouvrir aux autres. Il sera plus facile d'acquérir d'autres contributeurs si votre application est bien documentée.

Bien entendu les docstrings ne font pas tout, rien ne vous empêche par exemple de rédiger un fichier texte décrivant comment installer votre application, comment l'utiliser, ce qu'il reste à faire, ou à debugger. Libre à vous.

4.2   Introduction aux tests

Tester une application est vital. Une application non testée, est une application mort-née. Ici nous ne parlons pas des tests effectués par l'utilisateur final, mais des tests sur le comportement du code.

Plus une application va se développer et s'étendre en terme de code, plus les sources de bugs seront importantes. Pourquoi ne pas limiter les risques en encadrant le tout par des tests.

Les tests permettent de cibler les sources de bug plus facilement, donnent un regard neuf sur la qualité du code, et surtout offrent un filet de sécurité indéniable, permettant d'éviter les régressions.

Il existe 2 types de tests : les tests unitaires et les tests fonctionnels. Les fonctionnels sont souvent effectués par l'utilisateur final, il existe des frameworks pour établir ces tests, mais étant donné les divers types d'applications disponibles, nous ne nous attarderons pas sur ce type.

4.2.1   Tests unitaires

Les tests unitaires, permettent de tester une ou plusieurs parties de l'application, ils assurent la cohérence de chaque fonction ou méthode écrite dans l'application.

Prenons un exemple simple : si je code une fonction effectuant une addition, il est utile d'écrire un test, vérifiant que la fonction retourne le bon résulat.


>>> def add(a, b):
>>>   return a + b
>>> add(1, 41)
42

Bien évidemment rédiger tous ces tests de manière cohérente, est très long, heureusement, il existe des frameworks pour nous simplifier la tâche. Le plus populaire pour les tests unitaires en python est unittest, il est dérivé du framework JUnit écrit en Java.

4.2.2   Doc tests

Les doctests sont une variante de l'écriture des tests, elles combinent les avantages de la documentation et des tests. Rien de mieux que d'avoir sa documentation contenant des tests.

La rédaction de ces tests se fait à l'intérieur des docstrings.

>>> def add(a, b):
>>>   """Make an addition
>>>   >>> add(1, 41)
>>>   42
>>>   """

Cette technique permet d'avoir une cohérence entre la documentation et les tests qui illustrent la documentation.

4.2.3   Debugger avec les tests

Les tests unitaires permettent de debugger une application, prenons un cas simple : celui d'une fonction effectuant une division :

>>> def div(a, b)
>>>   return a / b

Cette fonction peut très bien marcher dans 99% des cas, mais il existe un cas où cela ne marche pas : la division par 0.

Donc il est nécessaire d'écrire un test, reproduisant le bug, et de corriger le code jusqu'à ce que l'ensemble des tests passent sans erreur.

>>> def div(a, b):
>>>   if b == 0:
>>>     raise "Can't divise by 0"
>>>   return a / b

4.3   Utilisation de SVN

Utiliser un gestionnaire de versions, pour le développement d'une application d'envergure avec plusieurs collaborateurs est nécessaire.

Cela permet à chaque développeur d'avoir une copie du code, de pouvoir la modifier et une fois les modifications effectuées de les publier pour que les autres développeurs puissent en bénéficier.

Dans notre cas, SVN, possède la notion de tags et de branches, qui permettent de figer un développement, ou bien de forker un développement lourd pour ensuite le merger avec le code principal, une fois le code stabilisé.

4.3.1   Commandes principales

svn checkout
Permet de rapatrier le code source d'un projet.
svn commit
Publie les fichiers modifiés
svn diff
Permet de voir les modifications entre la copie locale et la copie distante.
svn status
Permet de voir le statut de chaque fichier provenant du SVN
svn revert
Permet de réinitialiser la copie locale par rapport à la copie distante
pixelvide
pixelvide
  SujetsRV
Gmini - comment intégrer un moteur de rech...115277
Retours - critiques que la documentation d...714975
Magento et owebia : documentation officielle215074
Installation paiement ATOS / SIPS Magento18297
Personnaliser la facture dans Magento08451
MAGENTO ET la gestion des taxes48993
Magento : liste d'extensions intéressantes...24941
Magento et les exports - format CSV et xml03872
Magento et les comparateurs de prix - a te...04089
Magento et migration OScommerce03776
Magento 1.4.0.1 : probleme avec google ana...24587
Magento et tranche de prix - comment les m...04529
Magento et les emails transactionnels25895
Magento Commerce - Ajouter un nouveau form...06024
Liste de Django App reperé sur google code...19510
Comment franciser Magento a 100%?26215
pixelvide
Powered by Zwook
A propos d'Emencia Mentions légales crédits Plan du site Contact
pixelvide
pixelvide