# Documentation Django 5.2.8

# Écriture de votre première application Django, 3ème partie

Ce tutoriel commence là où le tutoriel 2 s’achève. Nous continuons l’application de sondage Web et allons nous focaliser sur la création de l’interface publique – les « vues ».

Où obtenir de l’aide :

Si vous rencontrez des problèmes dans le parcours de ce tutoriel, rendez-vous dans la section Obtenir de l’aide de la FAQ.

## Aperçu

Une vue est un « type » de page Web dans votre application Django qui sert généralement à une fonction précise et possède un gabarit spécifique. Par exemple, dans une application de blog, vous pouvez avoir les vues suivantes :

- La page d’accueil du blog – affiche quelques-uns des derniers billets.

- La page de « détail » d’un billet – lien permanent vers un seul billet.

- La page d’archives pour une année – affiche tous les mois contenant des billets pour une année donnée.

- La page d’archives pour un mois – affiche tous les jours contenant des billets pour un mois donné.

- La page d’archives pour un jour – affiche tous les billets pour un jour donné.

- Action de commentaire – gère l’écriture de commentaires sur un billet donné.

Dans notre application de sondage, nous aurons les quatre vues suivantes :

- La page de sommaire des questions – affiche quelques-unes des dernières questions.

- La page de détail d’une question – affiche le texte d’une question, sans les résultats mais avec un formulaire pour voter.

- La page des résultats d’une question – affiche les résultats d’une question particulière.

- Action de vote – gère le vote pour un choix particulier dans une question précise.

Dans Django, les pages Web et les autres contenus sont générés par des vues. Chaque vue est représentée par une fonction Python (ou une méthode dans le cas des vues basées sur des classes). Django choisit une vue en examinant l’URL demandée (pour être précis, la partie de l’URL après le nom de domaine).

Dans votre expérience sur le Web, vous avez certainement rencontré des perles comme par exemple `ME2/Sites/dirmod.htm?sid=&type=gen&mod=Core+Pages&gid=A6CD4967199A42D9B65B1B`. Vous serez certainement rassuré en sachant que Django permet des styles d’URL bien plus élégants que cela.

Un modèle d’URL est la forme générale d’une URL ; par exemple : `/archive/<année>/<mois>/`.

Pour passer de l’URL à la vue, Django utilise ce qu’on appelle des configurations d’URL (URLconf). Une configuration d’URL associe des motifs d’URL à des vues.

Ce tutoriel fournit des instructions de base sur l’utilisation des configurations d’URL, et vous pouvez consultez Distribution des URL pour plus de détails.

## Écriture de vues supplémentaires

Ajoutons maintenant quelques vues supplémentaires dans `polls/views.py`. Ces vues sont légèrement différentes, car elles acceptent un paramètre :

`polls/views.py`

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

    def results(request, question_id):
        response = "You're looking at the results of question %s."
        return HttpResponse(response % question_id)

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

Liez ces nouvelles vues avec leurs URL dans le module `polls.urls` en ajoutant les appels `path()` suivants :

`polls/urls.py`

    from django.urls import path

    from . import views

    urlpatterns = [
        # ex: /polls/
        path("", views.index, name="index"),
        # ex: /polls/5/
        path("<int:question_id>/", views.detail, name="detail"),
        # ex: /polls/5/results/
        path("<int:question_id>/results/", views.results, name="results"),
        # ex: /polls/5/vote/
        path("<int:question_id>/vote/", views.vote, name="vote"),
    ]

Ouvrez votre navigateur à l’adresse « /polls/34/ ». La fonction `detail()` sera exécutée et affichera l’ID fourni dans l’URL. Essayez aussi « /polls/34/results/ » et « /polls/34/vote/ », elles afficheront les pages modèles de résultats et de votes.

Lorsque quelqu’un demande une page de votre site Web, par exemple « /polls/34/ », Django charge le module Python `mysite.urls` parce qu’il est mentionné dans le réglage `ROOT_URLCONF`. Il trouve la variable nommée `urlpatterns` et parcourt les motifs dans l’ordre. Après avoir trouvé la correspondance `'polls/'`, il retire le texte correspondant (`"polls/"`) et passe le texte restant – `"34/"` – à la configuration d’URL “polls.urls” pour la suite du traitement. Là, c’est `'<int:question_id>/'` qui correspond ce qui aboutit à un appel à la vue `detail()` comme ceci :

    detail(request=<HttpRequest object>, question_id=34)

La partie `question_id=34` vient de `<int:question_id>`. En utilisant des chevrons, cela « capture » une partie de l’URL l’envoie en tant que paramètre nommé à la fonction de vue ; la partie `question_id` de la chaîne définit le nom qui va être utilisé pour identifier le motif trouvé, et la partie `int` est un convertisseur qui détermine ce à quoi les motifs doivent correspondre dans cette partie du chemin d’URL. Le caractère deux-points (`:`) sépare le convertisseur du nom de la partie capturée.

## Écriture de vues qui font réellement des choses

Chaque vue est responsable de faire une des deux choses suivantes : retourner un objet `HttpResponse` contenant le contenu de la page demandée, ou lever une exception, comme par exemple `Http404`. Le reste, c’est votre travail.

Votre vue peut lire des entrées depuis une base de données, ou pas. Elle peut utiliser un système de gabarits comme celui de Django – ou un système de gabarits tiers – ou pas. Elle peut générer un fichier PDF, produire de l’XML, créer un fichier ZIP à la volée, tout ce que vous voulez, en utilisant les bibliothèques Python que vous voulez.

Voilà tout ce que veut Django : `HttpResponse` ou une exception.

Parce que c’est pratique, nous allons utiliser l’API de base de données de Django, que nous avons vu dans le tutoriel 2. Voici une ébauche d’une nouvelle vue `index()`, qui affiche les 5 derniers sondages, séparés par des virgules et classés par date de publication :

`polls/views.py`

    from django.http import HttpResponse

    from .models import Question

    def index(request):
        latest_question_list = Question.objects.order_by("-pub_date")[:5]
        output = ", ".join([q.question_text for q in latest_question_list])
        return HttpResponse(output)

    # Leave the rest of the views (detail, results, vote) unchanged

There’s a problem here, though: the page’s design is hardcoded in the view. If you want to change the way the page looks, you’ll have to edit this Python code. So let’s use Django’s template system to separate the design from Python by creating a template that the view can use.

Tout d’abord, créez un répertoire nommé `templates` dans votre répertoire `polls`. C’est là que Django recherche les gabarits.

Le paramètre `TEMPLATES` de votre projet indique comment Django va charger et produire les gabarits. Le fichier de réglages par défaut configure un moteur `DjangoTemplates` dont l’option `APP_DIRS` est définie à `True`. Par convention, `DjangoTemplates` recherche un sous-répertoire « templates » dans chaque application figurant dans `INSTALLED_APPS`.

Dans le répertoire `templates` que vous venez de créer, créez un autre répertoire nommé `polls` dans lequel vous placez un nouveau fichier `index.html`. Autrement dit, le chemin de votre gabarit doit être `polls/templates/polls/index.html`. Conformément au fonctionnement du chargeur de gabarit `app_directories` (cf. explication ci-dessus), vous pouvez désigner ce gabarit dans Django par `polls/index.html`.

Espace de noms des gabarits

Il serait aussi possible de placer directement nos gabarits dans `polls/templates` (plutôt que dans un sous-répertoire `polls`), mais ce serait une mauvaise idée. Django choisit le premier gabarit qu’il trouve pour un nom donné et dans le cas où vous avez un gabarit de même nom dans une autre application, Django ne fera pas la différence. Il faut pouvoir indiquer à Django le bon gabarit, et la meilleure manière de faire cela est d’utiliser des espaces de noms. C’est-à-dire que nous plaçons ces gabarits dans un autre répertoire portant le nom de l’application.

Insérez le code suivant dans ce gabarit :

`polls/templates/polls/index.html`

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

Note

Pour ne pas rallonger le tutoriel, tous les exemples de gabarit utilisent du HTML incomplet. Dans vos propres projets, vous devriez utiliser des documents HTML complets.

Mettons maintenant à jour notre vue `index` dans `polls/views.py` pour qu’elle utilise le template :

`polls/views.py`

    from django.http import HttpResponse
    from django.template import loader

    from .models import Question

    def index(request):
        latest_question_list = Question.objects.order_by("-pub_date")[:5]
        template = loader.get_template("polls/index.html")
        context = {"latest_question_list": latest_question_list}
        return HttpResponse(template.render(context, request))

Ce code charge le gabarit appelé `polls/index.html` et lui fournit un contexte. Ce contexte est un dictionnaire qui fait correspondre des objets Python à des noms de variables de gabarit.

Chargez la page en appelant l’URL « /polls/ » dans votre navigateur et vous devriez voir une liste à puces contenant la question « What’s up » du tutoriel 2. Le lien pointe vers la page de détail de la question.

### Un raccourci : `render()`

Il est très courant de charger un gabarit, remplir un contexte et renvoyer un objet `HttpResponse` avec le résultat du gabarit interprété. Django fournit un raccourci. Voici la vue `index()` complète, réécrite :

`polls/views.py`

    from django.shortcuts import render

    from .models import Question

    def index(request):
        latest_question_list = Question.objects.order_by("-pub_date")[:5]
        context = {"latest_question_list": latest_question_list}
        return render(request, "polls/index.html", context)

Notez qu’une fois que nous avons fait ceci dans toutes nos vues, nous n’avons plus à importer `loader` et `HttpResponse` (il faut conserver `HttpResponse` tant que les méthodes initiales pour `detail`, `results` et `vote` sont présentes).

La fonction `render()` prend comme premier paramètre l’objet requête, un nom de gabarit comme deuxième paramètre et un dictionnaire comme troisième paramètre facultatif. Elle retourne un objet `HttpResponse` composé par le gabarit interprété avec le contexte donné.

## Les erreurs 404

Attaquons-nous maintenant à la vue du détail d’une question – la page qui affiche le texte de la question pour un sondage donné. Voici la vue :

`polls/views.py`

    from django.http import Http404
    from django.shortcuts import render

    from .models import Question

    # ...
    def detail(request, question_id):
        try:
            question = Question.objects.get(pk=question_id)
        except Question.DoesNotExist:
            raise Http404("Question does not exist")
        return render(request, "polls/detail.html", {"question": question})

Le nouveau concept ici : la vue lève une exception de type `Http404` si une question avec l’ID demandé n’existe pas.

Nous parlerons un peu plus tard de ce que pourriez mettre dans le gabarit `polls/detail.html`, mais si vous voulez avoir rapidement un exemple qui fonctionne, écrivez simplement ceci :

`polls/templates/polls/detail.html`

    {{ question }}

et vous obtiendrez un résultat élémentaire.

### Un raccourci : `get_object_or_404()`

Il est très courant d’utiliser `get()` et de lever une exception `Http404` si l’objet n’existe pas. Django fournit un raccourci. Voici la vue `detail()` réécrite :

`polls/views.py`

    from django.shortcuts import get_object_or_404, render

    from .models import Question

    # ...
    def detail(request, question_id):
        question = get_object_or_404(Question, pk=question_id)
        return render(request, "polls/detail.html", {"question": question})

La fonction `get_object_or_404()` prend un modèle Django comme premier paramètre et un nombre arbitraire de paramètres mots-clés, qu’il transmet à la méthode `get()` du gestionnaire du modèle. Elle lève une exception `Http404` si l’objet n’existe pas.

Philosophie

Pourquoi utiliser une fonction auxiliaire `get_object_or_404()` plutôt que d’intercepter automatiquement une exception `ObjectDoesNotExist` à un plus haut niveau, ou laisser l’API modèle lever une exception `Http404` à la place de `ObjectDoesNotExist` ?

Parce que cela couplerait la couche de gestion des modèles à la couche de vue. Un des buts principaux de la conception de Django et de garder un couplage le plus faible possible. Un peu de couplage contrôlé est introduit dans le module `django.shortcuts`.

Il y a aussi une fonction `get_list_or_404()`, qui fonctionne comme `get_object_or_404()`, sauf qu’elle utilise `filter()` au lieu de la méthode `get()`. Elle lève une exception `Http404` si la liste est vide.

## Utilisation du système de gabarits

Revenons à la vue `detail()` de notre application de sondage. Étant donné la variable de contexte `question`, voici à quoi le gabarit `polls/detail.html` pourrait ressembler :

`polls/templates/polls/detail.html`

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

Le système de gabarits utilise une syntaxe d’accès aux attributs de variables à l’aide de points. Dans cet exemple avec `{{ question.question_text }}`, Django commence par rechercher un dictionnaire dans l’objet `question`. En cas d’échec, il cherche un attribut – qui fonctionne dans ce cas. Si la recherche d’attribut n’avait pas fonctionné, Django aurait essayé une recherche d’index sur une liste.

L’appel de méthode a lieu dans la boucle `{% for %}` : `question.choice_set.all` est interprété comme le code Python `question.choice_set.all()`, qui renvoie un itérable d’objets `Choice` et qui convient pour l’utilisation de la balise `{% for %}`.

Voir le guide des gabarits pour plus d’informations sur les gabarits.

## Suppression des URL codés en dur dans les gabarits

Rappelez-vous, lorsque nous avons ajouté le lien vers la question dans le gabarit `polls/index.html`, le lien a été partiellement codé en dur comme ceci :

    <li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>

Le problème de cette approche codée en dur et fortement couplée est qu’il devient fastidieux de modifier les URL dans des projets qui ont beaucoup de gabarits. Cependant, comme vous avez défini le paramètre `name` dans les fonctions `path()` du module `polls.urls`, vous pouvez supprimer la dépendance en chemins d’URL spécifiques définis dans les configurations d’URL en utilisant la balise de gabarit `{% url %}` :

    <li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>

Le principe de ce fonctionnement est que l’URL est recherchée dans les définitions du module `polls.urls`. Ci-dessous, vous pouvez voir exactement où le nom d’URL de « detail » est défini :

    ...
    # the 'name' value as called by the {% url %} template tag
    path("<int:question_id>/", views.detail, name="detail"),
    ...

Si vous souhaitez modifier l’URL de détail des sondages, par exemple sur le modèle `polls/specifics/12/`, il suffit de faire la modification dans `polls/urls.py` au lieu de devoir toucher au contenu du ou des gabarits :

    ...
    # added the word 'specifics'
    path("specifics/<int:question_id>/", views.detail, name="detail"),
    ...

## Espaces de noms et noms d’URL

Le projet du tutoriel ne contient qu’une seule application, `polls`. Dans des projets Django réels, il peut y avoir cinq, dix, vingt applications ou plus. Comment Django arrive-t-il à différencier les noms d’URL entre elles ? Par exemple, l’application `polls` possède une vue `detail` et il se peut tout à fait qu’une autre application du même projet en possède aussi une. Comment peut-on indiquer à Django quelle vue d’application il doit appeler pour une URL lors de l’utilisation de la balise de gabarit `{% url %}` ?

La réponse est donnée par l’ajout d’espaces de noms à votre configuration d’URL. Dans le fichier `polls/urls.py`, ajoutez une variable `app_name` pour définir l’espace de nom de l’application :

`polls/urls.py`

    from django.urls import path

    from . import views

    app_name = "polls"
    urlpatterns = [
        path("", views.index, name="index"),
        path("<int:question_id>/", views.detail, name="detail"),
        path("<int:question_id>/results/", views.results, name="results"),
        path("<int:question_id>/vote/", views.vote, name="vote"),
    ]

Modifiez maintenant la partie suivante du gabarit `polls/index.html` :

`polls/templates/polls/index.html`

    <li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>

pour qu’elle pointe vers la vue « detail » à l’espace de nom correspondant :

`polls/templates/polls/index.html`

    <li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>

Lorsque vous êtes à l’aise avec l’écriture des vues, lisez la partie 4 de ce tutoriel pour apprendre les bases de la gestion de formulaires et des vues génériques.