Manuel Django Carrots

Voici un manuel pour apprendre à utiliser Python et Django. Même si vous ne savez pas encore programmer, en suivant le manuel et en travaillant sur chacun des exercices, vous devriez être capable de créer votre propre site web.

Ce manuel fait partie de l’atelier “Django Carrots”, organisé par l’association Geek Girls Carrots. Vous pouvez l’utiliser pour organiser vos propres ateliers ou pour pratiquer seul.

Si vous décidez d’utiliser ce manuel, nous vous serions reconnaissants de bien vouloir nous contacter (bien que cela reste optionnel).

Sommaire

Installation

Pendant nos ateliers, nous allons utiliser un interpréteur de langage Python, en version 3.5+. Vous trouverez ci-dessous quelques conseils pour vérifier si vous avez déjà l’interpréteur avec la “bonne version” ou bien, dans le cas contraire, pour l’installer ainsi que quelques outils complémentaires.

Windows

Vous pouvez télécharger une version Windows de l’interpéteur Python directement depuis python.org. Une fois que vous avez récupéré le fichier d’installation, portant l’extension .msi, il vous suffit de l’exécuter et de suivre les instructions. Retenez bien le chemin d’installation - c’est-à-dire le dossier dans lequel vous avez installé Python. Vous aurez besoin de cette information par la suite, au moment de l’installation des outils.

Linux (Ubuntu, Fedora, etc.) ou Mac OS X

Pour vérifier la version de Python installée sur votre système, entrez la commande suivante dans votre terminal :

$ python --version
Python 2.7.12+
$ python3 --version
Python 3.5.2+

Si la commande python n’est pas disponible, ou si une version inférieure est détectée :

Ubuntu

Tapez la commande suivante:

sudo apt install python3.5 python3.5-dev python3-venv
Fedora

Entrez la commande suivante:

sudo yum install python3.5
Mac OS X

Téléchargez et installez la version de Python correspondant à votre version d’OS X depuis python.org .

Autres

Utilisez le gestionnaire de paquets adapté à votre distribution. S’il n’existe pas de système adéquat, ou si vous n’y trouvez pas Python, vous pouvez l’installer en le compilant depuis les fichiers sources disponibles sur le site python.org. Un compilateur sera alors requis, ainsi que la librairie readline.

Officieusement, nous partons du principe que des utilisateurs de distributions moins populaires (mais tout aussi valables !) réussiront sans problème à accomplir cette tâche :)

Outils complémentaires

Console Windows

Nous allons effectuer la majeure partie de notre travail en saisissant des lignes de commande dans la console Windows. Pour la démarrer, appuyez sur les touches Win+R. Dans la fenêtre qui apparaît alors, tapez cmd puis cliquez sur OK. Une nouvelle fenêtre fait alors son apparition, avec du texte blanc sur fond noir :

Microsoft Windows [Version 6.1.7601]
Copyright (c) 2009 Microsoft Corporation. All rights reserved.


C:\Users\Name>

Le texte peut être légèrement différent selon la version de Windows que vous utilisez.

C:\Users\Name> est une invite de commande, que l’on désigne aussi parfois par le terme anglophone “prompt”. Cette invite nous informe du répertoire dans lequel nous nous trouvons actuellement et attend la saisie d’une commande.

Note

Petite précision de vocabulaire : dans le cadre de notre atelier, nous emploierons souvent le terme “répertoire”. C’est un synonyme de ce que les versions récentes de Windows appellent “dossier”, mais il est davantage utilisé dans le contexte du développement informatique.

Plus loin dans cette documentation, nous abrègerons souvent C:\Users\Name> en ~$, qui est une forme canonique d’invite de commande dans les environnements Linux et Mac OS X.

À l’aide de ligne de commande, vous pouvez vous déplacer dans le contenu de votre disque dur, comme vous le feriez via l’icône Mon ordinateur. Il suffit pour cela de saisir des commandes et d’appuyer sur Entrée. Les commandes suivantes seront utilisées au cours de l’atelier :

dir
Affiche le contenu du répertoire courant. Par exemple, si l’invite de commande indique C:\Users\Name, la commande dir affiche le contenu de votre répertoire personnel.
cd directory
Change le répertoire courant. Par exemple, si vous êtes dans le répertoire C:\Users\Name, le fait de saisir la commande cd Documents vous permettra d’accéder au répertoire contenant vos documents. Pour vous en convaincre, tapez alors la commande dir, vous verrez alors des fichiers familiers. La commande cd.. vous permet de remonter au répertoire supérieur dans l’arborescence.
mkdir directory
Crée un nouveau répertoire.
Environnement virtuel

Vous devez maintenant choisir le répertoire dans lequel installer votre environnement virtuel. Celui-ci va vous permettre d’isoler votre travail des autres parties du système. Par exemple, vous pouvez choisir votre répertoire personnel.

Sous Windows 7, le chemin du répertoire personnel de l’utilisatrice Yara est le suivant : C:\Users\Yara\. Vous êtes libre de choisir un répertoire différent, mais il est important de garder celui-ci en mémoire. En outre, il doit être facilement accessible car nous allons l’utiliser très souvent.

Par exemple, si votre répertoire personnel est C:\Users\Yara, la ligne de commande à saisir sera la suivante :

:: Windows
C:\Users\Yara> C:\Python35\python -m venv workshops
# Linux or Mac
~$ python3.5 -m venv workshops

Après ces manipulations, un nouveau répertoire nommé workshops est présent dans votre répertoire personnel, contenant ce que l’on appelle un “environnement virtuel”. Il convient maintenant d’activer celui-ci.

:: Windows
C:\Users\Yara> workshops\Scripts\activate
# Linux or Mac
~$ source workshops/bin/activate

La commande python, dès lors, permet de lancer la version adéquate de l’interpréteur Python, il n’est donc pas nécessaire de saisir le chemin complet vers celui-ci.

Assurez-vous maintenant que votre terminal est bien configuré :

:: Windows
(workshops) C:\Users\Yara>where python
C:\Users\Yara\workshops\Scripts\python.exe
...

(workshops) C:\Users\Yara>where pip
C:\Users\Yara\workshops\Scripts\pip.exe
...

(workshops) C:\Users\Yara>python --version
3.5.3
# Linux or Mac
(workshops) ~$ which python
/home/yara/workshops/bin/python
(workshops) ~$ which pip
/home/yara/workshops/bin/pip
...

(workshops) ~$ python --version
3.5.2+

Note

Il est possible que la commande pip soit déjà disponible sur votre système. Dans ce cas, il convient de vérifier que la version de pip est la bonne, avec la commande pip --version. Elle peut être exécutée des façons suivantes :

pip --version
pip3 --version
pip3.5 --version

Ceci vous indique votre version de pip ainsi que le chemin vers le répertoire contenant votre environnement virtuel.

Si vous ne trouvez pas pip ou si la commande which pip (ou bien where pip sous Windows) vous signale un problème, vous devez peut-être réinstaller pip :

python -m pip uninstall pip
python -m ensurepip

Si vous trouvez la commande ``pip`` dans une version inférieure à ``9.x``, vous pouvez la mettre à jour à l'aide de la commande :

.. code-block:: sh

        pip install -U pip
En résumé

Pour installer un nouvel environnement virtuel :

:: Windows
C:\Users\Yara> C:\Python35\python -m venv workshops
# Linux or Mac
python3.5 -m venv workshops

Pour activer un environnement virtuel :

:: Windows
C:\Users\Yara> workshops\Scripts\activate
# Linux or Mac
source workshops/bin/activate

Pour vérifier la version de Python :

(workshops) ~$ python --version
3.5.2+
IPython

Si vous le souhaitez, vous pouvez installer le logiciel IPython, qui améliore l’aspect et le confort d’utilisation de l’interpréteur Python. Pour cela, saisissez la commande suivante une fois votre environnement virtuel activé :

(workshops) ~$ pip install ipython

Introduction à Python

Commençons par lancer l’interpréteur Python que nous avons installé lors du chapitre précédant:

(workshops) ~$ python
Python 3.5.2+ (...)
Type "copyright", "credits" or "license" for more information.

>>>

Avant d’entrer python, nous entrions nos commandes sur la ligne de commande du système d’exploitation.

L’invite de ligne de commande (aussi appelée “prompt”) était ~$. Une fois la commande python entrée, l’invite de commande a changé et est désormais >>>. Cela signifie qu’à partir de maintenant, nous devons uniquement utiliser des commandes du langage Python.

Les commandes telles que cd ou mkdir ne fonctionneront pas. Il est temps d’apprendre un nouveau langage !

Nous ne taperons pas les signes >>> (de la même manière que pour ~$), l’interpréteur python le fera pour nous.

Commençons par additionner deux nombres:

>>> 2 + 2
4

Python est une super calculatrice:

>>> 6 * 7
42
>>> 1252 - 38 * 6
1024
>>> (3 - 5) * 7
-14
>>> 21 / 7
3.0
>>> 3**2
9
>>> 5 / 2
2.5

Faites bien attention lorsque vous entrez des nombres à virgules, utilisez des points comme séparateurs, et non pas des virgules. Les virgules nous seront utiles plus tard, pour définir des tuple, mais nous y reviendrons.

Le temps des présentations

Chaînes de caractères (Strings)

Les nombres ne sont pas suffisants pour communiquer de manière efficace, nous avons donc besoin d’utiliser des chaînes de caractères (aussi appelées strings).

Voici quelques exemples:

>>> "Bonjour tout le monde"
'Bonjour tout le monde'
>>> 'Foo Bar'
'Foo Bar'
>>> "Rock 'n' Roll"
"Rock 'n' Roll"
>>> 'Mon nom est "Camille"'
'Mon nom est "Camille"'

Vous pouvez également ajouter (on dit également concaténer) deux chaînes l’une à l’autre:

>>> 'Mon nom est ' + '"Camille"'
'Mon nom est "Camille"'

Ou elles peuvent être aussi multipliés par des nombres:

>>> 'oui ' * 3
'oui oui oui '

Une chaîne de caractères doit toujours commencer et se terminer par le même caractère. Il peut s’agir d’un guillemet simple ('), ou d’un guillemet double ("). Cela n’a aucun effet sur la valeur de la chaîne de caractères. Par exemple, si nous entrons "Batman", nous créons une chaîne de caractères Batman, les guillemets ne font pas partie de la chaîne de caractères, ils sont là uniquement pour indiquer qu’il s’agit d’une chaîne de caractères (string) (malheureusement, Python n’est pas suffisamment brillant pour se rendre compte de ça lui-même).

Afficher les chaînes de caractères

Mais, comment afficher ces chaînes de caractères d’une manière lisible ? Il est possible de le faire en utilisant la fonction print().

>>> print("Bonjour tout le monde !")
Bonjour tout le monde !

Il est aussi possible d’afficher différentes chaînes de caractères sur une même ligne, sans avoir à les ajouter l’une à l’autre. Elles seront séparées par des espaces:

>>> print("Bonjour, mon nom est", "Camille")
Bonjour, mon nom est Camille

La fonction print() peut être utilisée de différentes manières, puisqu’elle peut écrire à peu près n’importe quoi. Pour l’instant, le seul type de valeurs que nous connaissons sont les nombres:

>>> print(1)
1
>>> print(1, 2, 3)
1 2 3
>>> print("2 + 2 =", 2 + 2)
2 + 2 = 4

Et voilà, nous avons terminé d’utiliser la console interactive de Python pour l’instant. Pour sortir, entrez quit():

>>> quit()

Ou tapez Ctrl+D pour Linux ou Ctrl+Z pour Windows.

Fichiers de code source Python

Jusqu’à présent nous avons exécuté du code dans l’invite de commande interactive dans laquelle nous récupérons une réponse immédiate à nos commandes. C’est un bon moyen d’apprendre et d’expérimenter les éléments du langage. C’est pourquoi on y retourne.

Notre premier programme pourrait ressembler à ça:

print("Hi, my name is Lucas")

Enregistrez ce programme dans un fichier appelé visitingcard.py, et lancez-le depuis l’invite de commande python visitingcard.py:

(workshops) ~$ python visitingcard.py
Hi, my name is Lucas
(workshops) ~$

Un même programme peut contenir plusieurs commandes, chacune devant être sur une ligne séparée, par exemple:

print("Hi,")
print()

print("my name is Lucas")

print()
print("Bye.")

Nous pouvons ajouter des lignes vides où nous le souhaitons dans le fichier visitingcard.py pour améliorer la lisibilité. Ici, nous avons séparé l’entête du message d’avec son contenu et d’avec sa fin.

Envoyer du Morse grâce à Python

Essayons de créer un programme simple permettant d’envoyer des signaux en Morse.

En cas d’urgence et de panique liée à cet ordinateur, vous pouvez avoir besoin d’envoyer un signal de détresse.

Le signal de détresse, S.O.S (Save Our Soul) est en effet le signal le plus connu pour communiquer sa situation critique par la lumière ou par le son.

Il est utilisé par les bateaux et par les naufragé•e•s en situation de détresse.

Nous allons pour notre part commencer à afficher des signaux morse depuis le terminal. En commencant par notre fameux signal de détresse S.O.S.

Pour écrire un S.O.S en morse :

S.O.S va se traduire en morse par (... --- ...) soit 3 signaux courts suivis de 3 signaux longs.

Le S est donc égal à ... et le O à ---

Nous connaissons déjà la fonction print() (pour afficher des choses).

Nous allons donc créer un ‘’‘programme’‘’ qui stocke les deux lettres et leur équivalent en morse dans des ‘’‘variables’‘’.

Pour commencer, créez un fichier morse.py puis écrivez à l’intérieur le programme suivant :

print("... --- ...")

Lancez votre programme comme ceci:

$ python morse.py

Vous obtenez:

... --- ...

Comme vous le voyez notre programme a besoin de quelques améliorations :

Si quelqu’un souhaite envoyer un autre message que “S.O.S”, nous devons modifier le fichier morse.py.

Programmer c’est l’art de résoudre les problèmes, alors mettons nous au travail !

Cela va nous donner l’occasion d’apprendre de nouvelles fonctionnalités de Python.

Variables

Pour commencer nous aimerions bien rendre notre programme plus lisible, pour permettre au lecteur de savoir immédiatement quel code morse correspond à quelle lettre (... correspond à “S” et --- correspond à “O”).

C’est pourquoi nous donnons des noms à ces valeurs:

s = "..."
o = "---"
print(s, o, s)

Le résultat n’a pas changé:

... --- ...

Note

On utilise l’opérateur + qui sert à concaténer (coller) deux chaines de caractères entre elles. Tapez par exemple dans votre console Python:

>>> "Bonjour" + " a tous et a toutes"
'Bonjour a tous et a toutes'

Pour mieux comprendre le fonctionnement des variables, revenons à l’invité de commande (aussi nommée “console”) Python et essayons d’en créer quelques-unes :

>>> x = 42
>>> PI = 3.1415
>>> name = "Amelia"
>>> print("Quelques valeurs:", x, PI, name)
Quelques valeurs: 42 3.1415 Amelia

Une variable peut être vue comme une boite portant une étiquette :

  • Elle contient quelque chose (on dit que la variable contient une valeur)
  • Elle a un nom (comme l’inscription sur l’étiquette de la boite)

Deux variables (ayant des noms différent) peuvent contenir la même valeur :

>>> y = x
>>> print(x, y)
42 42

Ici les deux variables ont pour noms y et x (se sont les étiquettes sur les boites) et elles contiennent la même valeur : 42

On peut également modifier la valeur d’une variable (changer le contenu de la boite). La nouvelle valeur n’a pas besoin d’être du même type (nombre entier, nombre décimal, texte ...) que la précédente :

>>> x = 13
>>> print(x)
13
>>> x = "Scarab"
>>> print(x)
Scarab

Les variables sont indépendantes les unes des autres. Si on modifie la valeur de x, la valeur de y reste la même :

>>> print(y)
42

Nous pouvons également mettre le résultat de calculs ou d’opérations dans des variables et utiliser ensuite ces variables comme alias de la valeur dans d’autres calculs.

>>> s = "..."
>>> o = "---"
>>> aidez_moi = s + o + s
>>> print(aidez_moi)
...---...

À noter qu’une fois que la valeur est calculée, elle n’est pas modifiée :

>>> s = "@"
>>> print(aidez_moi)
...---...

Sauf si on demande à Python de la recalculer :

>>> aidez_moi = s + o + s
>>> print(aidez_moi)
@---@

Il est grand temps d’ajouter quelques commentaires à notre programme afin de faciliter la compréhension pour les lecteurs-trices (dont nous faisons partie).

Les commentaires nous permettent de rajouter du texte dans notre code Python. Les commentaires seront simplement ignorés par l’interpréteur Python lors de l’exécution du code.

En Python, un commentaire est tout ce qui se trouve entre un caractère # et la fin de la ligne:

# Code Morse du "S"
s = "..."

# Code Morse du "O"
o = "---"

aidez_moi = s + o + s # Code Morse pour "S.O.S"
print(aidez_moi)

Les fonctions

Notre programme n’est pas trop mal, mais si l’utilisateur-trice souhaite pouvoir envoyer plusieurs S.O.S, ou bien réutiliser ce bout de programme sans dupliquer trop de lignes, il va falloir empaqueter notre fonctionnalité dans ce qu’on appelle : une fonction.

Une fonction, c’est un mini moteur, un groupe d’instructions qui prend des données en entrée, execute les instructions (calcule) en utilisant (ou pas) les données en entrée et renvoie (ou pas) un résultat en sortie.

En Python, on définie une fonction comme suit:

def nom_de_la_fonction(argument1, argument2):
    # les instructions à executer
    # les instructions peuvent utiliser les arguments
    # pour retourner un résultat il faut utiliser le mot clef "return"
    # Si la fonction ne retourne rien, "return" est optionel
    return 42

Pour executer cette fonction (on dit “appeller” la fonction):

nom_de_la_fonction(argument1, argument2)

Pour récupérer la valeur de retour (résultat, sortie) de la fonction dans une variable:

ma_variable = nom_de_la_fonction(argument1, argument2)

Notre première fonction va se contenter d’imprimer notre signal de détresse.

On crée donc la fonction et on l’appelle à la fin du fichier:

def print_sos():
    s = "..."
    o = "---"
    print(s + o + s)

print_sos()

Note

On remarque qu’ici notre fonction ne prend aucun argument et ne renvoie aucune valeur (pas de mot clef return).

J’ai maintenant une fonction toute simple que je peux appeler à plusieurs reprises juste en duplicant l’appelle print_sos().

On peut aussi vouloir découper le signal et signifier que notre mot est terminé On va donc ajouter une nouvelle variable “stop” pour découper le mot et ainsi savoir quand le mot est terminé:

def print_sos():
    s = "..."
    o = "---"
    stop = "|"
    print(s + o + s + stop)

print_sos()

On peut encore simplifier notre code en remarquant que s contient 3 points et o contient 3 tirets. Il se trouve qu’on peut dupliquer une chaine de caractères en utilisant la syntaxe suivante:

>>> "hello" * 2
'hellohello'

On peut donc obtenir "..." en faisant "." * 3:

def print_sos():
    s = "." * 3
    o = "-" * 3
    stop = "|"
    print(s + o + s + stop)

print_sos()

Si maintenant on veut afficher plusieurs SOS, on peut écrire autant de fois que nécessaire print_sos() à la fin du fichier. Mais les informaticien•ne•s sont flemmard•e•s et la machine est là pour nous éviter de refaire la même chose et faire le travail répétitif et ennuyeux.

On a besoin de dire à la machine combien de fois on veut imprimer notre SOS. On va donc modifier la fonction et lui passer le nombre de fois que l’on veut imprimer le signal S.O.S en argument:

def print_sos(nb):
    s = "." * 3
    o = "-" * 3
    stop = "|"
    print((s + o + s + stop) * nb)

print_sos(3)

Ce qui donne:

$ python morse.py
...---...|...---...|...---...|

Pour qu’on puisse mieux lire les différents S.O.S et visualiser la fin de la phrase on va lui ajouter un retour à la ligne (\n):

def print_sos(nb):
    s = "." * 3
    o = "-" * 3
    stop = "|"
    print((s + o + s + stop + "\n") * nb)

print_sos(3)

Ce qui donne lorsqu’on execute morse.py:

$ python morse.py
...---...|
...---...|
...---...|

On a donc une fonction qui prend en entrée le nombre de fois que l’on veut emettre le signal SOS.

Pour l’instant elle ne se contente que d’afficher.

Si on veut rendre ce programme encore plus facile à utiliser, imaginons par exemple que nous ayons un robot qui transforme le . et le - en sons différents, ou une machine qui allume et éteigne une lampe plus ou moins longtemps ; on peut vouloir que cette fonction retourne la chaine de caractère du message à transmettre sans l’afficher dans le terminal.

On pourra ensuite mettre cette chaine dans une variable et la passer en argument à une autre fonction dont le rôle sera d’émettre du son ou d’allumer une lampe.

On va donc demander à la fonction de retourner (via return) le message morse et donc on va changer le nom de la fonction de print_sos à emit_sos:

def emit_sos(nb):
    s = "." * 3
    o = "-" * 3
    stop = "|"
    return (s + o + s + stop) * nb

emit_sos(5)

Cette fois-ci lorsqu’on execute notre programme, plus rien n’est affiché.

Mais rassurez-vous, on peut toujours vérifier que cela fonctionne en modifiant la dernière ligne en:

print(emit_sos(5))

En effet, emit_sos() retourne une chaine de caractère que print() va afficher.

On a donc créé une fonction réutilisable, et que l’on peut greffer à d’autre comme par exemple emettre un son

play(emit_sos(5))

ou encore allumer et éteindre un phare

flash(emit_sos(5))

Mais avant de gérer les phares et cassez les oreilles des autres, nous allons plutôt interagir avec notre machine à S.O.S et nous familiariser avec elle.

Un peu d’interactivité avec l’utilisateur•trice serait le bienvenu, par exemple demander à l’utilisateur•trice de rentrer au clavier le nombre de fois qu’il faut afficher le SOS.

Nous allons utiliser la fonction input() de Python 3.

La fonction input() laisse l’utilisateur•trice taper un message (terminé par l’appui sur la touche Entrée) puis retourne la chaine de caractère qui a été tapée:

>>> input()
Bonjour à toutes et à tous
'Bonjour à toutes et à tous'

Le résultat peut bien sûr être stocké dans une variable afin de l’utiliser par la suite:

>>> message = input()
Ceci est un test
>>> print("Vous avez tapé : " + message)
Vous avez tapé : Ceci est un test

Essayons maintenant de l’intégrer à notre machine à SOS:

def emit_sos(nb):
    s = "." * 3
    o = "-" * 3
    stop = "|"
    return (s + o + s + stop) * nb

print("Entrez le nombre de S.O.S que vous voulez: ")
nb_sos = input()
print(emit_sos(nb_sos))

Voici ce que le programme donne une fois executé:

$ python morse.py
Entrez le nombre de S.O.S que vous voulez:
5
Traceback (most recent call last):
  File "morse.py", line 9, in <module>
    print(emit_sos(nb_sos))
  File "morse.py", line 5, in emit_sos
    return (s + o + s + stop) * nb
TypeError: can't multiply sequence by non-int of type 'str'

Ceci est une erreur Python (on dit une exception). L’erreur vient du fait que la fonction input() retourne une chaine de caractères et non pas un nombre entier. En Python, "5" est différent de 5, le premier est une chaine de caractères et le deuxième est un entier:

>>> type("5")
<class 'str'>
>>> type(5)
<class 'int'>

Note

La fonction type() permet d’afficher le type d’une expression.

Pour convertir notre chaîne de caractères en entier, nous allons utiliser la fonction int():

>>> a = "5"
>>> a
'5'
>>> int(a)
5

Voici le code corrigé:

def emit_sos(nb):
    s = "." * 3
    o = "-" * 3
    stop = "|"
    return (s + o + s + stop) * nb

print("Entrez le nombre de S.O.S que vous voulez: ")
nb_sos = int(input())
print(emit_sos(nb_sos))

Une fois lancé:

$ python morse.py
Entrez le nombre de S.O.S que vous voulez:
5
...---...|...---...|...---...|...---...|...---...|

Les conditions

En avant vers notre prochaine problématique.

Maintenant on a une machine à émettre des S.O.S autant de fois qu’on le désire. Mais il faut faire attention, trop de S.O.S pour faire imploser le bateau ou faire sauter l’élecricité ou encore simplement rendre fou le capitaine.

On va donc prévenir le lanceur de S.O.S s’il dépasse la limite autorisée. On va modifier notre fichier morse.py pour ajouter des précautions d’usage:

def emit_sos(nb):
    s = "." * 3
    o = "-" * 3
    stop = "|"
    return (s + o + s + stop) * nb

print("Entrez le nombre de S.O.S que vous voulez: ")

nb_sos = int(input())
# Et maintenant nous allons vérifier que
# l'utilisateur n'abuse pas en nombre de sos
# si ... alors
if nb_sos == 0:
    print("Pas de S.O.S pour toi donc.")
# ou si ... alors
elif nb_sos > 10:
    print("Trop de SOS ! Stoppez ça s'il vous plait ! " +
          "Vous allez casser la machine !")
# sinon alors
else:
    print(emit_sos(nb_sos))

Maintenant l’utilisateur a peut être VRAIMENT un problème, il faut quand même envoyer un signal. On va donc quand même envoyer le signal mais en respectant la limite:

# si ... alors
if nb_sos == 0:
    print("Pas de S.O.S pour toi donc.")
# ou si ... alors
elif nb_sos > 10:
    print("Trop de SOS ! Stoppez ça s'il vous plait ! " +
          "Vous allez casser la machine ! Relais de 10 S.O.S")
    print(emit_sos(10))
# sinon alors
else:
    print(emit_sos(nb_sos))

L’utilisateur de la machine à S.O.S maintenant qu’il est prevenu peu informer de l’urgence de sa situtation en fonction du nombre de S.O.S qu’il envoie:

Nombre de S.O.S Type de Signal Signification
< 5 Avarie mineure on rentre au port rapidemment
5 – 12 Avarie moyenne patrouille de reconnaissance demandée
≥ 12 Avarie majeure envoi immédiat des forces d’interventions

Exercice : Ecrire une fonction qui va afficher le type de signal en fonction du nombre de S.O.S envoyé (n’oubliez pas de prendre en compte s’il n’y a pas de signal)

Pour aller plus loin

Quand l’utilisateur demande plus de 10 S.O.S, vous pouvez avoir envie de lui redemander le nombre de S.O.S à envoyer. Pour cela, on peut créer une fonction pour lui redemander à l’infini tant qu’il ou elle ne respecte pas la règle maximale de 10 S.O.S :

def emit_sos(nb):
    s = "." * 3
    o = "-" * 3
    stop = "|"
    return (s + o + s + stop) * nb


def ask_for_sos():
    print("Combien de S.O.S voulez-vous ?")

    nb_sos = int(input())

    # Et maintenant nous allons vérifier que l'utilisateur n'abuse pas en nombre de sos
    if nb_sos == 0:
        print("Pas de S.O.S pour toi donc.")
    # ou si ... alors
    elif nb_sos > 10:
        print("Trop de SOS ! Stoppez ça s'il vous plait ! " +
              "Choisissez un nb en 1 et 10!")
        ask_for_sos()
    # sinon alors
    else:
        print(emit_sos(nb_sos))

ask_for_sos()
Conditions : vrai ou faux

La première chose dont nous n’avons pas encore parlé sont les conditions. Pour les nombres, cela fonctionne exactement comme en mathématiques :

>>> 2 > 1
True
>>> 1 == 2
False
>>> 1 == 1.0
True
>>> 10 >= 10
True
>>> 13 <= 1 + 3
False
>>> -1 != 0
True

Le résultat d’une condition est toujours True ou False. On peut utiliser les opérateurs and et or pour construire des conditions plus complexes:

>>> x = 5
>>> x < 10
True
>>> 2 * x > x
True
>>> (x < 10) and (2 * x > x)
True
>>> (x != 5) and (x != 4)
False
>>> (x != 5) and (x != 4) or (x == 5)
True
Indentation

Une deuxième chose à laquelle il faut faire attention en Python, c’est l’indentation du code.

Ouvrez l’interpreteur Python et entrez une combinaison simple, par exemple:

>>> if 2 > 1:
...

Pour l’instant rien ne se passe, comme le montrent les points ... à la place des habituels chevrons >>>. Python s’attend à ce que nous donnions des instructions complémentaires qui devront être exécutées si la condition 2 > 1 s’avère vraie. Essayons d’afficher “OK”:

>>> if 2 > 1:
... print("OK")
  File "<stdin>", line 2
    print("OK")
        ^
IndentationError: expected an indented block

Apparemment, ça n’a pas très bien fonctionné. En fait Python doit savoir si l’instruction que nous avons entrée est une instruction à exécuter uniquement si la condition est vraie ou si c’est une instruction à exécuter sans qu’elle ne soit affectée par la condition.

C’est pourquoi nous devons indenter notre code:

>>> if 2 > 1:
...  print("OK")
...
OK

Tout ce que vous devez faire c’est ajouter un espace ou une tabulation avant votre instruction pour dire qu’elle fait partie des instructions dépendantes du if. Attention, toute les lignes a exécuter dans le if doivent être indentées de la même manière:

>>> if -1 < 0:
...  print("A")
...   print("B")
  File "<stdin>", line 3
    print("B")
    ^
IndentationError: unexpected indent

>>> if -1 < 0:
...     print("A")
...   print("B")
  File "<stdin>", line 3
    print("B")
            ^
IndentationError: unindent does not match any outer indentation level

>>> if -1 < 0:
...   print("A")
...   print("B")
...
A
B

Pour éviter la confusion, la plupart des développeurs Python se sont mis d’accord pour toujours utiliser quatre espaces pour chaque niveau d’indentation. Nous allons nous aussi adopter cette convention:

>>> if 2 > 1:
...     if 3 > 2:
...         print("OK")
...     else:
...         print("ECHEC")
...     print("FAIT")
OK
FAIT
Et si ce n’est pas le cas ?

On pourrait se débrouiller pour écrire un programme en utilisant uniquement des if

if nb_sos < 5:
    print("Avarie Mineure")
if nb_sos >= 5:
    if nb_sos < 12:
        print("Avarie Moyenne")
if nb_sos >= 12:
    print("Avarie Majeure")

Mais en fait, on peut aussi utiliser else et elif, afin de ne pas avoir à répéter les conditions similaires et améliorer la lisibilité du code. Dans des programmes plus compliqués, il n’est parfois pas évident de reconnaître que la condition lue est la condition inverse de la précédente.

En utilisant else , nous avons la garantie que les instructions données seront exécutées seulement si les instructions données après le if n’ont pas été exécutées:

if nb_sos < 5:
    print("Avarie Mineure")
else:
    # Si votre programme exécute ces instructions alors vous êtes
    # certains que nb_sos >= 5 !
    if nb_sos < 12:
        print("Avarie Moyenne")
    else:
        # Ici vous pouvez être certains que nb_sos >= 12
        # nous n'avons donc pas à le vérifier.
        print("Avarie Majeure")

Regardez bien attentivement la manière dont le code est indenté. À chaque utilisation de else, un niveau d’indentation a été ajouté à chaque niveau du code. C’est très ennuyeux d’avoir à lire du code avec de nombreux niveaux d’indentation.

C’est pourquoi les développeurs Python on ajouté un troisième mot clé, elif, qui permet de vérifier directement une autre condition.

if n < 1:
    # Si n est inférieur à un.
    print("inferieur à un")
elif n < 2:
    # Si est supérieur ou égal à un, et inférieur à deux.
    print("entre un (compris) et deux")
elif n < 3:
    # Si n est supérieur ou égal à un,
    # que n est supérieur ou égal à deux,
    # et que n est inférieur à 3
    print("entre deux et trois")
else:
    # Si aucune des conditions précédentes n'est vérifiée
    print("supérieur ou égal à trois")

Tuples

Nous savons déjà que nous pouvons concaténer des chaînes de caractères, les multiplier par des nombres, vous allez voir qu’on peut aussi les formater. Tout d’abord, nous avons besoin de découvrir un nouveau type de données (en plus des strings et des nombres, int et float, que nous connaissons déjà).

Rappelez-vous, je vous disais que nous ne pouvions pas utiliser les virgules dans les nombres car nous en aurions besoin par la suite pour définir les tuples. Nous y voici

>>> 1, 2, 3
(1, 2, 3)
>>> ("Ala", 15)
('Ala', 15)
>>> x = 1,5
>>> print(x)
(1, 5)

Un tuple n’est ni plus ni moins qu’une valeur contenant un groupe de valeurs. Les valeurs que nous souhaitons grouper doivent être séparées par des virgules. L’ensemble peut-être entouré de parenthèses pour rendre plus explicite le fait qu’il s’agisse bien d’un groupe, mais ce n’est pas obligatoire. Sauf pour le cas d’un groupe vide (aussi bizarre que cela puisse paraître).

>>> ()
()

Il est possible de combiner des tuples:

>>> names = ("Pauline", "Dupontel")
>>> details = (27, 1.70)
>>> names + details
('Pauline', 'Dupontel', 27, 1.7)

Un tuple peut aussi contenir un autre tuple, par exemple un point sur une carte peut-être groupé comme ceci:

>>> point = ("Pizzeria", (long, lat))

Avec long et lat des coordonnées géographiques.

On peut ensuite se référer aux valeurs d’un groupe en utilisant leurs positions (en commençant à zéro):

>>> p = (10, 15)
>>> p[0]  # première valeur
10
>>> p[1]  # deuxième valeur
15

En résumé

Dans ce chapitre nous avons appris les bases de la syntaxe Python. Nous avons découvert comment afficher des nombres entiers et décimaux, des chaînes de caractères et nous avons découvert les tuples.

Nous avons appris à utiliser la fonction print(), qui affiche des informations à l’utilisateur et la fonction input(), qui permet de lire les entrées de ce dernier.

Nous avons vu comment l’identation pouvait être importante, notamment lors de l’utilisation des instructions if, else et elif.

Nous avons réalisé notre premier programme dans un fichier que nous pouvons lancer.

Notre programme pose quelques questions à l’utilisateur, calcule des informations et présente les résultats dans une forme utile à l’utilisateur.

Ça fait finalement beaucoup de choses pour un premier programme. Nous avons encore pas mal de travail mais vous pouvez être fier de ce que vous avez fait jusqu’à présent !

L’Arbre de Noël

Noël arrive (dans plus ou moins longtemps c’est vrai), ce sera le temps des cadeaux et des sapins de Noël dans tous les magasins. :)

Comme exercice, je vous propose de dessiner un Arbre de Noël dans la console.

Nous allons commencer par la version la plus simple puis ajouter des fonctionnalités au fur et à mesure.

Pour démarrer, commençons par dessiner la moitié d’un Arbre de Noël :

print("*")
print("**")
print("***")
print("*")
print("**")
print("***")
print("****")
print("*")
print("**")
print("***")
print("****")
print("*****")
print("******")
*
**
***
*
**
***
****
*
**
***
****
*****
******

C’est pas si mal, mais nous avons du taper beaucoup de choses.

Et que se passe-t-il si je veux un arbre plus petit ? Ou un plus grand, composé de centaines d’étoiles pour l’imprimer sur un poster géant au format A0 ?

Oui ça fait certainement beaucoup trop de caractères à taper, quand bien même on multiplierait les caractères par centaines ("*" * 100, et ainsi de suite).

Ça ressemble au genre de tâche qu’on confierait volontiers à un programme ça, non ?

Les listes et les boucles for

Les boucles sont faites exactement pour ce genre d’actions répétitives. Pour rester dans l’atmosphère de Noël, imaginez un instant que vous êtes le Père Noël et que vous devez distribuer tous les cadeaux.

Comme vous le savez, les lutins ont une liste précise des enfants sages qui méritent un cadeau. La solution la plus simple pour garantir qu’un enfant ne soit pas oublié serait de prendre la liste et d’aller distribuer les cadeaux, dans l’ordre.

Outre les aspects physiques de la tâche [1], la procédure de distribution des cadeaux pourrait ressembler à cela:

Disons que la liste des enfants sages, contient la liste des enfants
qui méritent un cadeau.

Pour chaque enfant (alias ``child``),
  qui se trouve dans la liste des enfants sages:
     Distribuer un cadeau à cet enfant

La disposition du texte ci-dessus n’est pas une erreur, c’est en fait un programme Python déguisé:

children = children_who_deserve_gifts()

for child in children:
    deliver_gift(child)
    print("Cadeau distribué à :", child)
print("Tous les enfants sages ont reçus un cadeau")

La plupart des choses doivent vous sembler familières. On appelle deux fonctions :

children_who_deserve_gifts() et deliver_gift() - leur fonctionnement interne est uniquement connu du Père Noël.

Le résultat de la première peut recevoir comme alias children, afin de se rappeler par la suite à quoi corresponds cette valeur.

Le nouvel élément, c’est la boucle elle-même, qui consiste en :

  • Le mot clé for,
  • Le nom du prochain élément de la liste,
  • Le mot clé in,
  • Une liste de valeur ou un alias qui y fait référence.
  • Les instructions indentées à effectuer pour chaque valeur de la liste (comme dans le cas de if).

Attendez, nous n’avons encore rien dit à propos des listes, mais rassurez-vous, le concept de liste en Python est très proche du concept de liste dans la vie de tous les jours.

Nous pouvons simplement nous représenter une liste en Python comme nous nous représentons n’importe quelle autre liste le reste du temps (liste de courses, liste d’invités, résultats d’examens, etc.) écrite sur une papier et numérotée.

Commençons par une liste vide :

>>> l = []
>>> l
[]

Quand nous le souhaitons, nous pouvons demander le nombre d’éléments qui se trouvent dans notre liste en utilisant la fonction:func:len.

>>> len(l)
0

Essayons avec une autre liste (qui peut avoir le même nom ou pas) :

>>> l = ["Yara", "Pierre", "Amel"]
>>> len(l)
3

Comme pour le cas des tuples, les éléments consécutifs d’une liste sont séparés par des virgules. À la différence des tuples, les crochets sont obligatoires.

Pour récupérer la valeur d’un élément d’une position particulière de la liste (en se souvenant que les index des positions commencent à 0) :

>>> l[0]
'Yara'
>>> l[1]
'Pierre'
>>> l[2]
'Amel'
>>> l[3]
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
IndexError: list index out of range

On peut aussi utiliser une boucle for, pour exécuter une instruction sur chaque élément de la liste:

>>> for name in l:
...     print("Nom :", name)
...
Nom : Yara
Nom : Pierre
Nom : Amel

En passant, nous pouvons ainsi afficher la première moitié de notre Arbre de Noël :

>>> lst = [1, 2, 3]
>>> for n in lst:
...     print("*" * n)
...
*
**
***

Malheureusement, nous devons encore écrire le contenu de la liste. Ce problème peut-être résolu à l’aide de la fonction range(). Regardez help(range) pour apprendre à l’utiliser ou regardez ces exemples :

>>> list(range(2, 5, 1))
[2, 3, 4]
>>> list(range(1, 11, 2))
[1, 3, 5, 7, 9]
>>> list(range(1, 11))
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> list(range(1, 2))
[1]
>>> list(range(2))
[0, 1]

La fonction range() ne crée pas directement une liste, mais retourne un générateur. Les générateurs génèrent les éléments un à un, ce qui permet de ne pas avoir à stocker l’ensemble des valeurs de la liste dans la mémoire de l’ordinateur.

Pour obtenir une liste à partir d’un générateur, on utilise la fonction list(). Si on oublie l’appel à list(), le résultat ressemblera à ça :

>>> range(1, 4)
range(1, 4)

La fonction range() a trois formes. La plus simple, qui est la plus utilisée, permet de générer une séquence de nombres de 0 à un nombre donné. Les autres formes vous permettent de spécifier le chiffre de départ et le pas d’un nombre à l’autre de la séquence. La séquence créée n’inclut jamais la borne supérieure.

Affichons un Arbre de Noël plus grand :

>>> lst = list(range(1, 11))
>>> lst
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> for i in lst:
...     print("*" * i)
*
**
***
****
*****
******
*******
********
*********
**********

range() nous a épargné beaucoup de temps, on peut en gagner encore plus si on ne nomme pas la liste:

>>> for i in list(range(1, 5)):
...     print(i * "#")
#
##
###
####

Lorsqu’on utilise le mot clé for, on n’a pas besoin d’utiliser la fonction list(). for sait gérer le générateur retourné par range. Ce qui nous permet de simplifier notre programme encore plus.

>>> for i in range(1, 5):
...     print(i * "#")
#
##
###
####

Rien ne nous empêche de créer une boucle dans une autre boucle, essayons ! Simplement rappelez-vous d’utiliser l’indentation appropriée et d’utiliser des alias différents par exemple i et j (ou mieux un nom en rapport avec le contenu de la liste):

>>> for column in range(1, 3):
...    for line in range(11, 14):
...        print(column, line)
1 11
1 12
1 13
2 11
2 12
2 13

Nous avons une boucle intérieure allant de 11 à 13 (n’oubliez pas que, 14 n’est pas incluse lorsqu’on utilise range) inclue dans une boucle extérieure qui elle va de 1 à 2.

Comme vous pouvez le voir les éléments de la boucle intérieure sont affichés deux fois, une fois pour chaque itération de la boucle extérieure.

En utilisant cette technique, on peut répéter les éléments de notre Arbre de Noël :

>>> for etages in range(3): # répéter 3 fois
...    for taille in range(1, 4):
...        print(taille * "*")
*
**
***
*
**
***
*
**
***

Avant d’aller plus loin, créez le fichier noel.py avec ce programme et essayez de le modifier afin que pour chaque itération de la boucle extérieure la boucle intérieure soit exécutée une fois de plus. (Que pour chaque étage on ait une branche de plus).

Vous devriez obtenir le résultat de notre demi Arbre de Noël décrit en début de chapitre.

Les fonctions

Nous avons déjà pu voir comment les fonctions résolvent nombre de nos problèmes. Par contre elle ne les résolvent pas tous - ou du moins pas exactement de la manière dont nous aimerions les résoudre.

Parfois, et même assez souvent nous devons résoudre nous-mêmes un problème. Ce serait donc assez cool de pouvoir créer des fonctions qui le fassent pour nous.

Voici comment nous pouvons faire en Python:

>>> def print_triangle(n):
...     for size in range(1, n + 1):
...         print(size * "*")
...
>>> print_triangle(3)
*
**
***
>>> print_triangle(5)
*
**
***
****
*****

Regardons de plus près la fonction print_triangle():

def print_triangle(n):
    for size in range(1, n + 1):
        print(size * "*")

La définition d’une fonction commence toujours avec le mot clé def. Ensuite on donne un nom à la fonction. Entre les parenthèses, on indique quels sont les noms des arguments passés à la fonction lorsqu’elle est appelée. Les lignes suivantes définissent les instructions à exécuter lors de l’utilisation de la fonction.

Comme vu dans l’exemple, les instructions peuvent utiliser les alias des noms des arguments. Le principe opératoire est le suivant - si on créé une fonction avec trois arguments :

>>> def foo(a, b, c):
...     print("FOO", a, b, c)

Lorsque vous appelez cette nouvelle fonction, vous devez spécifier une valeur pour chacun des arguments. De la même manière que ce que nous faisions pour appeler les fonctions précédentes :

>>> foo(1, "Ala", 2 + 3 + 4)
FOO 1 Ala 9
>>> x = 42
>>> foo(x, x + 1, x + 2)
FOO 42 43 44

On notera qu’un argument est simplement un alias, si on modifie la valeur liée à cet alias pour une autre valeur, les autres alias ne sont pas modifiés - c’est la même chose pour les arguments:

>>> def plus_five(n):
...     n = n + 5
...     print(n)
>>> x = 43
>>> plus_five(x)
48
>>> x
43

ça fonctionne comme pour les alias (variables) que nous avons vu précédement. Il y a seulement deux différences :

Premièrement, les alias des arguments d’une fonction sont définis à chaque appel de la fonction, et Python attache la valeur correspondante à la valeur de l’argument à l’alias de l’argument qu’il vient de créér.

Deuxièmement, les alias des arguments ne sont pas utilisable à l’extérieur de la fonction car ils sont créé lors de l’appel de la fonction et oublié à la fin de celle-ci. C’est pourquoi, si vous essayez d’accéder à la valeur n que nous avons définie dans notre fonction plus_five() à l’extérieur du code de la fonction Python vous dit qu’elle n’est pas définie :

>>> n
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'n' is not defined

C’est comme ça que notre cher Python fait le ménage à la fin d’un appel de fonction :)

Retourner un résultat

Une des propriétés principales des fonctions que nous avons utilisées précédement manque cruellement à nos fonctions — elles retournaient le résultat qu’elles avaient calculé au lieu de l’écrire directement. Pour obtenir un comportement similaire, il faut utiliser l’instruction return. C’est une instruction spécifique qui ne fonctionne qu’au sein d’une fonction.

Pour finir, comme dernier exemple de fonction, voici la solution au problème posé à la fin du chapitre précédent :

# noel.py

def print_triangle(n):
    for size in range(1, n + 1):
        print(size * "*")

for i in range(2, 5):
    print_triangle(i)
*
**
*
**
***
*
**
***
****

Un Arbre de Noël entier

Le chapitre précédent était principalement de la théorie. Utilisons nos nouvelles connaissances pour terminer notre programme et afficher notre Arbre de Noël.

Voici à quoi ressemble notre fichier actuel:

# noel.py

def print_triangle(n):
    for size in range(1, n+1):
        print(size * "*")

for i in range(2, 5):
    print_triangle(i)

Comment pouvons-nous améliorer la fonction print_triangle(), pour afficher un Arbre de Noël entier et non juste la moitié ?

Tout d’abord, essayons de déterminer le résultat attendu en fonction de la valeur de l’argument n. Il parait naturel que n soit la largeur. Ainsi pour n = 5 on s’attendrait à:

  *
 ***
*****

Il est intéressant de noter que chaque ligne possède deux étoiles de plus que la ligne précédente. Nous pouvons donc utiliser le troisième argument de range():

def print_segment(n):
    for size in range(1, n + 1, 2):
        print(size * "*")

print_segment(5)
*
***
*****

Ce n’est pas exactement ce à quoi on s’attendait, il y a effectivement le bon nombre d’étoiles mais on souhaiterait qu’elle soit aligné au centre.

La fonction str.center(), que vous avez peut-être trouvée en répondant à la question de fin du chapitre précédent, peut nous aider:

def print_segment(n):
    for size in range(1, n + 1, 2):
        print((size * "*").center(n))

print_segment(5)
  *
 ***
*****

Cependant, un nouveau problème apparait :

def print_segment(n):
    for size in range(1, n + 1, 2):
        print((size * "*").center(n))

for i in range(3, 8, 2):
    print_segment(i)
 *
***
  *
 ***
*****
   *
  ***
 *****
*******

Si nous avions un moyen de connaitre à l’avance la taille du segment le plus grand, nous pourrions ajouter un argument supplémentaire à print_segment(), pour faire le centrage sur cette largeur. En combinant toute la connaissance acquise :

def print_segment(segment_size, total_width):
    for line_size in range(1, segment_size + 1, 2):
        print((line_size * "*").center(total_width))

def print_tree(size):
    for segment_size in range(3, size + 1, 2):
        print_segment(segment_size, size)

print("Choisissez la taille de votre Arbre de Noël :")
tree_size = int(input())
print_tree(tree_size)
Choisissez la taille de votre Arbre de Noël :
7
   *
  ***
   *
  ***
 *****
   *
  ***
 *****
*******
Pour aller plus loin (facultatif)

Réaliser une classe XMASTree qui pour une taille donnée et lors de l’appel de la méthode draw va afficher les résultats suivants (pour les tailles 1, 2 et 3) :

  *
 /|\
/_|_\
  |
   *
  /|\
 /_|_\
  /|\
 / | \
/__|__\
   |
    *
   /|\
  /_|_\
   /|\
  / | \
 /__|__\
   /|\
  / | \
 /  |  \
/___|___\
    |

Notes

[1]En considérant que vous avez 24 heures pour distribuer un cadeau à chaque personne de la Terre, il faudrait distribuer un cadeau toute les 10 microsecondes.

Objets et classes

Dans les faits, ce chapitre pourrait faire l’objet d’un atelier d’un week-end à lui tout seul. Nous allons simplement nous concentrer sur les fonctionnalités les plus simples dont vous aurez besoin pour pouvoir travailler avec Django.

Toute valeur est un objet

Toutes les choses que nous avons appelées valeur jusqu’à présent peuvent être appelé “un objet” dans l’univers de Python. On dit souvent que “tout est objet”.

Par exemple les entiers, pour lesquels la fonction help() nous retournait des dizaines de lignes d’information à propos de int() sont aussi des objets.

Tout objet a une classe

Une classe est le type d’un objet (un type d’objet ?). Par analogie, on peut dire que c’est le moule qui permet de créer l’objet.

On peut tout simplement utiliser la fonction type(): pour connaitre le type d’un objet :

>>> type(2)
<class 'int'>
>>> type(2.0)
<class 'float'>
>>> type("spam eggs")
<class 'str'>
>>> x = 1, 2
>>> type(x)
<class 'tuple'>
>>> type([])
<class 'list'>

Nous avons déjà parlé des classes que vous pouvez voir ici : int, float, str, tuple.

Quand nous utilisons des nombres dans notre programme, nous attendons qu’ils se comportent comme des nombres, et nous savons intuitivement ce qu’est un nombre. Par contre, Python doit savoir exactement ce que signifie “être un nombre”.

Par exemple que se passe-t-il lorsqu’on additionne deux nombres ? Ou qu’on les divise ? La classe int définit tout cela et bien plus.

En utilisant la fonction help() , vérifiez ce que nous donne la classe str. Voici quelques fonctionnalités intéressantes :

>>> help(str.lower)
Help on method_descriptor:

lower(...)
    S.lower() -> str

    Return a copy of the string S converted to lowercase.

>>> help(str.upper)
Help on method_descriptor:

upper(...)
    S.upper() -> str

    Return a copy of S converted to uppercase.

>>> help(str.ljust)
Help on method_descriptor:

ljust(...)
    S.ljust(width[, fillchar]) -> str

    Return S left-justified in a Unicode string of length width. Padding is
    done using the specified fill character (default is a space).

>>> help(str.center)
Help on method_descriptor:

center(...)
    S.center(width[, fillchar]) -> str

    Return S centered in a string of length width. Padding is
    done using the specified fill character (default is a space)

Toutes ces opérations sont applicable à n’importe quelle chaîne de caractères. Pour y accéder, on ajoute un point suivi de l’appel de la fonction à appliquer :

>>> x = "Ala"
>>> x.upper()
'ALA'
>>> x.lower()
'ala'
>>> x.center(9)
'   Ala   '

Une fonction appliquée à un objet est appelée une méthode de l’objet.

Encore une dernière chose importante, pour créer un nouvel objet, on appelle la classe de l’objet (dans le jargon technique on dit qu’on instancie un objet). L’objet ainsi créé est appelé une instance de la classe :

>>> int()
0
>>> str()
''
>>> list()
[]
>>> tuple()
()

Une instance est donc une nouvelle valeur du type décrit par la classe.

Pour résumer, nous avons vu les classes int(), str(), tuple() et list(). Nous avons vu que pour connaitre la classe décrivant une valeur (un objet), nous pouvions regarder son type avec la fonction type(). Pour créer une instance de la classe (un nouvel objet), on appelle la classe de la même manière que nous appelons une fonction, en ajoutant des parenthèses (). Par exemple : int().

Définir une classe

Les classes telles que int ou str font partie du langage Python et sont déjà définies, mais nous pouvons créer nos propres classes pour définir leur comportement. Cela s’appelle définir une classe.

Il est aussi facile de définir une classe que de définir une fonction. En fait une classe n’est rien de plus qu’un ensemble de fonctions. Prenons par exemple une classe Dog :

class Dog(object):

    def bark(self):
        print("Woof! Woof!")

Les classes commencent par le mot clé class, suivi du nom de la classe. L’(object) indique que nouveau type Dog est un nouveau type de l’ensemble des classes de type object. Ainsi, les instances de notre classe, c’est à dire les objets créés, seront de type Dog mais également du type plus général des objects.

En fait c’est exactement pour cela qu’on dit que “tout est objet en Python”. Car chaque classe est une spécialisation de la classe object de Python. C’est pourquoi quasiment chaque valeur est de type général object.

Il est important de noter que chaque fonction d’une classe doit prendre pour premier argument la valeur de l’objet duquel elle a été appelée. Nous l’appelons systématiquement self par convention. Dans notre exemple, nous avons une fonction appelée bark (“aboyer” en anglais), qui comme vous le voyez n’a qu’un seul argument. Regardons comment elle fonctionne :

my_new_pet = Dog()
my_new_pet.bark()
Woof! Woof!

Vous pouvez appeler ce premier argument comme vous le souhaitez mais la convention la plus répandue est de l’appeler self.

Attributs des objets

Outre les méthodes (les fonctions définies dans une classe), les objets peuvent également avoir des attributs.

my_new_pet = Dog()
my_new_pet.name = "Snoopy"

print(my_new_pet.name)
Snoopy

Parfois nous souhaitons que tous les objets d’une classe aient un attribut, par exemple tous les chiens doivent avoir un nom. Nous pouvons le spécifier en créant une fonction, au nom spécial, appelée __init__ :

class Dog(object):

    def __init__(self, name):
        self.name = name

    def bark(self):
        print("Woof! Woof!")

Dans la fonction __init__, nous avons assigné une valeur à un nouvel attribut name de l’objet self. Comme expliqué précédemment, self est l’objet courant de la classe Dog que nous sommes en train de manipuler. Nous pouvons maintenant utiliser cet attribut dans les autres méthodes :

class Dog(object):

    def __init__(self, name):
        self.name = name

    def bark(self):
        return "Woof! %s! Woof!" % (self.name,)

snoopy = Dog("Snoopy")
pluto = Dog("Pluto")
print(snoopy.bark())
print(pluto.bark())
Woof! Snoopy! Woof!
Woof! Pluto! Woof!

La fonction __init__() est appelée durant la création de l’objet. On l’appelle constructeur, car elle aide à la création de l’objet.

Dans cet exemple, la fonction __init__ accepte deux arguments: self et name, mais quand on créé une instance de la classe Dog, nous ne spécifions que l’argument `name, self est automatiquement spécifié par Python. Désormais, lorsque que nous instancions un nouvel objet Dog, celui-ci a un attribut: son nom.

Héritage

Dans le chapitre précédent, nous avons créé une classe Dog comme sous-ensemble du type object, mais ce n’est pas la seule possibilité. Nous pouvons également dire que Dog est aussi un Animal :

class Animal(object):
    pass

class Dog(Animal):

    def __init__(self, name):
        self.name = name

    def bark(self):
        return "Woof! %s! Woof!" % (self.name,)

Nous avons donc une nouvelle classe Animal, qui hérite du type object. Dog hérite du type Animal. En d’autres termes :

  • Tout Animal est un object
  • Tout Dog est un Animal, tout Dog est un object

Ainsi nous pouvons décrire des comportements communs à tous les Animaux dans notre classe Animal, par exemple le fait de courir, et laisser dans la classe Dog des comportements plus spécifiques, comme aboyer:

class Animal(object):

    def run(self, distance):
        return "Run %s meters." % (distance,)

La méthode run sera disponible pour tous les sous-types de Animal (comme les objets de type Dog par exemple) :

>>> scooby = Dog("Scooby")
>>> print(scooby.run(10))
Run 10 meters.

Introduction à Django

Django, qu’est-ce que c’est ?

Nous avons jusqu’ici appris des rudiments de Python, mais Python est juste un langage — accompagné de bibliothèques de code assez basiques — permettant de créer des programmes.

La création d’un site web interactif en Python demande un travail considérable, c’est pourquoi nous allons utiliser Django, qui nous fournit un ensemble d’outils, fonctions (comme celles que nous avons étudiées précédemment, mais plus élaborées) et classes qui facilitent beaucoup la création de sites.

Pour mettre en place un site web pleinement interactif, il nous faut ces quelques éléments :

  • Un serveur d’application - ici nous allons utiliser Django.
  • Des fichiers HTML et CSS, qui vont décrire l’apparence du site.
  • Une base de données pour stocker les questions et réponses à nos sondages.

Commençons par créer le serveur d’application.

Création du projet

Installation

Installez Django en exécutant la commande suivante dans la console : pip install django.

(workshops) ~$ pip install django
Collecting django
  Downloading Django-1.10.6-py2.py3-none-any.whl (6.8MB)
Installing collected packages: django
Successfully installed django-1.10.6

Le paquet adéquat est alors téléchargé depuis PyPI — un dépôt centralisé de paquets de code où sont disponibles de nombreuses bibliothèques.

Démarrage du projet

Django fournit le script d’administration django-admin.py qui permet entre autres choses de créer l’arborescence initiale du site. Pour démarrer le nouveau projet, exécutez la commande suivante : django-admin.py startproject carrots

# Linux

(workshops) ~$ django-admin startproject carrots
(workshops) ~$ tree carrots
carrots/
├── carrots
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
└── manage.py

1 directory, 5 files
# Windows

(workshops) C:\Users\Alex> python -m django-admin startproject carrots
(workshops) C:\Users\Alex> tree /f carrots
Folder PATH listing
Volume serial number is 00FA-07FF
C:\USERS\ALEX\DOCUMENTS\CARROTS
│   manage.py
│
└───carrots
        settings.py
        urls.py
        wsgi.py
        __init__.py
Structure du projet

Le projet que nous venons de créer contient un répertoire carrots et quelques fichiers de base.

Le fichiers carrots/settings.py contient des paramètres de configurations tels que le langage, la connexion à la base de données ou encore la liste des applications installées dans le projet. Vous pouvez modifier ce fichier vous-même, il contient un paramétrage par défaut ainsi que des commentaires explicatifs.

Le fichier manage.py nous permet d’administrer le site web, créer ou effacer la base de données, démarrer un simple serveur d’application, etc... Nous verrons par la suite comment l’utiliser.

Le fichier carrots/urls.py contient des informations sur les chemins d’accès aux différentes pages du site.

Les autres fichiers présentent moins d’intérêt dans l’immédiat. De manière générale, on n’a pas besoin de les ouvrir ou de les modifier. Si ces fichiers vous intriguent, n’hésitez pas à rechercher sur Internet des informations supplémentaires à leur sujet.

Réglages de l’application

Modifiez ainsi dans le fichier carrots/settings.py les lignes suivantes pour régler la langue et le fuseau horaire utilisés par l’application

LANGUAGE_CODE = 'fr_FR'

TIME_ZONE = 'Europe/Paris'

Pour simplifier les choses nous allons désactiver la gestion avancée des fuseaux horaires dans la base de données car nous n’en aurons pas besoin pour ce projet. Localisez le paramètre USE_TZ et positionnez-le à False

USE_TZ = False

La section INSTALLED_APPS contient des informations sur les applications installées. Un projet Django est en effet composé de plusieurs applications, comme ici l’application auth qui sert à authentifier les utilisateurs, l’application sessions qui permet de gérer les sessions des utilisateurs, et ainsi de suite.

Comme vous pouvez le voir, INSTALLED_APPS est tout simplement un tuple de noms d’applications. Vous pouvez vérifier que l’interface d’administration fournie par Django est bien dans la liste. Nous allons voir plus tard comment l’utiliser.

Base de données

Le moment est maintenant venu d’utiliser le fichier manage.py pour créer la base de données de notre site. Pour ce faire, nous allons utiliser l’option migrate.

Lancez donc la commande python manage.py migrate depuis le répertoire du projet :

(workshops) ~$ cd carrots

(workshops) ~/carrots$ python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying sessions.0001_initial... OK

Lancez ensuite la commande python manage.py createsuperuser pour créer le compte administrateur :

(workshops) ~/carrots$ python manage.py createsuperuser
Username (leave blank to use 'ubuntu'): admin
Email address: admin@admin.com
Password:
Password (again):
Superuser created successfully.

Vous pouvez laisser le nom d’utilisateur qui vous est proposé et saisir n’importe quelle adresse email.

Retenez bien ces informations, en particulier le nom d’utilisateur et le mot de passe ; elles vous seront nécessaires pour vous connecter à l’interface d’administration. Dans l’exemple décit ci-dessus, le nom d’utilisateur sera admin. Si le validateur de mot de passe vous dérange vous pouvez utiliser ubuntu17_ par exemple.

Si vous voulez en apprendre davantage au sujet de manage.py, vous pouvez exécuter la commande python manage.py help.

(workshops) ~/carrots$ python manage.py help

Vous verrez alors la liste de toutes les commandes et options proposées par manage.py.

Pour obtenir de l’aide sur l’une de ces commandes, il suffit alors de taper python manage.py help, suivi du nom de la commande en question – par exemple

(workshops) ~/carrots$ python manage.py help migrate
Interface d’administration

Nous pouvons maintenant lancer notre application. Démarrez le serveur en tapant la commande python manage.py runserver

(workshops) ~/carrots$ python manage.py runserver
Validating models...

0 errors found
April 19, 2013 - 20:14:37
Django version 1.8.2, using settings 'carrots.settings'
Development server is running at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.

Notre site web est dès lors disponible à l’adresse http://127.0.0.1:8000/ ou encore http://localhost:8000/.

L’interface d’administration, quant à elle, peut être consultée au chemin admin/, c’est pourquoi nous y accédons par l’adresse http://localhost:8000/admin/.

Créons une nouvelle application pour nos sondages

Nous avons jusqu’à présent créé un projet appelé carrots. Les projets Django sont divisés en applications qui fournissent chacune des fonctions spécifiques.

Dans la suite de ce tutoriel, nous allons vous présenter comment créer une application Django. Nous prenons comme exemple la mise en place d’une application de sondage.

N’hésitez pas à suivre le tutoriel en faisant une autre application qui vous ferez plus envie.

Par exemple :

  • Recettes de cuisine,
  • Galerie de photos,
  • Gestion d’articles ou de vidéos favorites,
  • Gestion d’une bibliothèque de livre, de vinyles ou de séries TV,
  • etc.

Votre mentor vous aidera à adapter le tutoriel à votre application et répondra à vos questions, alors profitez-en :D

Donc nous voulons publier des sondages sur notre site, nous allons donc créer une application nommée polls (ce qui signifie sondages en anglais — l’anglais étant la langue la plus fréquemment utilisée au sein des projets informatiques).

Depuis l’invite de commandes, tapez python manage.py startapp polls

(workshops) ~/carrots$ python manage.py startapp polls
(workshops) ~/carrots$ tree -I '__pycache__'
.
├── carrots
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   ├── wsgi.py
├── db.sqlite3
├── manage.py
└── polls
    ├── admin.py
    ├── apps.py
    ├── __init__.py
    ├── migrations
    │   └── __init__.py
    ├── models.py
    ├── tests.py
    └── views.py


2 directories, 14 files

Une fois l’application créée, elle doit être activée dans notre projet. Ajoutez-la donc dans la section INSTALLED_APPS du fichier carrots/settings.py. Vous devriez parvenir à un résultat similaire à celui-ci

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
            'polls',
]

Les applications Django sont constituées de plusieurs fichiers :

  • admin.py - permet de configurer l’interface d’administration,
  • apps.py - permet de configurer l’application notamment son nom,
  • models.py - contient la définition des modèles de la base de données,
  • migrations/ - contient les règles de mise à jour de la base de données lors des changements des models,
  • tests.py - contient l’ensemble des tests permettant de valider le bon fonctionnement de l’application,
  • views.py - contient le code des différentes vues de l’application.
En résumé

Pour installer Django :

(workshops) ~$ pip install django

Pour créer un projet Django :

# Linux

(workshops) ~$ django-admin.py startproject carrots
# Windows

(workshops) C:\Users\TeddyBear> python -m django-admin startproject carrots

Pour régler le langage et le fuseau horaire, dans le fichier carrots/settings.py :

LANGUAGE_CODE = 'fr_FR'

TIME_ZONE = 'Europe/Paris'

USE_TZ = False

Pour créer ou mettre à jour la base de données, il faut lancer cette commande après avoir ajouté un nouveau modèle de données :

(workshops) ~/carrots$ python manage.py migrate

Pour créer un compte administrateur permettant d’accéder à l’interface d’administration :

(workshops) ~/carrots$ python manage.py createsuperuser

Pour démarrer le serveur d’application :

(workshops) ~/carrots$ python manage.py runserver

Pour créer une nouvelle application, par exemple nommée polls :

(workshops) ~/carrots$ python manage.py startapp polls

N’oubliez alors pas de rajouter cette nouvelle application à la section ``INSTALLED_APPS`` !

Models

La prochaine étape consiste en la définition du modèle de notre application.

Le modèle décrit ce qui peut être stocké dans la base de données et comment cela sera stocké. Du point de vue de Python, ce n’est qu’une classe standard, qui hérite de models.Model.

Notre application inclura des questions et leurs réponses, nous allons donc créer deux modèles : Poll et Choice. Le modèle Poll contient le contenu des questions ainsi que la date de publication. Le modèle Choice contient une référence vers la question adéquate, le contenu des réponses et le nombre de votes.

Dans le fichier polls/models.py nous écrivons:

from django.db import models

class Poll(models.Model):
    question = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')

class Choice(models.Model):
    poll = models.ForeignKey(Poll)
    choice_text = models.CharField(max_length=200)
    votes = models.IntegerField(default=0)

En ajoutant de nouveaux modèles, nous avons changé la disposition de la base de données. Nous devons appliquer la commande makemigrations afin de créer des fichiers de migration permettant d’ajouter les nouvelles tables correspondantes en base de données, ainsi que la commane migrate, permettant d’exécuter les fichiers de migrations créés par la commande précédente.

(workshops) ~/carrots$ python manage.py makemigrations
Migrations for 'polls':
  polls/migrations/0001_initial.py:
    - Create model Choice
    - Create model Poll
    - Add field poll to choice
(workshops) ~/carrots$ python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, polls, sessions
Running migrations:
  Applying polls.0001_initial... OK

C’est tout ! Par contre, vous souhaitez probablement avoir la possibilité d’éditer les objets. La façon la plus simple de le faire est d’utiliser l’interface d’administration fournie par Django.

Nous créons pour cela un fichier polls/admin.py qui contient:

from django.contrib import admin
from polls.models import Poll, Choice

admin.site.register(Poll)
admin.site.register(Choice)

Ainsi Poll et Choice seront disponibles dans l’interface d’administration.

Note

Certaines modifications nécessitent un redémarrage du serveur. Dans la console où le serveur a été activé, appuyez sur Ctrl+C puis relancez python manage.py runserver.

Lorsque nous revenons sur la page http://localhost:8000/admin/ nous allons voir apparaître un nouvel onglet Polls.

Jouer dans la console

Django fournit sa propre console. C’est une simple console Python (où nous pouvons faire exactement les mêmes choses que lorsque nous exécutons la commande python), mais nous pouvons aussi utiliser les outils et modèles de Django.

(workshops) ~/carrots$ python manage.py shell

Lorsque vous êtes dans le shell commencez par:

>>> from polls.models import Poll, Choice

et affichez tous les sondages de la base de données ; mais il n’y a rien ici pour le moment, donc nous obtenons une liste vide:

>>> Poll.objects.all()
[]

Nous créons le premier sondage:

>>> import datetime
>>> p = Poll(question="What's new?", pub_date=datetime.datetime.now())

Sauvegardez le sondage dans la base de données. Pour cela, vous devez toujours appeler la fonction save():

>>> p.save()

Chaque objet dans la base de données est assigné à un identifiant unique:

>>> p.id
1

p est un objet normal dont nous pouvons lire les attributs:

>>> p.question
"What's new?"
>>> p.pub_date
datetime.datetime(2015, 10, 18, 13, 0, 0, 775217)

Après la modification des attributs, nous devons encore appeler save() pour sauvegarder les changements:

>>> p.question = "What's up?"
>>> p.save()

objects.all() retourne une liste de tous les objets de la base de données:

>>> Poll.objects.all()
[<Poll: Poll object>]

Les modèles de Django sont des classes et les classes peuvent avoir des méthodes. Une méthode est une fonction qui prend un paramètre supplémentaire self, représentant l’objet courant (par exemple, le sondage actuel). Les méthodes dans les classes (modèles) vous permettent de définir des comportements supplémentaires ou de modifier les comportements existants.

Une des méthodes par défaut est __str__(), qui vous permet de modifier l’affichage du modèle (un sondage ou une question). <Poll: Poll object> n’indique pas grand-chose. Corrigeons cela en ajoutant la méthode __str__ aux classes Poll et Choice:

class Poll(models.Model):
    # ...
    def __str__(self):
        return self.question

class Choice(models.Model):
    # ...
    def __str__(self):
        return self.choice_text

Django utilisera ces méthodes pour afficher les objets, pas uniquement dans la console, mais aussi dans l’interface d’administration.

Nous pouvons aussi ajouter d’autres méthodes. Dans le fichier carrots/polls/models.py ajoutez (ici, les commentaires #... indiquent où se trouve le code dans le fichier):

import datetime
from django.utils import timezone
# ...
class Poll(models.Model):
    # ...
    def was_published_recently(self):
        return self.pub_date >= datetime.datetime.now() - datetime.timedelta(days=1)

Notez que nous devons importer le module datetime avec import datetime afin de pouvoir manipuler les objets représentant les dates et les heures en Python.

Sauvegardons les changements et lançons une nouvelle fois l’interpréteur avec la commande python manage.py:

>>> from polls.models import Poll, Choice

# Vérifions si notre méthode __str__() fonctionne
>>> Poll.objects.all()
[<Poll: What's up?>]

Jusqu’à présent, nous avions utilisé la méthode all qui vous permettait d’obtenir la liste de tous les objets d’un type défini (par exemple, toutes les questions). Il y a d’autres méthodes qui permettent de trouver des objets répondants à certaines conditions:

# Django fournit une recherche très simple des objets dans la base de données. Voyons quelques exemples.
>>> Poll.objects.filter(id=1)
[<Poll: What's up?>]
>>> Poll.objects.filter(question__startswith='What')
[<Poll: What's up?>]
>>> Poll.objects.get(pub_date__year=2015)
<Poll: What's up?>

# La tentative de récupération d'un objet non présent entrainera une violente protestation de Python.
# Mais nous sommes déjà habitués à cela.
>>> Poll.objects.get(id=2)
Traceback (most recent call last):
    ...
DoesNotExist: Poll matching query does not exist. Lookup parameters were {'id': 2}

# Essayons notre propre méthode.
>>> p = Poll.objects.get(pk=1)
>>> p.was_published_recently()
True

Nous pouvons avoir accès aux réponses (Choice) des questions:

# Pour le moment notre sondage n'inclut aucune question. Ajoutons-en !
>>> p.choice_set.all()
[]

# .. par exemple trois. Nous allons utiliser la méthode "create". En résultat, nous obtenons un objet "Choice".
>>> p.choice_set.create(choice_text='Not much', votes=0)
<Choice: Not much>
>>> p.choice_set.create(choice_text='The sky', votes=0)
<Choice: The sky>
>>> c = p.choice_set.create(choice_text='Just hacking again', votes=0)

# Avec l'objet "Choice" nous pouvons trouver le sondage auquel la réponse correspond.
>>> c.poll
<Poll: What's up?>

# ...et vice versa, toutes les réponses d'un sondage
>>> p.choice_set.all()
[<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]
>>> p.choice_set.count()
3

# Et maintenant quelque chose de plus difficile. Que fait cet appel?
>>> Choice.objects.filter(poll__pub_date__year=2015)
[<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]

# Finalement, enlevons une des questions. Utilisons la méthode ``delete``.
>>> c = p.choice_set.filter(choice_text__startswith='Just hacking')
>>> c.delete()

En résumé

  • Nous créons des modèles en définissant des classes héritant de``models.Model`` du fichier polls/models.py.

  • Après la création d’un nouveau modèle, nous devons nous souvenir d’exécuter python manage.py makemigrations et python manage.py migrate.

  • Pour récupérer tous les objets d’un modèle:

    Poll.objects.all()
    
  • Pour récupérer l’objet qui correspond à une condition:

    Poll.objects.filter(question__startswith='What')
    
  • Pour récupérer un seul objet:

    Poll.objects.get(id=2)
    

Afficher des sites Web

La connexion à l’adresse principale http://localhost:8000/ affiche toujours une affreuse page d’erreur. Cela ne peut plus durer !

Il est préférable de commencer à travailler sur un site Web après avoir préparé un plan des URLs (adresses). Nous savons que nous souhaitons voir une liste de tous les sondages du site, de permettre aux utilisateurs de voter et d’afficher l’ensemble des résultats du sondage.

Ouvrons le fichier urls.py une nouvelle fois et ajoutons quatre nouvelles entrées:

url(r'^polls/$', views.index),
url(r'^polls/(?P<poll_id>\d+)/$', views.detail),
url(r'^polls/(?P<poll_id>\d+)/results/$', views.results),
url(r'^polls/(?P<poll_id>\d+)/vote/$', views.vote),

Nous allons également rajouter une règle pour importer les views

from polls import views

Finalement, le fichier devrait ressembler à:

from django.conf.urls import include, url
from django.contrib import admin
from polls import views

urlpatterns = [
    url(r'^polls/$', views.index),
    url(r'^polls/(?P<poll_id>\d+)/$', views.detail),
    url(r'^polls/(?P<poll_id>\d+)/results/$', views.results),
    url(r'^polls/(?P<poll_id>\d+)/vote/$', views.vote),
    url(r'^admin/', include(admin.site.urls)),
]

Regardons encore une fois cet exemple. Chaque argument passé à la fonction patterns (sauf le tout premier, mais nous reviendrons dessus plus tard) détermine notre motif d’URL (adresse). Le motif est écrit à l’aide des expressions rationnelles. C’est un terme technique compliqué pour un minuscule langage utilisé pour la représentation concise des motifs de texte.

Lorsqu’un utilisateur essaye d’entrer une adresse spécifique dans notre site Web, telle que: http://localhost:8000/polls/1/, Django sélectionne la troisième partie de l’URL, après la barre oblique (dans ce cas, polls/1/) et essaie de la passer dans une expression régulière pour correspondre à urlpatterns. Prenons une de ces expressions en exemple:

r'^polls/(?P<poll_id>\d+)/vote/$'

C’est une chaîne de caractères normale (sauf, peut être pour le r au début, qui est utilisé uniquement par commodité). Lorsque nous essayons d’adapter le texte à la chaîne de caractères (nous pensons toujours au polls/1/), nous devons nous rappeler des règles suivantes:

Expressions rationnelles:

  • Chaque lettre et nombre de l’expression rationnelle s’applique à la même lettre/nombre de la chaîne de caractères adaptée. De même avec la barre oblique (/), l’espace (' ' ), le souligné (_) et le trait d’union (-).
  • ^ s’applique seulement au début de la chaîne de caractères (“le début” est ici un symbole abstrait avant le premier caractère)
  • $ correspond seulement à la fin de la chaîne de caractères (d’une façon similaire au “début”).
  • Le point (.) correspond à n’importe quel caractère.
  • Si plusieurs caractères sont placés dans des crochets, comme ceci: [abde], le groupe compte pour une unité et correspondra à n’importe quel caractère compris dans le groupe.
  • Il y a une notation abrégée pour de tels groupes. Plutôt que d’écrire toutes les minuscules de l’alphabet, nous pouvons écrire [a-z] pour correspondre à n’importe quelle lettre minuscule. De même pour les lettres majuscules [A-Z] ou les nombres [0-9].
  • Correspondre à un nombre peut encore être plus court en utilisant la marque \d.
  • Si après n’importe quelle expression ci-dessus nous plaçons un ? il sera alors considéré comme optionnel. Cela signifie que si dans la chaîne de caractères correspondante il n’y a pas de telle expression, la correspondance restera possible. Si elle existe, alors elle y correspondra.
  • Si nous plaçons un * après l’expression, elle correspondra à n’importe quel nombre de répétitions même pour un nombre de zéro, ou pour ainsi dire, comme s’il était optionnel.
  • Si nous plaçons un + après l’expression elle correspondra à n’importe quel nombre de répétitions, sauf à zéro (l’expression doit apparaitre au moins une fois).
  • Si plusieurs caractères sont placés dans des parenthèses, tel que (\d \d) ils seront traités comme un groupe et tous les modificateurs fonctionneront sur l’ensemble de caractères du groupe. Si vous écrivez aussi avec le (? P <NOM> chaîne), le groupe peut être par la suite appelé avec NOM. C’est très courant lorsque l’on travaille avec Django.

Fiou ... il y a tant de règles, mais personne ne se rappelle vraiment de toutes. Celles ci-dessus sont suffisantes pour la majorité des cas.

Voyez-vous déjà que la simple phrase correspond à polls/1/? Pourquoi ?

Une fois que Django trouve une correspondance il va regarder la seconde partie de la ligne. Elle définit la vue, qui est appelée pour créer le site pour l’utilisateur. Pour polls/1/ cela sera polls.views.detail. Tous les groupes nommés seront transférés à la vue comme argument ayant le même nom.

Première vue

Ok, voyons voir comment cela fonctionne dans la pratique. Malheureusement, en entrant l’adresse http://localhost:8000/polls/1/ cela ne se finit pas très bien:

ViewDoesNotExist at /polls/1/

Could not import polls.views.detail. View does not exist in module polls.views.

Cela est dû à la non-définition de la vue (Django nous indique qu’il cherchait polls.views. detail, malheureusement, il n’a rien trouvé de tel).

Ouvrons le fichier polls/views.py et ajoutons quelques nouvelles fonctionnalités:

from django.http import HttpResponse

def index(request):
    return HttpResponse("Hello, world. You're at the poll index.")

def detail(request, poll_id):
    return HttpResponse("You're looking at poll %s." % poll_id)

def results(request, poll_id):
    return HttpResponse("You're looking at the results of poll %s." % poll_id)

def vote(request, poll_id):
    return HttpResponse("You're voting on poll %s." % poll_id)

Ce sont les vues les plus simples possibles. Elles ne renvoient pas de chaîne de caractères régulières, tel que la fonction qui construit un arbre de Noël en Python, car elles doivent parler le protocole HTTP, qui est un peu plus compliqué (Il serait intéressant ici de voir la page dans un navigateur lorsque vous entrez l’adresse http://localhost:8000/polls/1/).

Une vue qui fait réellement quelque chose

Pour le moment nos vues ne font pas grand-chose. Donnons-leur plus de travail !

Tout ce dont Django attend d’une vue, c’est qu’elle retourne un objet HttpResponse ou qu’elle émette une exception. Tous le reste est sous votre contrôle. Par exemple, nous pouvons utiliser les fonctions que nous avons apprises dans le mode interactif pour afficher les sondages à l’utilisateur.

Au début du fichier polls/views.py ajoutez:

from django.http import HttpResponse
from polls.models import Poll

Étoffez la fonction index pour quelle ressemble à ceci:

def index(request):
    latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5]
    output = ', '.join([p.question for p in latest_poll_list])
    return HttpResponse(output)

Note

Maintenant nous ne donnons plus l’intégralité du fichier car il serait trop long. Seules les changements importants sont rapportés.

Cela fonctionne ! Il y a seulement un problème avec cet exemple : nous définissons dans la vue non seulement ce qui doit être retourné, mais aussi dans quel format cela doit être affiché à l’utilisateur du site. Une des compétences principales d’un développeur, c’est sa faculté à distinguer ces deux choses indépendantes.

Les programmeurs Django y ont pensé et ont décidé de créer un système de templates:

Au début du fichier polls/views.py ajoutez:

from django.template import Context, loader

Ce qui nous permet d’utiliser le système de templates.

Dans le même fichier, étendez la fonction index afin de la faire ressembler à ceci:

def index(request):
    latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5]
    t = loader.get_template('polls/index.html')
    c = Context({
        'latest_poll_list': latest_poll_list,
    })
    return HttpResponse(t.render(c))

Les fonctions: get_template (qui trouve un template) et render (qui transforme un template en un texte qui sera finalement envoyé à l’utilisateur) sont responsables de la gestion de nos templates.

Le code est un peu plus long, mais nous allons bientôt voir que tout est bien plus clair. Par contre, premièrement chargeons la page http://localhost:8000/polls/ pour voir le résultat de notre travail:

TemplateDoesNotExist at /polls/
polls/index.html

Oups ! Bien, nous n’avons toujours pas ajouté de template. Pour ce faire, créez un fichier polls/templates/polls/index.html et insérez à l’intérieur:

{% if latest_poll_list %}
<ul>
    {% for poll in latest_poll_list %}
        <li><a href="/polls/{{ poll.id }}/">{{ poll.question }}</a></li>
    {% endfor %}
</ul>
{% else %}
    <p>No polls are available.</p>
{% endif %}

Note

Les templates de l’application sont situés dans le répertoire templates des applications et la fonction get_template recherche les templates dans ces répertoires, c’est pourquoi nous n’avons pas eu besoin de donner le chemin complet polls/templates/polls/index.html, polls/index.html. suffisait.

Lorsque vous rechargez la page dans le navigateur, vous devriez voir une liste de tous les sondages créés auparavant.

Note

Si vous rafraichissez la page et que voyez toujours une erreur, vous devez redémarrer le serveur. Dans la console où le serveur est, appuyez sur Ctrl + C et exécutez python manage.py runserver une nouvelle fois. Cela devrait maintenant fonctionner.

Note

HTML et CSS sont des formats qui définissent l’apparence des pages Web. Nous allons utiliser les templates de Django pour générer le code HTML. Une bonne description du HTML est présenté dans le livre Interactive Data Visualization for the Web. Les caractéristiques incroyables du Web sont que les codes HTML et CSS de n’importe quel site Web sont publics. Nous vous recommandons de regarder le code de vos sites favoris.

Vous allez devoir utiliser un template dans presque toutes les vues. Par conséquent, Django propose une fonction render qui vous permet de faire cela d’une façon plus courte:

Veuillez corriger le début du fichier polls/views.py pour correspondre à ceci:

from django.shortcuts import render
from polls.models import Poll

Veuillez corriger la fonction index pour ressembler à ceci:

def index(request):
    latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5]
    return render(
        request,
        'polls/index.html',
        {'latest_poll_list': latest_poll_list})

Retourner un code 404

Maintenant, concentrons-nous sur la vue détaillant un sondage - un site qui affiche les questions à partir d’un sondage précis.

Au début du fichier polls/views.py ajoutez:

from django.http import Http404

Http404 est une exception partagée par Django. Nous pouvons utiliser cette exception lorsque notre application ne peut trouver le sondage demandé par l’utilisateur (en écrivant raise Http404). Ainsi, le navigateur affichera la page d’erreur 404.

Note

Vous pouvez changer la page affichée par Django en cas d’erreur 404 (la page n’existe pas) et 500 (erreur inattendue du serveur). Pour ce faire, vous devez créer des templates 404.html et 500.html. Avant de vérifier que cela fonctionne, changez DEBUG dans le fichier settings.py à False. Sinon, Django continuera d’afficher ses pages jaunes.

Changez la fonction detail comme suit:

def detail(request, poll_id):
    try:
        p = Poll.objects.get(id=poll_id)
    except Poll.DoesNotExist:
        raise Http404
    return render(request, 'polls/detail.html', {'poll': p})

Puis créez le fichier polls/templates/polls/detail.html ayant pour contenu:

<h1>{{ poll.question }}</h1>
<ul>
{% for choice in poll.choice_set.all %}
    <li>{{ choice.choice_text }}</li>
{% endfor %}
</ul>

Gestion de formulaire

Changeons le template polls/templates/polls/details.html, en ajoutant un simple formulaire HTML.

Changez le fichier polls/templates/polls/details.html comme suit:

<h1>{{ poll.question }}</h1>

{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}

<form action="/polls/{{ poll.id }}/vote/" method="post">
{% csrf_token %}
{% for choice in poll.choice_set.all %}
    <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}" />
    <label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br />
{% endfor %}
<input type="submit" value="Vote" />
</form>

Note

{% csrf_token %} est une méthode très magique pour protéger vos sites Web d’une nouvelle forme d’attaque contre les utilisateurs. Plus d’information dans la documentation Cross Site Request Forgery.

Les lecteurs attentifs noteront que le formulaire est envoyé à l’adresse /polls/{{ poll.id }}/vote/, qui n’est pas encore supporté par les formulaires. Maintenant nous allons ajouter le support des formulaires.

Au début du fichier polls/views.py ajoutez:

from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from django.shortcuts import get_object_or_404
from polls.models import Choice

Corrigez la fonction vote function, comme suit:

def vote(request, poll_id):
    p = get_object_or_404(Poll, id=poll_id)
    try:
        selected_choice = p.choice_set.get(id=request.POST['choice'])
    except (KeyError, Choice.DoesNotExist):
        # If user would choose the wrong option, show error
        return render(request, 'polls/detail.html', {
            'poll': p,
            'error_message': "You have to choose correct option.",
        })

    # Sauvegarde le nouveau nombre de votes
    selected_choice.votes += 1
    selected_choice.save()
    # Redirige un utilisateur à la vue détaillée du sondage, sur laquelle il ou elle vient de voter
    return HttpResponseRedirect(reverse(results, args=(p.id,))

Dans la vue il y a un ensemble de nouvelles idées que nous n’avons pas encore détaillées.

L’objet request contient les données envoyées par l’utilisateur et request.POST contient les données du formulaire envoyées par l’utilisateur. De cette façon nous savons quelle option a été sélectionnée.

Voilà la question importante. Il peut arriver qu’une vue reçoive une réponse inexistante. Nous devons toujours vérifier les données provenant de l’utilisateur et répondre même aux situations où les données n’ont aucun sens. C’est ce qui arrive dans la clause except. Ensuite nous redirigeons l’utilisateur sur le sondage et affichons l’erreur.

Si l’utilisateur sélectionne une option correcte, nous pouvons augmenter le nombre de votes et sauvegarder les modifications. Ensuite nous effectuons une redirection avec HttpResponseRedirect vers la vue détaillée du sondage précédemment inscrite.

Un autre problème important : après avoir voter nous pouvions juste afficher la page, comme à la fin de la vue des détails (en utilisant render). Malheureusement, cela pourrait provoquer le renvoi du sondage. Si l’utilisateur commence à jouer avec les boutons précédent et suivant du navigateur, ou simplement en rafraîchissant la page (en appuyant sur F5). En résumé, après que le formulaire correct est soumis (dans ce cas, après le vote à un sondage), nous devons effectuer une redirection en utilisant HttpResponseRedirect.

Pour trouver le chemin pour accéder à la vue results, on peut utiliser la fonction reverse en lui passant la fonction en premier paramètre.

À la fin nous devons encore développer une vue pour les résultats du sondage, affichés après un vote.

Corrigez la fonction results, comme suit:

def results(request, poll_id):
    p = get_object_or_404(Poll, id=poll_id)
    return render(request, 'polls/results.html', {'poll': p})

Et créez le fichier polls/templates/polls/results.html, contenant ceci:

<h1>{{ poll.question }}</h1>

<ul>
{% for choice in poll.choice_set.all %}
    <li>{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li>
{% endfor %}
</ul>

<a href="/polls/{{ poll.id }}/">Vote again?</a>

C’est tout ! Entrez l’adresse http://localhost:8000/admin/ et créez plusieurs nouveaux sondages et questions. Puis jouez en votant et invitez d’autres utilisateurs à faire de même.

polls/views.py

from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from django.shortcuts import get_object_or_404

from polls.models import Choice
from django.http import Http404
from django.shortcuts import render
from polls.models import Poll


def index(request):
    latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5]
    return render(
        request,
        'polls/index.html',
        {'latest_poll_list': latest_poll_list})


def detail(request, poll_id):
    try:
        p = Poll.objects.get(id=poll_id)
    except Poll.DoesNotExist:
        raise Http404
    return render(request, 'polls/detail.html', {'poll': p})


def results(request, poll_id):
    p = get_object_or_404(Poll, id=poll_id)
    return render(request, 'polls/results.html', {'poll': p})


def vote(request, poll_id):
    p = get_object_or_404(Poll, id=poll_id)
    try:
        selected_choice = p.choice_set.get(id=request.POST['choice'])
    except (KeyError, Choice.DoesNotExist):
        # if user chooses a wrong option, show error
        return render(request, 'polls/detail.html', {
            'poll': p,
            'error_message': "You have to choose a correct option",
        })

    # Sauvegarde le nombre de votes
    selected_choice.votes += 1
    selected_choice.save()
    # Redirige l'utilisateur à la vue détaillée du sonage sur laquelle il/elle vient de voter
    return HttpResponseRedirect(reverse('results', args=(p.id,)))

urls.py

from django.conf.urls import patterns, include, url

from django.contrib import admin
admin.autodiscover()

urlpatterns = patterns('',
  url(r'^polls/$', views.index),
  url(r'^polls/(?P<poll_id>\d+)/$', views.detail),
  url(r'^polls/(?P<poll_id>\d+)/results/$', views.results),
  url(r'^polls/(?P<poll_id>\d+)/vote/$', views.vote),
  url(r'^admin/', include(admin.site.urls)),
)

polls/models.py

from django.db import models

class Poll(models.Model):
    question = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')

    def __str__(self):
        return self.question


class Choice(models.Model):
    poll = models.ForeignKey(Poll)
    choice_text = models.CharField(max_length=200)
    votes = models.IntegerField(default=0)

    def __str__(self):
        return self.choice_text

Code de conduite Geek Girls Carrots

Geek Girls Carrots (aussi désigné par l’acronyme GGC) cherche à favoriser un environnement d’apprentissage encourageant et accompagné dans lequel les individus de tous horizons peuvent participer confortablement, sans distinction de race, couleur, origine, âge, religion, opinion politique, capacité physique, identité de genre, orientation sexuelle ou statut socio-economique.

Geek Girls Carrots s’engage à mettre en place une communauté respectueuse pour chacun-e. Nous ne tolérons ni le harcèlement, ni l’intimidation ou la brutalisation de quelque sorte qu’elle soit à l’égard des membres de la communauté.

Cela ne concerne pas uniquement les membres de GGC mais s’applique à toute personne en relation avec les membres, développeurs, intégrateurs de communauté Geek Girls Carrots au sens large, au cours des événements ou de toute autre interaction.

Le harcèlement inclut des propos blessants, par voie verbale ou électronique, à propos des choix ou des caractéristiques d’une personne; des images ou des commentaires sexuels en public ou sur des espaces en ligne; l’intimidation délibérée, la brutalisation, la traque, le suivi d’une personne, la prise de photos ou de vidéos sans l’accord de la personne, des propos perturbateurs de manière verbale, sur IRC (Internet Relay Chat), lors de rendez-vous téléphoniques, lors de réunions ou lors de tout autre événement; des contacts physiques innapropriés ou des attouchements sexuels non sollicités.

Les participants auxquels est demandé l’arrêt de ce type de comportement sont priés de s’exécuter immédiatement.

Si un participant commence à pratiquer ce genre de comportement, les représentants de la communauté pourront mettre en place les actions qu’ils jugent nécessaire, incluant un avertissement du contrevenant, l’expulsion des événements de Girls Carrots ou l’expulsion des listes de diffusion, conversations IRC, groupes de travail ou de tout autre moyen de communication électronique afin de résoudre le problème. Cela peut même aller jusqu’à la radiation de la communauté si cela s’avère nécessaire.

Si vous êtes harcelé-e, observez que quelqu’un est harcelé-e ou avez n’importe quel autre soucis, merci d’intervenir et de prendre contact avec un-e membre de GGC, un-e administrateur-trice IRC, un-e administrateur-trice du site internet, une des organisateurs-trices / représentant-e-s lors d’un événement sous la responsabilité de GGC.

Une partie de ce document a été adaptée du code de conduite des PyLadies, lui-même adapté du code de conduite de la Plone Foundation. Ce code de conduite a été annoté et complété pour refléter les spécificités de la communauté Geek Girls Carrots.