Python African Tour Dakar - 06-10 juillet 2009
Agence Universitaire de la Francophonie
| Auteurs: | Ousmane Wilane <ousmane@wilane.org> |
|---|---|
| Version: | $Id: django-course.rst 5 2009-07-06 06:15:01Z wilane $ |
Qui sommes nous:
Je suis:
from django.db import models class Facture(models.Model): numero = models.CharField(max_length=50) montant = models.FlotField() date_facture = models.DateField()
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})
from django.conf.urls.defaults import *
import views
urlpatterns = patterns('',
(r'^dernieres/$', views.dernieres_factures),
)
<html>
<head><title>Factures</title></head>
<body>
<h1>Factures</h1>
<ul>
{% for facture in listes_factures %}
<li>{{ facture.nummero }}</li>
{% endfor %}
</ul>
</body>
</html>
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. >>>
archive depuis http://www.djangoproject.com/download/
- tar xzvf Django-1.1-beta-1.tar.gz
- cd Django-*
- sudo python setup.py install
svn co http://code.djangoproject.com/svn/django/trunk/ django-svn-trunk
- cd django-svn-trunk
- sudo python setup.py install
>>> import django >>> django.VERSION (1, 1, 0, ‘beta’, 1)
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/
Démarrer un projet:
django-admin.py startproject monprojet
Échafaudage:
mysite/ __init__.py manage.py settings.py urls.py
- 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
from django.http import HttpResponse
def salut(request):
return HttpResponse("Bonjour PAT")
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 = ''
...
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
...
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
...
Créer une vue qui affiche la date et l'heure: Contenu dynamique.
Vue:
from django.http import HttpResponse import datetime def date_actuelle(request): nunc = datetime.datetime.now() html = "<html><body>Hey PAT! Il est %s.</body></html>" % nunc return HttpResponse(html)
url:
urlpatterns = patterns('',
('^ladate/$', date_actuelle),
)
(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 = "<html><body>Dans %s heure(s), il sera %s.</body></html>" % (offset, dt)
return HttpResponse(html)
| 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) |
| 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 |
(r'^facture/list/$', 'views.facturelist'), (r'^facture/new/$', 'views.add_a_facture'), (r'^facture/new/(?P<extra>\d+)$', 'views.add_x_facture'), (r'^facture/edit/(?P<facture_id>\d+)$', 'views.editer'),
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')))
Utilisation de chaîne codée en dur dans les vues précédant, ce qui implique:
<html>
<head><title>Avis de commande</title></head>
<body>
<h1>Avis de commande</h1>
<p>Bonjour {{ nom_complet }},</p>
<p>Nous vous remercions pour la commande passée par {{ company }}.
Vous serez livré le {{ date_livraison|date:"D d M Y" }}.</p>
<p>Rappel des éléments commandés:</p>
<ul>
{% for element in liste_elements %}
<li>{{ element }}</li>
{% endfor %}
</ul>
{% if garantie %}
<p>Les informations relatives à la garantie seront
inclus dans le colis.</p>
{% else %}
<p>Vous n'avez pas souscris à une garantie.</p>
{% endif %}
<p>Cordialement,<br />{{ company }}</p>
</body>
</html>
La mise en oeuvre d'une template est très simple:
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.'
>>> print t <django.template.Template object at 0xb7ea7e2c> >>>
>>> id(t) 3085598252L >>> int(0xb7ea7e2c) 3085598252L
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.
Lorsque le système de template rencontre un . il procède à sa résolution dans l'ordre suivant:
Les tags permettent de donner des ordres au système de template
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 %}
<p>Génial c'est le Weekend!</p>
{% endif %}
{% if on_est_le_weekend %}
<p>Génial c'est le Weekend!</p>
{% else %}
<p>Bon, il va falloir travailler.</p>
{% endif %}
Sont considérés comme fausse par Python comme par le système de template les objets suivants dans un contexte booléen:
Il permet d'itérer parmi les éléments d'une séquence
<ul>
{% for facture in listes_factures %}
<li>{{ facture.numero }}</li>
{% endfor %}
</ul>
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 %}
<p>{{ facture.numero }}</p>
{% endfor %}
{% else %}
<p>Aucune facture.</p>
{% endif %}
Définissez une liste de facture (liste de dictionnaire) et utiliser la en console pour le rendu de la template ci-dessus.
Parce que le motif précédant est courant, un tag empty a été introduit:
{% for facture in listes_factures %}
<p>{{ facture.numero }}</p>
{% empty %}
<p>Aucune facture.</p>
{% endfor %}
Utilisez {{ forloop.last }} pour séparer une liste de mots par des virgules et finir la liste par un point.
{% ifequal type_facture 'pro-forma' %}
<h1>Factures Pro-forma</h1>
{% else %}
<h1>Facture</h1>
{% endifequal %}
{% ifequal variable True %}
{% ifequal variable [1, 2, 3] %}
{% ifequal variable {'cle': 'valeur'} %}
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 permettent d'altérer les valeurs des objets avant de les afficher. Il utilisent le caractère |
from django.http import HttpResponse import datetime def date_actuelle(request): nunc = datetime.datetime.now() html = "<html><body>Hey PAT! Il est %s.</body></html>" % nunc return HttpResponse(html)
from django.http import HttpResponse
from django.template import Template, Context
import datetime
def date_actuelle(request):
t = Template("<html><body>Hey PAT! Il est
{{ nunc }}.</body></html>")
html = t.render(Context({'nunc': datetime.datetime.now()}))
return HttpResponse(html)
L'approche de la page précédente ne règle pas les problème évoqués liés à cette approche.
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.
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)
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', )
os.path.join(os.path.dirname(__file__), 'templates')
.replace('\\','/'),
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)
Faite référence à une template qui n'existe pas avec get_template et commentez l'erreur.
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})
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.
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 %}
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="fr">
<head>
<title>{% block titre %}{% endblock %}</title>
</head>
<body>
<h1>Mon site horloge</h1>
{% block contenu %}{% endblock %}
{% block pied_de_page %}
<hr>
<p>Merci de votre visite.</p>
{% endblock %}
</body>
</html>
{% extends "base.html" %}
{% block titre %}L'heure actuelle{% endblock %}
{% block contenu %}
<p>Il est {{ nunc }}.</p>
{% endblock %}
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})
Cette approche pose un nombre de problèmes:
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})
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 = ''
| Configuration | Base de données | Pilote |
| postgresql | PostgreSQL | psycopg2 |
| mysql | MySQL | MySQLdb |
| sqlite3 | SQLite | aucun pour Python >= 2.5 |
| oracle | Oracle | cx_oracle |
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() >>>
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
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()
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)
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', )
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
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
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
)
;
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
)
;
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;
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$
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()
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
[<Client: Client object>, <Client: Client object>]
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)
Client.objects.filter(nom='AUF BAO') [<Client: AUF BAO Dakar>] >>>
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 <django.db.models.sql.query.BaseQuery object at 0x8fae14c> >>> q.query.as_sql()
nom__contains permet de recherche des occurrences dont le nom contient
q = Client.objects.filter(nom__contains='BAO') q.query.as_sql()
Les QuerySet peuvent être chaînés
q = Client.objects.filter(ville='Dakar').order_by('nom')
q
[<Client: AUF BAO Dakar>, <Client: PAT Dakar>]
Utilisez la QuerySet Client.objects.filter(ville='Dakar').order_by('nom') et commentez le résultat.
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)
class Meta: ordering = ['nom']
Ajouter un ordre au modèle par la méthode précédente et testez le résultat.
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)
admin.site.register(Client, ClientAdmin)
Les drapeaux:
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')
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)
<html>
<head>
<title>Recherche</title>
</head>
<body>
<form action="/chercher/" method="get">
<input type="text" name="q">
<input type="submit" value="Chercher">
</form>
</body>
</html>
from factures import views
urlpatterns = patterns('',
# ...
(r'^rech-form/$', views.form_recherche),
(r'^chercher/$', views.rechercher),
# ...
)
def affichez_meta(request):
values = request.META.items()
values.sort()
html = []
for k, v in values:
html.append('<tr><td>%s</td><td>%s</td></tr>' % (k, v))
return HttpResponse('<table>%s</table>' % '\n'.join(html))
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})
from django import forms class FormRecherche(forms.Form): q = forms.CharField()
>>> from django import forms
>>> class RechForm(forms.Form):
... q = forms.CharField()
...
>>> RechForm()
<__main__.RechForm object at 0xb783f1cc>
>>> f= RechForm()
>>> print f
<tr><th><label for="id_q">Q:</label></th><td>
<input type="text" name="q" id="id_q" />
</td></tr>
>>> print f.as_ul()
<li>
<label for="id_q">Q:</label>
<input type="text" name="q" id="id_q" />
</li>
>>> print f.as_p()
<p>
<label for="id_q">Q:</label>
<input type="text" name="q" id="id_q" />
</p>
>>> f= RechForm({'q':'AUF'})
>>> print f
<tr>
<th>
<label for="id_q">Q:</label>
</th>
<td>
<input type="text" name="q" value="AUF" id="id_q" />
</td>
</tr>
>>> f = RechForm()
>>> f.is_bound
False
>>> f = RechForm({'q':'PAT'})
>>> f.is_bound
True
>>> f.is_valid()
True
>>> 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.']
>>> f = RechForm({'q':'PAT'})
>>> f.is_bound
True
>>> f.is_valid()
True
>>> f.cleaned_data
{'q': u'PAT'}
>>>
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})
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