Introduction

Cet article est un petit tutoriel sur Django. Il reprend le contenu de l'atelier du 17 décembre 2013. Il ne vise en aucun cas à être complet, il sert simplement de rattrapage pour ceux ou celles qui ont raté la première session et veulent participer à la deuxième.

  • Mais au fait, c'est quoi Django ?

C'est un framework web écrit en Python. Ça sert donc à écrire des applications web en Python. C'est un framework libre (sinon il n'aurait pas sa place sur ce site) et il a l'avantage d'être assez simple à aborder et très complet.

  • Python 2 ou Python 3 ?

Les deux \o/ Django 1.6 est compatible avec Python 2.6, 2.7 et 3.3. Pour ce tutoriel, on va partir avec Python 3.3 et Django 1.6.

  • Et qu'est ce que l'on va faire ?

Une application presque utile ! Ce sera une simple todolist.

  • Un lien vers un tutoriel complet et mieux :

https://docs.djangoproject.com/fr/1.6/intro/tutorial01/

Installation

Sous Linux et sûrement sous les BSD

Il y a plusieurs manières d'installer Django. On peut l'installer via les paquets, en global, ou dans un environnement virtuel.

Un environnement virtuel, c'est un environnement python local indépendant du système. On peut travailler avec plusieurs environnements sur une même machine. Ça sert à tester une nouvelle version d'une bibliothèque sans casser son système et c'est facile à reproduire sur une autre machine. Le soucis c'est que c'est indépendant du gestionnaire de paquets et il faut faire les mises à jour à la main (ça peut être un avantage quand on a pas encore testé la compatibilité avec une nouvelle version de django).

Python 3

Il faut commencer par installer Python 3, là le plus simple c'est de passer par votre gestionnaire de paquets. apt-get install python3-all sous Debian.

Pour tester si python 3 est installé : la commande python3 dans un shell doit lancer un shell python.

pip et virtualenv

Ensuite on installe pip, qui est un gestionnaire de paquets pour python. Sous debian il faut installer le paquet python3-pip. Sinon, l'installation manuelle est détaillée sur le site officiel.

Ensuite on installe virtualenvwrapper. En root, il faut lancer la commande pip3 install virtualenvwrapper (c'est aussi possible d'installer en local, pip3 --help vous en dira plus).

Création de l'environnement virtuel

Maintenant, on peut enfin créer l'environnement virtuel. Dans un shell (pas root), tapez la commande suivante :

virtualenv-3.3 envdjango

Cela crée un répertoire envdjango qui contient des répertoires bin/, include/ et lib/ avec une copie de python3 dedans. C'est dans ce répertoire que sera installé django.

Pour utiliser votre environnement virtuel, il faut exécuter la commande source envdjango/bin/activate. Si tout ce passe bien le prompt de votre shell doit être préfixé par (envdjango).

Pour quitter l'environnement virtuel, il suffit d'exécuter la commande deactivate.

Installation de django

Normalement si vous tapez la commande python3 -c "import django", vous avez le droit à un beau message d'erreur :

(envdjango)$ python3 -c 'import django'
Traceback (most recent call last):
  File "<string>", line 1, in <module>
ImportError: No module named 'django'

C'est normal, on a pas installé django, et c'est super simple : pip install django. Et tant que l'on y est, on va installé ipython (un shell python plus agréable à utilisé) : pip install ipython.

Si django est bien installé, la commande python3 -c "import django" ne doit pas renvoyer de message d'erreur et la commande django-admin.py doit afficher un beau message d'aide.

Sous Windows

C'est un peu pareil que sous Linux mais la console est pourrie. Du coup, on oublie l'histoire d'environnement virtuel et on installe que python et django.

Python

Il faut récupérer l'installateur sur le site officiel. Lors de l'installation, il est recommandé d'activer le rajout de python.exe dans le path.

Django

Il faut ouvrir une console (cmd.exe). Puis il faut taper quelque chose comme C:\\Python3.3\Scripts\easy_install.exe django. Une fois installer, la commande python3.exe C:\\Python3.3\Scripts\django-admin.py doit afficher un beau message d'aide.

Sous MacOS

Aucune idée, mais ça doit être comme sous Linux. Normalement il y a déjà un interpréteur Python d'installé (2 ou 3). pip et virtualenv doivent s'installer facilement.

Sous Hurd ou un autre truc exotique

Commencez par avoir du réseau qui marche et un clavier fonctionnel…

Éditeur de texte

Bien entendu vous aurez besoin d'un éditeur de texte. Il n'est pas nécessaire de sortir l'artillerie lourde (eclipse/pydev, c'est plus lourd qu'un maillet de Kaori). Un éditeur avec de la coloration syntaxique pour python, html, css et javascript fera l'affaire. Vim est parfait.

Il est important de configurer son éditeur pour qu'il indente avec 4 espaces (à mort les tabulations).

Création d'un projet

Une fois l'environnement près, on peut commencer à coder.

On va commencer par créer un projet django avec la commande django-admin.py startproject lenomduprojet. Cette commande crée un répertoire lenomduprojet dans le répertoire courant. Il est recommandé de l'exécuter dans un répertoire extérieur à l'environnement virtuel. Notre projet va s'appeler tracker, et l'on exécute la commande :

django-admin.py startproject tracker

On obtient un répertoire qui contient un fichier manage.py et un répertoire tracker :

tracker/
+--- manage.py
+--- tracker/
|   +--- wsgi.py
|   +--- urls.py
|   +--- settings.py
|   +--- __init__.py

Le fichier manage.py est un script qui permet de lancer les commandes django relatives à notre projet (création de la base de données, lancement du serveur de développement, etc.).

Le sous-répertoire tracker contient les fichiers de paramétrage du projet. Il contient 4 fichiers :

  • __init__.py : ce fichier vide permet de transformer le dossier tracker en un package python
  • settings.py : ce fichier contient les paramètres du projets :
    • la liste des applications installées (INSTALLED_APPS). Par défaut il s'agit des applications communes à tout projet django (tout ce qui concerne l'authentification, l'interface d'administration et les fichiers statiques).
    • les accès à la base de données (par défaut une base sqlite) (DATABASES)
    • D'autres paramètres comme la langue par défaut (LANGUAGE_CODE)
  • urls.py : ce fichier permet de décrire sous forme d'expressions régulières à quelles fonctions python sont associées les urls.
  • wsgi.py : ce fichier sert au déploiement sous apache (avec mod_wsgi) ou sous nginx+uwsgi ou nginx+gunicorn et d'autres serveurs frontaux.

Premières utilisations de manage.py

Le script manage.py permet de lancer les commandes de gestion de notre projet. On va commencer par créer la base de données. En effet, même si on n'a pas encore définit nos modèles de données, django a besoin d'une base pour gérer les utilisateurs.

La création de la base se fait avec la commande python manage.py syncdb. Cette commande vous demande si vous voulez créer un super-utilisateur :

(envdjango)$ python manage.py syncdb
Creating tables ...
Creating table django_admin_log
Creating table auth_permission
Creating table auth_group_permissions
Creating table auth_group
Creating table auth_user_groups
Creating table auth_user_user_permissions
Creating table auth_user
Creating table django_content_type
Creating table django_session

You just installed Django's auth system, which means you don't have any superusers defined.
Would you like to create one now? (yes/no): 

Répondez oui (yes) et répondez aux questions posées. Le super utilisateur est un utilisateur qui au niveau de l'interface d'administration possède tous les droits. Dans le cadre d'un environnement de développement, l'adresse mail n'a pas besoin d'être valide et vous pouvez modifier à tout moment le mot de passe avec la commande python manage.py changepassword.

Une fois cette commande exécutée, on peut constater la création d'une base sqlite (fichier db.sqlite3).

Pour gagner du temps, vous pouvez rendre le fichier manage.py exécutable (chmod +x manage.py). Vous n'aurez plus qu'à taper ./manage.py nomdelacommande.

Création d'une application

Une fois le projet créé, on peut commencer à coder sa première application. Dans django, un projet est composé de plusieurs applications. Celles-ci peuvent être fournie par django, des tierces-parties ou par vous même. Une application regroupe un ensemble cohérent de modèles et de vues. Pour notre projet nous allons créer une seule application, todolist qui contiendra l'ensemble du code. Mais si nous voulions rajouter un module de commentaire ou un blog, cela se ferait par l'ajout d'applications séparées.

Pour créer l'application, il faut lancer la commande ./manage.py startapp todolist.

Cela crée un dossier todolist qui contient les fichiers suivant :

  • __init__.py, ce fichier vide dit que todolist est un package python
  • models.py, ce fichier sert à définir les modèles de données
  • admin.py, ce fichier sert à définir quels modèles sont exposés dans l'interface d'administration
  • views.py, ce fichier contient les vues qui répondront aux requêtes http
  • tests.py, ce fichier contient les tests unitaires et fonctionnels que l'on peut (et doit) écrire

Il faut encore enregistrer notre application. Pour cela il faut éditer le fichier tracker/settings.py et rajouter 'todolist' à la liste des applications installées (après les applications django) :

# Application definition

INSTALLED_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'todolist',
)

Le modèle de données

ORM

Django c'est cool en autre parce qu'il fournit un ORM qui s'intègre très bien dans le framework (ou le contraire).

Un ORM, pour Object-Relational Mapping (mapping objet-relationnel) est une technique de programmation informatique qui crée l'illusion d'une base de données orientée objet à partir d'une base de données relationnelle en définissant des correspondances entre cette base de données et les objets du langage utilisé. On pourrait le désigner par « correspondance entre monde objet et monde relationnel ». Ça c'est la définition Wikipedia. En clair, cela signifie que l'on n'écrit pas de SQL, que ce soit pour la définition du schéma ou les requêtes. À la place, on écrit des classes Python et on attaque la base avec des méthodes sur les objets.

Ça a plusieurs avantages :

  • le même code est compatible avec toutes les bases de données supportées par Django (postgres, sqlite, oracle) et même MySQL
  • ça échappe automatiquement les requêtes SQL → pas d'injections SQL
  • c'est plus lisible que du SQL, surtout quand on n'a pas de jointure à écrire à la main
  • on n'a pas à écrire de code pour convertir les données

Définition de modèles

Attention, on va écrire du code ! On va écrire non pas une, non pas deux mais trois lignes dans un fichier !

Il faut éditer le fichier todolist/models.py. On constate qu'il y a déjà un import du module models de django. Il faut remercier les développeurs pour nous éviter l'effort de se souvenir où se trouve ce module.

On rajoute les lignes suivantes à la fin du fichier (on peut virer le commentaire qui commence par #):

class Item(models.Model):
    titre = models.CharField()
    description = models.TextField()

Ce qui donne pour le fichier complet :

from django.db import models

class Item(models.Model):
    titre = models.CharField()
    description = models.TextField()

Explication :

On définit une classe Item qui hérite de models.Model. Notre application va gérer des listes de tâches à faire. J'ai donc choisi Item comme nom. Cela correspond grosso modo au nom de la table. Il vaut mieux nommer les classes en CamelCase (convention Python) et au singulier (django utilise le terme au singulier dans l'interface d'administration).

Cette classe hérite de la classe models.Model (classe Model du module models). C'est la classe de base des modèles dans django.

Ensuite on rajoute deux attributs d'une manière un peu particulière. Ces attributs, titre est description correspondent aux colonnes de la table. Le premier, titre, est définit comme un CharField, ce qui correspond à un champ qui a une longueur maximale fixe. Le deuxième, description, est un TextField, c'est-à-dire une chaîne de caractères de taille variable sans longueur maximale (enfin ça dépend sûrement de la base de données). Dans le vocabulaire django, on parle de Field.

En quelque sorte, on définit les colonnes comme attributs de la classe. Cela peut surprendre les habitués au langage Python. En fait c'est la classe Model qui fait tout grâce a une jolie métaclasse (si vous ne comprenez pas c'est normal). Ce qu'il faut noter, c'est que l'on n'écrit pas de constructeur, la classe Model en définit un dont nous verrons l'utilisation par la suite.

Pour rappel, il faut que les lignes commençant par titre et description soit indentées (de 4 espaces par convention qui vous évitera des soucis lors de vos futurs copier-coller).

Voilà notre premier modèle est presque fini. Il ne reste plus qu'à mettre à jour le schéma de la base de données avec la commande :

./manage.py syncdb

Oups, un message d'erreur en rouge apparaît :

CommandError: One or more models did not validate:
todolist.item: "titre": CharFields require a "max_length" attribute that is a positive integer.

Il nous dit que l'attribut "titre" de notre modèle Item n'est pas valide car il manque un attribut max_length. C'est la longueur maximale de notre chaîne de caractères. On va donc modifier le modèle :

from django.db import models

class Item(models.Model):

    titre = models.CharField(max_length=20)
    description = models.TextField()

Ici on a choisi (au pif) une longueur maximale de 20 caractères (on aurait pu aussi bien mettre 250). On peut constater que les options des colonnes sont données comme arguments nommés des fields. Chaque Field a des arguments, certains sont optionnels (comme la valeur par défaut) d'autres sont obligatoires (comme max_length pour les CharFields).

Bon, une fois notre petite erreur corrigée, on peut relancer la commande syncdb.

(envdjango)$ ./manage.py syncdb
Creating tables ...
Creating table todolist_item
Installing custom SQL ...
Installing indexes ...
Installed 0 object(s) from 0 fixture(s)

On remarquer dans la trace d'exécution que la table todolist_item a été créée. Son nom est dérivé du nom de l'application et de celui du modèle.

On n'a pas définit de colonne contenant une clé primaire. En fait django définit automatiquement une colonne id si on ne spécifie pas de clé primaire. C'est souvent une bonne idée de garder cette colonne. Ça ne coûte pas grand chose et ça facilite certaines utilisations de l'ORM.

Les curieux peuvent taper la commande ./manage.py sql todolist pour voir le schéma SQL généré par django :

(envdjango)$ ./manage.py sql todolist
BEGIN;
CREATE TABLE "todolist_item" (
    "id" integer NOT NULL PRIMARY KEY,
    "titre" varchar(20) NOT NULL,
    "description" text NOT NULL
)
;

COMMIT;

Utilisation

Une fois la table créée, on peut lui ajouter du contenu. L'un des points forts de Python est son interpréteur interactif qui permet de rapidement tester des morceaux de code. Django fournit une commande qui lance un interpréteur avec la connexion à la base de données établie :

./manage.py shell

Cette commande utilise IPython, un interpréteur Python amélioré (complétion avec tab, possibilités d'exécutés des commandes shell en les préfixant par ! et bien d'autres choses) si celui-ci est installé.

Une fois le shell lancé, on va commencer par importer le modèle Item :

In [1]: from todolist.models import Item

(Si IPython n'est pas installé, le prompt est >>> )

Nous allons maintenant voir comment on crée un objet Item et l'enregistrer dans la base. Il y a deux manières, soit on lance une commande qui créé et enregistre en une étape, soit on le fait en deux lignes distinctes.

Pour créer et enregistrer en une étape :

In [2]: mon_item = Item.objects.create(titre="un titre", description="une description")

Explications :

  • On ne passe pas directement par le constructeur de la classe Item mais par l'attribut objects de celle-ci. Cet attribut est automatiquement rajouté aux modèles. Il sert à séparer les fonctions d'accès à la base aux méthodes propres aux instances des modèles. On parle de Manager.
  • Chaque Manager a une méthode create qui permet de créer et sauvegarder un objet.
  • La méthode create accepte comme arguments nommés (argument=valeur) les attributs que l'on a définit dans la classe Item.

Une fois l'objet créé, on peut le manipuler comme n'importe quel objet Python :

  • Accès à l'attribut titre :
In [4]: mon_item.titre
Out[4]: 'un titre'
  • Accès à l'id (la valeur est fournie par la base de données) :
In [5]: mon_item.id
Out[5]: 1
  • Modification de la description :
In [6]: mon_item.description = "une autre description"

À ce moment là, la modification de la description n'a pas été enregistrée en base. Pour cela il faut appeler la méthode save() :

In [7]: mon_item.save()

Là deuxième méthode passe par le constructeur de la classe Item et l'appel à la méthode save()

In [8]: mon_item = Item(titre="Regarder FLCL", description="Ceci est un super conseil en cadeau pour les lecteurs de ce tutoriel")

À ce moment là l'objet n'est pas enregistré est son id vaut None (l'équivalent de null en Python) :

In [9]: mon_item.id is None
Out[9]: True

On sauvegarde l'objet avec save() :

In [10]: mon_item.save()

Il est alors enregistré et a un id :

In [11]: mon_item.id
Out[11]: 2

La récupération d'éléments passe aussi par le manager (objects).

  • On peut récupérer tous les éléments d'une table avec la méthode all() :
In [12]: Item.objects.all()
Out[12]: [<Item: Item object>, <Item: Item object>]

On obtient un objet similaire à une liste qui est itérable (comprendre utilisable dans une boucle for) :

In [13]: for item in Item.objects.all():
   ...:     print(item.titre)
   ...:     
un titre
Regarder FLCL

On peut constater que l'on récupère automatiquement des objets Item. Django fait le travail de conversion pour nous.

  • Récupérer tous les éléments peut être coûteux et souvent on veut travailler avec des éléments qui répondent à certains critères. Pour cela il faut passer par la méthode filter() du manager. Cette méthode accepte des arguments nommés un peu particulier.

Il est possible de filtrer sur la valeur d'un attribut :

In [14]: Item.objects.filter(titre="un titre")
Out[14]: [<Item: Item object>]

On obtient une liste similaire à celle de la méthode all()

In [16]: item = Item.objects.filter(titre="un titre")[0]

In [17]: item.description
Out[17]: 'une autre description'

On peut donc utiliser accéder aux éléments avec la notation [] de Python.

On peut également filtrer suivant certaines propriétés des attributs. Par exemple, on peut vouloir filtrer sur des nombres supérieurs à une valeur. Le filtrage se fait en donnant un argument construit suivant le format nomattribut__filtre=valeur. Il y a par exemple un filtre startswith qui permet de filtrer les chaînes de caractères commençant par une sous-chaîne donnée. Dans notre cas cela donne :

In [18]: flcl = Item.objects.filter(titre__startswith="Reg")[0]
In [19]: flcl.description
Out[19]: 'Ceci est un super conseil en cadeau pour les lecteurs de ce tutoriel'

(Pour les amateurs de Python, les slices sont supportés mais pas les index négatifs)

L'objet retourné par filter n'est pas une liste mais un queryset. Cet objet permet d'enchaîner les filtrages. Il faut noter que la requête SQL n'est pas exécutée tout de suite mais au moment de l'évaluation (affichage dans un shell, accès a un élément avec [], itération dans une boucle for...).

In [20]: queryset = Item.objects.filter(titre__startswith="Reg")

In [21]: type(queryset)
Out[21]: django.db.models.query.QuerySet

Ici aucune requête n'a été exécutée.

Il y a aussi un filtre contains qui filtre si une chaîne contient une sous-chaîne (icontains pour un filtrage insensible à la casse)

In [22]: queryset.filter(description__contains="super")
Out[22]: [<Item: Item object>]

Dans le cas où on est sûr qu'une requête ne retourne qu'un élément, on peut utiliser la méthode get(). Elle prend les mêmes paramètres que filter et peut être appliquée après un appel à filter.

On peut par exemple l'utiliser pour obtenir l'item dont l'id vaut 1 :

In [23]: item = Item.objects.get(id=1)

In [24]: item.titre
Out[24]: 'un titre'

In [25]: item = Item.objects.filter(description__contains="super").get(titre="Regarder FLCL")

In [26]: item.id
Out[26]: 2

Je vous encourage à tester l'utilisation de filter et get. Il y a aussi exclude() pour exclure des éléments. Elle prend les mêmes arguments que filter.

Interface d'administration

Il est temps de lancer un serveur web. Django fournit un serveur web de développement. Il n'est donc pas nécessaire de configurer un serveur apache pour tester son code.

Pour lancer le serveur, il faut taper la commande ./manage.py runserver. Ensuite il ne reste plus qu'à ouvrir la page http://127.0.0.1:8000/ et admirer. Même si django nous dit que l'on a rien fait, on a en réalité une interface d'administration fonctionnelle.

Ouvrez la page http://127.0.0.1:8000/admin/ et connectez vous avec votre compte super-utilisateur créé avec le premier syncdb.

Par défaut, seule l'application de gestion des comptes utilisateurs est enregistrée. Vous pouvez dès lors rajouter d'autres utilisateurs et gérer les permissions.

Pour activer l'édition des items, il faut modifier le fichier todolist/admin.py :

from django.contrib import admin

from .models import Item

admin.site.register(Item)

On sauvegarde, on actualise la page et sans relancer le serveur et tada on peut éditer des items. Un clic sur item de la liste permet de le modifier et il y a un bouton "add item" à droite pour en rajouter.

Cependant, on peut trouver illisible la liste. Pour la rendre plus lisible il faut modifier le modèle Item et définir la méthode __str__. C'est la méthode standard en Python qui est appelée pour convertir un objet en chaîne de caractères (avec la fonction str()).

todolist/models.py :

from django.db import models

class Item(models.Model):

    titre = models.CharField(max_length=20)
    description = models.TextField()

    def __str__(self):
        return "Item({}, {})".format(self.id, self.titre)

__str__ doit retourner une chaîne de caractères. Ici, j'utilise la méthode format pour obtenir des valeurs comme Item(2, Regarder FLCL). J'ai mis l'id dans la chaîne pour faciliter le debuggage. L'utilisation du shell sera plus agréable (il faut le relancer pour que les modifications de code prennent effet) :

In [1]: from todolist.models import Item

In [2]: Item.objects.all()
Out[2]: [<Item: Item(1, un titre)>, <Item: Item(2, Regarder FLCL)>]

Voilà vous êtes parés pour assister à la deuxième session de l'atelier ou continuer la suite du tutoriel quand elle sera mise en ligne.

Une première vue

Moteur de templates

Source: Attach:tracker.tar.gz

© 2006-14 Association Actux - Documentation sous licence GFDL - Conception & design: Imaginair.net - Logo: Zazo0o - Propulsé par PmWiki