.. -*- mode: rst -*- ==================================== Django! Perfectionnistes à la bourre ==================================== .. include:: .. include:: Python African Tour Dakar - 06-10 juillet 2009 Agence Universitaire de la Francophonie :Auteurs: Ousmane Wilane :Version: $Id: django-course.rst 5 2009-07-06 06:15:01Z wilane $ .. class:: left Python African Tour? ____________________ - Une très bonne idée de **Kamon AYEVA** pour la promotion de Python et des logiciels libre en Afrique - Nous sommes une équipes de volontaires de tous les horizons et de tous les pays - Nous utilisons la dynamique, l'engagement et la passion de l'écosystème du Libre pour la gestion des logistiques complexes liés à ce projet - Nous organisons des formations gratuites et participons aux évènements organisés par les développeurs. PAT? (2) ________ Qui sommes nous: - Développeurs Python - Promoteurs Python et logiciels libre par la pratique - Social activistes DakarLUG ________ - Un groupe d'amis qui comptent le rester - Tout le même peut être membre de DakarLUG, rien à payer, il faut juste le pense très fort et venir aux manifestation (aider si possible) - On est contre l'idée d'une association formelle, juste un nom de domaine possédé par l'un d'entre nous. Ousmane Wilane ______________ Je suis: - Membre de DakarLUG - ``Vieux`` programmeur Python - Développeur (Web) Indépendant - http://wilane.org - ousmane@wilane.org Ressources __________ - Django: http://djangoproject.com/ - http://djangobook.com/en/2.0/ - Django Snippets: http://www.djangosnippets.org/ - Pas mal de Blogs intéressants des auteurs et enthousiastes Pré-requis __________ - Base de la programmation procédurale et OO - Introduction à Python - Version 1.1 Django (actuellement en bêta) Introduction ____________ Qu'est ce qu'un Framework WEB _____________________________ - Une réponse efficace aux problèmes de type CGI - Une réponse efficace au syndrome php (Gardez votre avis pour PHP African Tour s'il y a en un jour) Design Pattern MVC __________________ - Django est un MVC/MVT - Modèles: Couche d'accès aux données - Vue: Définit ce qu'il faut afficher et comment - Contrôleur: Décide de la vue à utiliser - models.py, views.py, urls.py, dernieres_factures.html Les parpaings de Django (1) ___________________________ :: from django.db import models class Facture(models.Model): numero = models.CharField(max_length=50) montant = models.FlotField() date_facture = models.DateField() Les parpaings de Django (2) ___________________________ :: from django.shortcuts import render_to_response from models import Facture def dernieres_factures(request): liste_factures = Facture.objects .order_by('-pub_date')[:10] return render_to_response('dernieres_factures.html', {'listes_factures': liste_factures}) Les parpaings de Django (3) ___________________________ :: from django.conf.urls.defaults import * import views urlpatterns = patterns('', (r'^dernieres/$', views.dernieres_factures), ) Les parpaings de Django (4) ___________________________ :: Factures

Factures

    {% for facture in listes_factures %}
  • {{ facture.nummero }}
  • {% endfor %}
Installation et configuration _____________________________ Pré-requis __________ Python (2.3-2.6) :: Python 2.6.2 (release26-maint, Apr 19 2009, 01:56:41) [GCC 4.3.3] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> Téléchargement et installation (Dist) _____________________________________ - aptitude/apt-get sous les distributions de la famille Debian - rpm & co - Exécutable sous Windows - ports Mac OS X Téléchargement et installation (à la main) __________________________________________ - archive depuis http://www.djangoproject.com/download/ 1. tar xzvf Django-1.1-beta-1.tar.gz 2. cd Django-* 3. sudo python setup.py install Téléchargement et installation (à la main) (2) ______________________________________________ - svn co http://code.djangoproject.com/svn/django/trunk/ django-svn-trunk 1. cd django-svn-trunk 2. sudo python setup.py install Téléchargement et installation (svn) ____________________________________ :: >>> import django >>> django.VERSION (1, 1, 0, ‘beta’, 1) Choix du support de stockage (officiels) ________________________________________ - Django ne nécessite pas une base de données - Installer le pilote Python d'une base supportée * PostgreSQL (http://www.postgresql.org/), psycopg ou psycopg2 * SQLite 3 (http://www.sqlite.org/) (rien à installer si Python>=2.5, pysqlite) * MySQL (http://www.mysql.com/) (mysqldb) * Oracle (http://www.oracle.com/) (cx_Oracle) Choix du support de stockage (extérieurs) _________________________________________ Installer le pilote Python d'une base supportée par une communauté externe * IBM DB2: http://code.google.com/p/ibm-db/ * Microsoft SQL Server 2005: http://code.google.com/p/django-mssql/ * Firebird: http://code.google.com/p/django-firebird/ * ODBC: http://code.google.com/p/django-pyodbc/ Création d'un nouveau projet ____________________________ Démarrer un projet:: django-admin.py startproject monprojet Échafaudage:: mysite/ __init__.py manage.py settings.py urls.py Exercice ________ - Créer un projet - Démarrer le serveur WEB de développement ``python manage.py runserver`` - Démarrer le serveur WEB sur un port différent Vues et urls Django simples ___________________________ - Importer la classe ``HttpResponse`` du module ``dajngo.http`` - Définir la fonction ``salut`` :: from django.http import HttpResponse def salut(request): return HttpResponse("Bonjour PAT") Fonctions de type vue _____________________ - Une vue attend au moins un paramètre ``request`` - ``request`` contient des informations de la requête ayant déclenchée la vue - ``request`` est de type HttpRequest - Aucune contrainte de nommage de la fonction Odyssée de la requête _____________________ - Recherche de ``settings.py`` dans le répertoire courant - Que vaut ROOT_URLCONF - Récursion dans la liste des motifs - Appel de la vue dés qu'il y a une correspondance - Renvoie de HttpResponse - Django retourne la bonne réponse au navigateur Classe HttpResponse (Anatomie) ______________________________ :: class HttpRequest(object): """A basic HTTP request.""" _encoding = None _upload_handlers = [] def __init__(self): self.GET, self.POST, self.COOKIES, self.META, self.FILES = {}, {}, {}, {}, {} self.path = '' ... Objet HttpRequest (Anatomie) ____________________________ :: class HttpResponse(object): """A basic HTTP response, with content and dictionary-accessed headers.""" status_code = 200 def __init__(self, content='', mimetype=None, status=None, content_type=None): from django.conf import settings self._charset = settings.DEFAULT_CHARSET ... Object QueryDict (Anatomie) ___________________________ :: class QueryDict(MultiValueDict): _mutable = True _encoding = None def __init__(self, query_string, mutable=False, encoding=None): MultiValueDict.__init__(self) if not encoding: from django.conf import settings encoding = settings.DEFAULT_CHARSET ... Exercice ________ Créer une vue qui affiche la date et l'heure: Contenu dynamique. Exercice (2) ____________ Vue:: from django.http import HttpResponse import datetime def date_actuelle(request): nunc = datetime.datetime.now() html = "Hey PAT! Il est %s." % nunc return HttpResponse(html) url:: urlpatterns = patterns('', ('^ladate/$', date_actuelle), ) Exercice (3) ____________ - Construire une vue dynamique avec une url dynamique ``/ladate/plus/`` - Arrêter l'exécution pour ausculter la page d'erreur de Django (commentez le bloc try) Exercice (4) ____________ ``(r'^ladate/plus/\d{1,2}/$', dans_x_heures)`` :: def dans_x_heures(request, offset): try: offset = int(offset) except ValueError: raise Http404() dt = datetime.datetime.now() + datetime.timedelta(hours=offset) assert False html = "Dans %s heure(s), il sera %s." % (offset, dt) return HttpResponse(html) Routage d'URLs ______________ URLconf _______ - Un fichier Python comportant une séquence de motifs - Chaque motif peut être associé à un callable - Un callable implémente ``__call__`` Expressions régulières ______________________ - Technique compacte d'expression de motifs dans le texte - Très peu de motifs utilisés en général pour les URLs - re documenté sur http://docs.python.org/library/re.html#re-syntax Expressions régulières (2) __________________________ ========= =============== Symbole Correspondance ========= =============== . (point) N'importe quel caractère \d Un chiffre [A-Z] Un caractère entre A et Z [a-z] Un caractère entre a et z [aA-zZ] Un caractère entre a et z (insensible à la casse) ========= =============== Expressions régulières (3) __________________________ ======= ============== Symbole Correspondance ======= ============== \+ Un ou plusieurs occurrences de l'expression précédente [^/]+ Un ou plusieurs caractères jusqu'au prochain / (exclu) ? 0 ou une occurrence de l'expression précédente \* 0 ou plusieurs occurrence de l'expression précédente {1,n} Entre et n occurrence de l'expression précédente ======= ============== Exemple d'URLconf simple ________________________ :: (r'^facture/list/$', 'views.facturelist'), (r'^facture/new/$', 'views.add_a_facture'), (r'^facture/new/(?P\d+)$', 'views.add_x_facture'), (r'^facture/edit/(?P\d+)$', 'views.editer'), Utilisation de plusieurs URLconfs _________________________________ :: from django.conf.urls.defaults import patterns, include urlpatterns = patterns('') urlpatterns += patterns('', (r'^$', views.homepage), (r'^search/$', views.search), (r'^news/$', views.news)) urlpatterns += patterns('', (r'^comptess/', include('comptes.urls'))) Passage d'arguments aux URLs ____________________________ - Les vue peuvent recevoir des arguments de type **kwargs** - (r'^facture/(?P Avis de commande

Avis de commande

Bonjour {{ nom_complet }},

Nous vous remercions pour la commande passée par {{ company }}. Vous serez livré le {{ date_livraison|date:"D d M Y" }}.

Rappel des éléments commandés:

    {% for element in liste_elements %}
  • {{ element }}
  • {% endfor %} Exemple de template(3) ______________________ ::
{% if garantie %}

Les informations relatives à la garantie seront inclus dans le colis.

{% else %}

Vous n'avez pas souscris à une garantie.

{% endif %}

Cordialement,
{{ company }}

Utilisation d'une template ___________________________ La mise en oeuvre d'une template est très simple: - Création d'un objet Template avec les données brutes. - Appel de la méthode ``render()`` en lui passant une série de variable appelés ``contexte`` Exemple _______ Utilisez le shell du projet (où définir ``DJANGO_SETTINGS_MODULE`` à la main sans ``.py``) :: >>> from django.template import Template, Context >>> t=Template('Je suis le {{ nom_complet }}.') >>> c = Context({'nom_complet': 'Python African Tour'}) >>> t.render(c) u'Je suis le Python African Tour.' Objets Template _______________ :: >>> print t >>> - Lorsque vous créez un objet Template, il est compilé sous une forme interne optimisée. - Si l'objet Template décèle une erreur l'exception ``TemplateSyntaxError`` sera levée. - ``0xb7ea7e2c`` changera tout le temps, c'est l'identité Python de l'objet :: >>> id(t) 3085598252L >>> int(0xb7ea7e2c) 3085598252L Exception ``TemplateSyntaxError`` _________________________________ - tag invalide - arguments invalides à un tag correcte - filtre invalide - arguments invalides à un filtre correcte - syntaxe de template incorrecte - tags non fermé (pour les tags qui requièrent la fermeture) Objets Context ______________ - Un objet de type ``template.Context`` permet de résoudre les variables dans la template - Le constructeur du ``Context`` attend un argument en option qui est un dictionnaire associant des variables et leurs valeurs. - ``Template.render(Context)`` renvoie un objet unicode - On peut accéder au éléments d'un ``Context`` comme s'il s'agissait d'un dictionnaire. Exercice ________ En console chargez la template définit ci-dessous (faire référence à la template Avis de commane) et passez lui le contexte adéquat et ensuite effectuer le rendu. Résolution de variables dans une template _________________________________________ - L'accès à un dictionnaire se fait avec le signe **.** ``{{pesonne.nom_complet}}`` - L'accès aux attributs d'objet se fait avec le signe **.** - Le **.** peut aussi permettre de faire référence à une méthode d'un objet - Le **.** est aussi utilisé pour faire référence à l'indice d'une liste Ordre de résolution ___________________ Lorsque le système de template rencontre un **.** il procède à sa résolution dans l'ordre suivant: - Recherche dans un dictionnaire - Recherche d'un attribut - Appel à une méthode * la méthode ne doit pas disposer d'arguments obligatoires) * Si la méthode lève une exception, elle sera propagée à moins que l'exception définisse l'attribut ``silent_variable_failure`` à False. - Indice de liste (pas d'indice négatif) Gestion de variable indéfini _____________________________ - Par défaut les variables indéfinis sont considérées comme vide - L'arbitrage consiste à ne pas lever une exception sur un site web pour une faute de frappe. Les Tags ________ Les tags permettent de donner des ordres au système de template if/else _______ Les opérateurs ``not``, ``or`` et ``and`` peuvent être utilisés (sans parenthèses pour le contrôle de l'ordre) :: {% if on_est_le_weekend %}

Génial c'est le Weekend!

{% endif %} :: {% if on_est_le_weekend %}

Génial c'est le Weekend!

{% else %}

Bon, il va falloir travailler.

{% endif %} if/else (2) ___________ Sont considérés comme fausse par Python comme par le système de template les objets suivants dans un contexte booléen: - Liste vide ``[]`` - Tuple vide ``()`` - Dictionnaire vide ``{}`` - Chaîne vide ``''`` - Zéro littéral ``0`` - L'objet cpécial ``None`` - L'objet ``False`` - Objets personnalisés qui définissent leurs comportements dans un contexte booléen. for ___ Il permet d'itérer parmi les éléments d'une séquence ::
    {% for facture in listes_factures %}
  • {{ facture.numero }}
  • {% endfor %}
for (2) _______ Un motif couramment utilisé est la vérification de la taille de la séquence avant de démarrer la boucle: :: {% if listes_factures %} {% for facture in listes_factures %}

{{ facture.numero }}

{% endfor %} {% else %}

Aucune facture.

{% endif %} Exercice ________ Définissez une liste de facture (liste de dictionnaire) et utiliser la en console pour le rendu de la template ci-dessus. for (3) _______ Parce que le motif précédant est courant, un tag ``empty`` a été introduit: :: {% for facture in listes_factures %}

{{ facture.numero }}

{% empty %}

Aucune facture.

{% endfor %} for (3) _______ - Pas de ``break`` et ``continue`` - Des variables sont définit par le système de template dans la boucle ``for``: * {{ forloop.counter }} et {{ forloop.counter0 }} * {{ forloop.revcounter }} et {{ forloop.revcounter0 }} * {{ forloop.first }} et {{ forloop.last }} sont des booléens Exercice ________ Utilisez ``{{ forloop.last }}`` pour séparer une liste de mots par des virgules et finir la liste par un point. ifequal/ifnotequal __________________ - Comparaison avec des variables de template - Comparaison avec des chaînes, entiers et décimaux. :: {% ifequal type_facture 'pro-forma' %}

Factures Pro-forma

{% else %}

Facture

{% endifequal %} ifequal/ifnotequal (syntaxe invalide) _____________________________________ :: {% ifequal variable True %} {% ifequal variable [1, 2, 3] %} {% ifequal variable {'cle': 'valeur'} %} commentaires ____________ Le système de template accepte permet les commentaire exprimés au choix sous les formes suivantes: :: {# Je suis un commentaire et je peut pas passer à la ligne #} :: {% comment %} Je suis un commentaire multi-ligne . {% endcomment %} Les filtres ___________ Les filtres permettent d'altérer les valeurs des objets avant de les afficher. Il utilisent le caractère ``|`` - {{ nom_complet|capitalize }}, {{ date_livraison|date:"D d M Y" }} - Les filtres sont cahinable: {{ ma_list|first|upper }} (premier élément de la liste en majuscule) - Certains filtres nécessitent un argument: {{ mabio|truncatewords:"30" }} Utilisation des templates dans une vue ______________________________________ :: from django.http import HttpResponse import datetime def date_actuelle(request): nunc = datetime.datetime.now() html = "Hey PAT! Il est %s." % nunc return HttpResponse(html) Utilisation des templates dans une vue (2) __________________________________________ :: from django.http import HttpResponse from django.template import Template, Context import datetime def date_actuelle(request): t = Template("Hey PAT! Il est {{ nunc }}.") html = t.render(Context({'nunc': datetime.datetime.now()})) return HttpResponse(html) Utilisation des templates dans une vue (3) __________________________________________ L'approche de la page précédente ne règle pas les problème évoqués liés à cette approche. Exercice ________ Sauvegarder le code html dans le répertoire de votre projet sous un répertoire appelés ``templates`` et utilisez ``open`` pour accéder au contenu. Exercice (2) ____________ :: def date_actuelle(request): now = datetime.datetime.now() fp = open('/home/wilane/pat-sn/monprojet/templates/heure.html') t = Template(fp.read()) fp.close() html = t.render(Context({'nunc': now})) return HttpResponse(html) Utilisation des templates dans une vue (4) __________________________________________ - L'approche de l'exercice précédant ne gère pas les exceptions liées au problèmes d'accès à un fichier. - Il code en dur l'emplacement des templates - Il utilise une série de ligne de code répétitives et ennuyeuses. Chargement de templates _______________________ Le fichier ``settings.py`` contient une directive indiquant une série de répertoires contenant des templates: :: TEMPLATE_DIRS = ( # Put strings here, like "/home/html/django_templates" or # "C:/www/django/templates". # Always use forward slashes, even on Windows. # Don't forget to use absolute paths, not relative paths. ) :: TEMPLATE_DIRS = ( '/home/wilane/pat-sn/monprojet/templates', ) Chargement de templates (2) ___________________________ - On peut ajouter autant de répertoire tant qu'ils sont accessible par l'utilisateur - Si ce tuple ne contient qu'un seul élément, ne pas oublier la virgule (notez que cette virgule permet de lever tout équivoque entre une expression entre parenthèse et un tuple) - On peut utiliser le chemin relatif au fichier ``setting.py``: :: os.path.join(os.path.dirname(__file__), 'templates') .replace('\\','/'), Chargement de templates (3) ___________________________ :: from django.http import HttpResponse import datetime from django.template import Context from django.template.loader import get_template def date_actuelle_templatedir(request): now = datetime.datetime.now() t = get_template('heure.html') html = t.render(Context({'nunc': now})) return HttpResponse(html) Exercice ________ Faite référence à une template qui n'existe pas avec get_template et commentez l'erreur. Chargement de templates (3) ___________________________ Pour éviter de répéter le cycle chargement de template, rendu contextuel et renvoie de HttpResponse on utilise ``render_to_response`` sous ``django.shortcuts``: :: from django.shortcuts import render_to_response import datetime def date_actuelle_rtr(request): now = datetime.datetime.now() return render_to_response('heure.html', {'nunc': now}) Exercice ________ 1- Utilisez un sous répertoire sous ``templates`` et vérifiez que ``render_to_response()`` peut accéder aux fichiers dans le sous-répertoire. 2- Utilisez la fonction ``locals()`` pour encore condenser le code ci-dessus. Tag include ___________ La tag ``{% tag %}`` peut être utilisée pour inclure une template dans une autre en utilisant la même méthode de résolution: :: {% include 'nav.html' %} {% include "nav.html" %} {% include nom_template %} Héritage de templates _____________________ - Gérer la complexité du design d'un site web avec ce qu'on a vu jusque là sur les template serait encore assez laborieux et répétitif. - Une façon élégante de résoudre la généricité dans le design utilise l'héritage entre templates - L'idée de l'héritage consiste à définir un squelette constitué de ``block`` que les templates filles peuvent ``surcharger`` Exemple (base.html) ___________________ :: {% block titre %}{% endblock %}

Mon site horloge

{% block contenu %}{% endblock %} {% block pied_de_page %}

Merci de votre visite.

{% endblock %} Exemple (fille heure_extends.html) __________________________________ :: {% extends "base.html" %} {% block titre %}L'heure actuelle{% endblock %} {% block contenu %}

Il est {{ nunc }}.

{% endblock %} Exercice ________ - Mettre en oeuvre l'héritage précédant et commentez le comportement du pied_de_page - Créer une template utilisant la vue ``dans_x_heures`` héritant de ``base.html`` Modèle Base de Données ______________________ - Une vue gère la logique métier et renvoie une réponse. - Le routage URLconf permet d'associer des motifs d'accès aux ressources à des vues. - Nombre de site modernes utilisent des données stockées dans une ``base de données`` - Il est aisé d'utiliser Django pour la mise en oeuvre de site utilisant des ``base de données`` Accès DB à ``l'ancienne`` _________________________ :: from django.shortcuts import render_to_response import sqlite3 def liste_factures(request): cx=sqlite3.connect('/tmp/MaBase.pasdesoucis') cursor = cx.cursor() cursor.execute('SELECT numero FROM facture ORDER BY date_facture') liste_facture = [ligne[0] for ligne in cursor.fetchall()] cx.close() return render_to_response('list_factures.html', {'liste_factures': liste_factures}) Accès DB à ``l'ancienne`` (2) _____________________________ Cette approche pose un nombre de problèmes: - Les paramètres de connexion sont codés en dur dans la vue - Nous écrivons du code répétitif et ennuyeux (connexion, curseur, exécution de requête, fermeture de connexion, etc) - Ce code nous lie d'une certaine façon à SQLite et si nous voulons changer de base il faudra réécrire une bonne partie du code. Accès DB à ``l'ancienne`` (3) _____________________________ La couche ``base de données`` Django est une tentative de résolution de ces problèmes entre autre: Exemple:: def liste_factures(request): liste_factures = Facture.objects..order_by('date_facture') return render_to_response('list_factures.html', {'liste_factures': liste_factures}) Configuration d'accès à une base de données ___________________________________________ Les paramètres de configuration de la couche d'accès à la base de données sont généralement stockés dans le fichier ``settings.py`` où on a définit TEMPLATE_DIR: :: DATABASE_ENGINE = '' DATABASE_NAME = '' DATABASE_USER = '' DATABASE_PASSWORD = '' DATABASE_HOST = '' DATABASE_PORT = '' DATABASE_ENGINE _______________ ============= =============== ======================= Configuration Base de données Pilote postgresql PostgreSQL psycopg2 mysql MySQL MySQLdb sqlite3 SQLite aucun pour Python >= 2.5 oracle Oracle cx_oracle ============= =============== ======================= Exercice ________ - Pointez vers une Base SQLite - Testez la connexion :: Python 2.6.2 (release26-maint, Apr 19 2009, 01:56:41) [GCC 4.3.3] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> from django.db import connection >>> cursor=connection.cursor() >>> Concept d'application Django ____________________________ - Un projet Django est une unité de regroupement de plusieurs groupes de fonctionnalités homogènes - Chaque ``élément`` du projet est généralement une application - Django dispose d'une série d'applications telle que l'admin, le système de commentaires et la gestion des utilisateurs/groupes - Une application est portable et est généralement réutilisable dans plusieurs projets Création d'une application __________________________ - Créez autant d'applications que vous avez de fonctionnalités homogènes réutilisables. - Réutilisez des applications connues pour des fonctionnalités similaires ou analogues - Pour utiliser une application avec Django il est convenu que le modèle de données doit résider au sein de l'application. Création d'une application (2) ______________________________ Dans le répertoire ``monprojet`` exécutons la commande suivante: :: python manage.py startapp factures :: factures/ __init__.py models.py tests.py views.py Ces fichiers contiendrons les modèles et les vue de l'application facture Définition de notre modèle Django (hypothèses simplistes) _________________________________________________________ - Une facture dispose d'un client, d'un numéros, du montant, de la date, de la date de paiement et des détails - Chaque élément de détail d'une facture est constitué de l'item facturé, de la quantité et du prix unitaire. - On simplifie en regroupant les détails Définition de notre modèle Django _________________________________ :: from django.db import models class Client(models.Model): nom = models.CharField(max_length=30) adresse = models.CharField(max_length=50) ville = models.CharField(max_length=60) pays = models.CharField(max_length=50) website = models.URLField() Définition de notre modèle Django (2) _____________________________________ :: class Facture(models.Model): numero = models.CharField(max_length=64) client = models.ForeignKey(Client) montant = models.FloatField() class Detail_Facture(models.Model): facture = models.ForeignKey(Facture) nom = models.CharField(max_length=30) prix_unitaire = models.FloatField() description = models.CharField(max_length=50) Définition de notre modèle Django (commentaires) ________________________________________________ - Chaque modèle est représenté par une classe Python dérivée de ``django.db.models.Model`` - La classe parent ``Model`` dispose de toute la plomberie nécessaire à l'interaction avec une base de donnée - Notre modèle est responsable de la définition des champs dont il a besoin - Les modèles définit ci-dessous sont suffisant pour accéder à un modèle de donnée complet - Chaque modèle correspond généralement à une table et chaque champ correspond à un champ de la table. Activation d'application ________________________ Dans ``settings.py`` nous allons ajouter notre application dans ``INSTALLED_APPS``:: INSTALLED_APPS = ( # 'django.contrib.auth', # 'django.contrib.contenttypes', # 'django.contrib.sessions', # 'django.contrib.sites', 'factures', ) Validation d'application ________________________ Pour vérifier que tout se passe bien nous disposons de la commande de gestion ``validate`` :: python manage.py validate :: wilane@ndoucoumane:~/pat-sn/monprojet$ python manage.py validate 0 errors found Cette commande vérifie la syntaxe et la logique de vos modèle et rapporte les erreurs éventuelles trouvées. Génération SQL ______________ Si le modèle est valide alors on peut regarder le code ``SQL`` correspondant à la définition de nos modèles:: python manage.py sqlall factures Génération SQL (2) __________________ :: wilane@ndoucoumane:~/pat-sn/monprojet$ python manage.py sqlall factures BEGIN; CREATE TABLE "factures_client" ( "id" integer NOT NULL PRIMARY KEY, "nom" varchar(30) NOT NULL, "adresse" varchar(50) NOT NULL, "ville" varchar(60) NOT NULL, "pays" varchar(50) NOT NULL, "website" varchar(200) NOT NULL ) ; Génération SQL (3) __________________ :: CREATE TABLE "factures_facture" ( "id" integer NOT NULL PRIMARY KEY, "numero" varchar(64) NOT NULL, "client_id" integer NOT NULL REFERENCES "factures_client" ("id"), "montant" real NOT NULL ) ; Génération SQL (4) __________________ :: CREATE TABLE "factures_detail_facture" ( "id" integer NOT NULL PRIMARY KEY, "facture_id" integer NOT NULL REFERENCES "factures_facture" ("id"), "nom" varchar(30) NOT NULL, "prix_unitaire" real NOT NULL, "description" varchar(50) NOT NULL ) ; CREATE INDEX "factures_facture_client_id" ON "factures_facture" ("client_id"); CREATE INDEX "factures_detail_facture_facture_id" ON "factures_detail_facture" ("facture_id"); COMMIT; Convention de nommage _____________________ - Les noms des tables générées sont préfixés par le nom de l'application avec le nom du modèle en minuscule - Django ajoute une clé primaire à chaque modèle par défaut - Django ajoute un post-fixe ``_id`` aux clés primaires dans les références - Les instructions ``SQL`` sont adaptés au moteur utilisé Synchronisation de données __________________________ Une fois que le modèle est définit, vous devez demander à Django de créer le schéma :: python manage.py syncdb :: wilane@ndoucoumane:~/pat-sn/monprojet$ python manage.py syncdb Creating table factures_client Creating table factures_facture Creating table factures_detail_facture Installing index for factures.Facture model Installing index for factures.Detail_Facture model wilane@ndoucoumane:~/pat-sn/monprojet$ Accès aux données _________________ - Django vous fournit une API d'accès aux données de vos modèles. - l'API vous permet de créer de créer des instances de vos modèles (enregistrements) - l'API vous permet de récupérer, de mettre à jour et de supprimer des des données Gesion simple de données ________________________ :: python manage.py shell :: from factures.models import Client, Facture, Detail_Facture c0 = Client(nom='PAT', adresse='22 Rue de la vie', ville='Dakar', pays='Sénégal', website='http://www.coactivate.org/projects/python-african-tour/project-home') c0.save() Gesion simple de données (2) ____________________________ :: c1 = Client(nom='AUF BAO', adresse='Corniche Ouest', ville='Dakar', pays='Sénégal', website='http://www.refer.sn') c1.save() liste_clients = Client.objects.all() liste_clients [, ] Exercice ________ - Utilisez ``objects.create()`` pour instancier et sauver en même temps: * Créer Deux factures pour chaque client * Chaque facture définit deux livres - Utilisez vos connaissances de Python pour ausculter les attributs des éléments renvoyés par ``.objects.all()`` Représentation chaîne d'un modèle _________________________________ - Dans les exercices précédant on a remarqué que les objets sont juste nommés ``Type object`` - Python permet de Exercice ________ Ajouter une méthode ``__unicode__`` à chaque modèle dans ``models.py`` :: class Client(models.Model): nom = models.CharField(max_length=30) adresse = models.CharField(max_length=50) ville = models.CharField(max_length=60) pays = models.CharField(max_length=50) website = models.URLField() def __unicode__(self): return u'%s %s' % (self.nom, self.ville) - Testez le résultats avec ``objects.all()`` - Mettez à jour les données en utilisant les attributs et ``.save`` Les QuerySets _____________ - Pour accéder à des données ``Client`` on a utiliser le modèle ``Client`` - Le modèle ``Client`` nous a donné accès à un objet ``objects`` appelé ``Manager`` - Chaque modèle dispose automatiquement au moins d'un ``Manager`` appelé ``objects`` - ``all()`` est la méthode du ``Manager` qui renvoie l'ensemble des ``enregistrements`` - La **liste** renvoyé n'est pas vraiment une ``Liste``, c'est en fait ce qu'on appelle une ``QuerySet`` - Toutes les accès aux données suivent généralement le même motif: utilisation des services d'un ``Manager`` Application de Filtres ______________________ - On a généralement pas besoin de toutes les données ``all()`` - ``filter()`` est une autre méthode de ``Manager`` permettant de circonscrire les données avec un mécanisme de sélection de critères :: Client.objects.filter(nom='AUF BAO') [] >>> Exercice ________ Utilisez ``.query_as_sql()`` pour voir la requête générée par le filtre précédant:: >>> q=Client.objects.filter(nom='AUF BAO') >>> q.query >>> q.query.as_sql() Critères de recherche _____________________ ``nom__contains`` permet de recherche des occurrences dont le ``nom contient`` :: q = Client.objects.filter(nom__contains='BAO') q.query.as_sql() - Plusieurs infos peuvent être passées à filter pour préciser les données, passage normal de paramètre à une méthode - ``.get`` permet de récupérer un seul enregistrement ou lever une exception. Chaîner QuerySets _________________ Les QuerySet peuvent être chaînés :: q = Client.objects.filter(ville='Dakar').order_by('nom') q [, ] Exercice ________ Utilisez la QuerySet ``Client.objects.filter(ville='Dakar').order_by('nom')`` et commentez le résultat. Slicing de QuerySets ____________________ - Les ``QuerySet`` disposent des même découpages en ``tranche`` que les listes - ``[:n]`` renvoie au plus ``n`` enregistrement - ``[m:n]`` renvoie ``n`` enregistrements à partir de l 'offset ``m`` - Les indexes négatifs ne sont pas supportés Exercice ________ Vérifiez que l'API n'effectue pas le découpage en tranche sur l'ensemble des données mais construit plutôt une requête adaptées pour les bases de données le supportant (Indice: ``LIMIT SQL``) Ordres dans les QuerySets _________________________ - Il est possible d'utiliser la méthode ``order_by`` pour - Il est possible de demander au modèle de définir un ordre par défaut :: class Meta: ordering = ['nom'] Exercice ________ Ajouter un ordre au modèle par la méthode précédente et testez le résultat. Exercice ________ - Nous avons vu ``all() filter() order_by()`` - Testez et interprétez ``exclude()`` en le chainant à la méthode ``filter()`` - Interprétez les critères de recherche suivants: ``startswith, endswith, in`` ``gt, gte, exact iexact`` - Testez ``.delete()`` Pour aller plus loin ____________________ - QuerySet API Reference: http://docs.djangoproject.com/en/dev/ref/models/querysets/ - Gestion de données liées - Gestion de données liées - Objets Q Application contrib.admin _________________________ - l'admin est une application django ``livré avec`` sous ``django.contrib`` - ``django.contrib`` contient d'autres applications telle que ``auth``, ``sessions``, ``comments``, etc. - l'admin permet assez simplement sans programmation supplémentaire onéreuse d'accéder aux données définit par les modèles gérés - Dans certaines application l'admin est utilisé comme un ``backoffice`` (gérer les données des utilisateurs, les commentaires, etc) - l'admin est utile lorsque des utilisateurs non technique doivent regarder le modèle et/ou entrer des données rapidement Activation de l'admin _____________________ - Ajouter ``django.contrib.admin`` à la séquence ``INSTALLED_APPS`` dans ``settings.py`` - Vérifier que ``INSTALLED_APPS`` contient aussi ``django.contrib.auth``, ``django.contrib.contenttypes`` et ``django.contrib.sessions``, l'admin en a besoin. - Vérifiez que ``MIDDLEWARE_CLASSES`` contient ``django.middleware.common.CommonMiddleware``, ``django.contrib.sessions.middleware.SessionMiddleware`` et ``django.contrib.auth.middleware.AuthenticationMiddleware`` Activation de l'admin (suite) _____________________________ - Exécutez ``python manage.py syncdb``, vous aurez à créer le super-utilisateur (si vous répondez non, vous pourrez le créer plus tard avec ``python manage.py createsuperuser`` - Editez votre ``urls.py`` et activez le motif préfixé par ``admin`` (vérifier que les modules utiles sont importés) Exercice ________ - Lancer la validation de votre application - Lancer le serveur web de développement - Testez l'admin en changeant la langue dans ``settings.py`` Enregistrement de nos modèles dans l'admin __________________________________________ - Créer un fichier ``admin.py`` sous ``factures/`` - Importer le module ``ædmin`` - Importez les modèles de l'application ``factures`` :: from django.contrib import admin from factures.models import Client, Facture, Detail_Facture admin.site.register(Client) admin.site.register(Facture) admin.site.register(Detail_Facture) Personnalisation dans le modèle _______________________________ - Utilisez de ``balnk=True`` et ``null=True`` dans les champs en option - Utilisez ``verbose_name="Joli nom"`` pour le nom affiché pour les champs - Utilisez ``choices=Tuple de Tuples à deux éléments`` pour une liste de choix ``CharField`` - Utilisez ``verbose_name`` et ``verbose_name_plural`` dans la classe ``Meta`` de chaque modèle Classe ModelAdmin personnalisée _______________________________ - Par défaut l'admin utilise une classe ``ModelAdmin`` par défaut pour la gestion de votre modèle dans l'admin. - Vous pouvez personnaliser nombre d'aspects de cette gestion en dérivant la classe ``django.contrib.admin.ModelAdmin`` - Votre classe personnalisée doit être passé comme second argument à la fonction ``admin.site.register()`` :: admin.site.register(Client, ClientAdmin) Exercice ________ - Définir la classe ``ClientAdmin`` fille de ``ModelAdmin`` dans ``admin.py`` - Définir dans la classe ``ClientAdmin`` l'attribut ``list_display = ['nom', 'ville', 'pays']`` - Testez le resulta dans l'interface admin et commentez - Devinez l'attribut ``list_filter = ['ville', 'pays']`` et interprétez - Définir l'attribut ``search_fields = ['nom', 'ville', 'pays']`` et interprétez - Ajoutez un attribut ``date_facture`` au modèle ``Facture`` et synchronisez - Définir une classe dérivée de ``ModelAdmin`` avec un attribut ``date_hierarchy = 'date_facture'`` et interprétez Gestion des utilisateurs ________________________ - Puisque vous êtes connecté comme utilisateur ``ædmin`` vous pouvez faire ce que voulez. - l'admin utilise un système de permission pour gérer les autorisations d'accès. - La gestion des utilisateurs est disponible à travers l'interface admin. - Les usagers sans droit ``super-utilisateur`` sont gérés par le système de permission/autorisation avec une granularité ``CRUD`` par défaut Les drapeaux: * ``actif`` si le compte est actif ou non * ``statut équipe`` si l'utilisateur à le droit de se connecter à l'interface admin ou non * ``statut super-utilisateur`` si l'utilisateur est un super-utilisateur ou non Formulaires HTML ________________ - Les formulaires sont généralement incournables dans nombre de site et applications webs. - Django fournit une batterie d'outils permettant de gérer les formulaires durant tout leurs cycle de vie - Django permet simplement de gérer la validation des données avec l'esprit dit ``DRY`` - l'objet HttpRequest (request) nous donne accès à une série d'information utile pour la gestion des foumlaires (request.GET, request.POST, request.META, request.method, etc) - La mise en oeuvre d'un formulaire nécessite comme toujours une vue, un URLconf et une template. Gestion d'un formulaire simple (vue formulaire) _______________________________________________ On va construire un formulaire permettant de rechercher un client :: from django.shortcuts import render_to_response def form_recherche(request): return render_to_response('form_recherche.html') Gestion d'un formulaire simple (vue de recherche) _________________________________________________ :: from django.http import HttpResponse def search(request): if 'q' in request.GET and request.GET['q']: message = 'Vous avez recherché: %r' % request.GET['q'] else: message = 'Votre formulaire envoyé est vide.' return HttpResponse(message) Gestion d'un formulaire simple (template) _________________________________________ :: Recherche
Gestion d'un formulaire simple (URLconf) ________________________________________ :: from factures import views urlpatterns = patterns('', # ... (r'^rech-form/$', views.form_recherche), (r'^chercher/$', views.rechercher), # ... ) Exercice ________ - Avec vos connaissance de l'API d'accès aux données et des templates complétez le formulaire de rechercher et afficher les clients trouvés dans un tableau html (utilisez ``Client.objects.filter(nom__icontains=q)``). - Testez le code suivant et commentez :: def affichez_meta(request): values = request.META.items() values.sort() html = [] for k, v in values: html.append('%s%s' % (k, v)) return HttpResponse('%s
' % '\n'.join(html)) Exercice ________ - Améliorer le résultat de votre travail en gérant le formulaire et la recherche dans une seule vue Exercice ________ :: def search(request): error = False if 'q' in request.GET: q = request.GET['q'] if not q: error = True else: clients = Client.objects.filter(nom__icontains=q) return render_to_response('resultats.html', {'clients': clients, 'query': q}) return render_to_response('form_recherche.html', {'error': error}) Exercice ________ - Modifier le code de l'exercice précédant pour vérifier que la longueur de la chaîne est au moins de 3 caractères et au plus de 20 caractère avant de lancer la recherche. - Modifier le code pour qu'il soit capable de traiter plusieurs erreurs (champs vide, 20 < longueur < 3 caractères) API Form ________ - La gestion manuelle du formulaire précédant est plutôt inefficace même si on est arrivé à un résultat plutôt acceptable. - ``django.forms.Form`` est une classe permettant une gestion des formulaires - Créons un formulaire de recherche :: from django import forms class FormRecherche(forms.Form): q = forms.CharField() API Form (2) ____________ :: >>> from django import forms >>> class RechForm(forms.Form): ... q = forms.CharField() ... >>> RechForm() <__main__.RechForm object at 0xb783f1cc> >>> f= RechForm() >>> print f Rendu du formulaire ___________________ :: >>> print f.as_ul()
  • >>> print f.as_p()

    Association d'un formulaire ___________________________ :: >>> f= RechForm({'q':'AUF'}) >>> print f Validation du formulaire ________________________ :: >>> f = RechForm() >>> f.is_bound False >>> f = RechForm({'q':'PAT'}) >>> f.is_bound True >>> f.is_valid() True Validation du formulaire (2) ____________________________ :: >>> f = RechForm({'q':''}) >>> f.is_bound True >>> f.is_valid() False >>> f.errors {'q': [u'Ce champ est obligatoire.']} >>> f['q'].errors [u'Ce champ est obligatoire.'] Données valide d'un formulaire attaché ______________________________________ :: >>> f = RechForm({'q':'PAT'}) >>> f.is_bound True >>> f.is_valid() True >>> f.cleaned_data {'q': u'PAT'} >>> Formulaire et Vue (Exercice) ____________________________ - Reprenez le code de votre formulaire de recherche - Créer un fichier ``forms.py`` sous votre application ``factures`` - Définissez la classe ``RechForm`` ci-dessus - Adaptez la template du formulaire de recherche pour recevoir l'objet ``form`` - Utilisez vos connaissance de la ``Form`` API et modifiez la vue Formulaire et Vue (Exercice) (2) ________________________________ :: from django.shortcuts import render_to_response from factures.forms import RechForm def recherche(request): if request.method == 'POST': form = RechForm(request.POST) if form.is_valid(): q = form.cleaned_data['q'] clients = Client.objects.filter(nom__icontains=q) return render_to_response('resultats.html', {'clients': clients, 'query': q}) else: form = RechForm() return render_to_response('form_recherche.html', {'form': form}) Personnalisation des paramètres de champs _________________________________________ - L'API Form permet de personnaliser tous les aspects de la gestion des formulaire - Toutes les validations standard associés aux champs sont gérés par l'API - Elle permet de contrôler les aspects de validation à travers la définition des champs (``required=False``, ``max_length=25``, ``label=MonLabel``, etc) - Elle vous fournit une série de ``widget`` que vous pouvez utiliser pour changer les widgets par défaut (``forms.CharField(widget=forms.Textarea)`` Validation personalisée des champs __________________________________ :: from django import forms class RechForm(forms.Form) q = forms.CharField() def clean_q(self): q = self.cleaned_data['q'] if not 3 < len(q) < 20: raise forms.ValidationError("La chaîne de recherche doit avoir entre 3 et 20 caractères!") return q Thèmes avancés à exploré ________________________ - Application contrib.auth - Vues génériques - Pisa/Reportlab pour la génération de PDFs - Debug avec Django - Emails avec Django - Déployer Django - Services Web RESTful - Quelques considérations de sécurité - Cache de données - Intégration jQuery - Sites de réfèrences