aboutsummaryrefslogtreecommitdiff
path: root/files/fr/learn/server-side/django
diff options
context:
space:
mode:
authorSphinxKnight <SphinxKnight@users.noreply.github.com>2022-03-16 17:52:18 +0100
committerGitHub <noreply@github.com>2022-03-16 17:52:18 +0100
commit500f444d23a7a758da229ebe6b9691cc5d4fe731 (patch)
treeca277561f7f3c5f2c9c3e80a895ac32f30852238 /files/fr/learn/server-side/django
parentde831e4687986c3a60b9ced69ce9faefda8df4b9 (diff)
downloadtranslated-content-500f444d23a7a758da229ebe6b9691cc5d4fe731.tar.gz
translated-content-500f444d23a7a758da229ebe6b9691cc5d4fe731.tar.bz2
translated-content-500f444d23a7a758da229ebe6b9691cc5d4fe731.zip
Fix #4269 - Removes empty/special characters (#4270)
* Remove ufeff * Remove u2064 * Remove u2062 * Replace u202f followed by : with &nbsp;: * Replace u202f next to « or » with &nbsp; and « or » * Replace u202f followed by ; with &nbsp;; * Replace u202f followed by ! with &nbsp; * Replace u202f followed by ? with &nbsp;? * Replace remaining u202f with classical space * Replace u200b surrounded by space with classical space * Replace u200b surrounded by space with classical space - again (repeated) * Remove remaining u200b * Remove u200a * Replace u2009 with &nbsp; * Remove u00ad * Replace u00a0 followed by : ! or ? with &nbsp; and punctuation * Replace u00a0 surrounded « or » with &nbsp; and punctuation * Replace u00a0 followed by whitespaces * Replace u00a0 preceded by whitespaces * Replace u00a0 followed by a newline with a newline * Replace u00a0 followed by a newline with a newline - Take2 * Replace u00a0 followed by a ; &nbsp; and punctuation * Remove u00a0 followed by , * Remove u00a0 in indentation spaces with \n([ ]*)([\u00a0])([ ]*) * Manual replacement of ([\u00a0])([ ]+) * Replace remaining ([\u00a0]+) by a space * cleaning empty elements * remove ufe0f * Remove u00a0 and u202f after merging against updated main * remove double whitespace using (\w)( )(\w)
Diffstat (limited to 'files/fr/learn/server-side/django')
-rw-r--r--files/fr/learn/server-side/django/admin_site/index.md20
-rw-r--r--files/fr/learn/server-side/django/generic_views/index.md172
-rw-r--r--files/fr/learn/server-side/django/home_page/index.md46
-rw-r--r--files/fr/learn/server-side/django/introduction/index.md96
-rw-r--r--files/fr/learn/server-side/django/models/index.md86
-rw-r--r--files/fr/learn/server-side/django/skeleton_website/index.md74
-rw-r--r--files/fr/learn/server-side/django/testing/index.md630
-rw-r--r--files/fr/learn/server-side/django/tutorial_local_library_website/index.md4
8 files changed, 564 insertions, 564 deletions
diff --git a/files/fr/learn/server-side/django/admin_site/index.md b/files/fr/learn/server-side/django/admin_site/index.md
index 6752286d3d..27733d46e2 100644
--- a/files/fr/learn/server-side/django/admin_site/index.md
+++ b/files/fr/learn/server-side/django/admin_site/index.md
@@ -198,7 +198,7 @@ Pour le moment, toutes les classes d’administration sont vides (cf. pass), par
### Configurer les vues en liste
-La liste des auteurs (objet `Author`) est affichée dans l'application _LocalLibrary_ à l'aide du nom généré par la méthode  `__str__()`. Ceci fonctionne bien, judqu'à ce que vous aurez de nombreux auteurs et éventuellement des doublons parmi ces auteurs. Pour bien les différencier, ou simplement parce que vous souhaitez avoir directement plus d'information, vous allez utiliser la directive [list_display](https://docs.djangoproject.com/fr/2.2/ref/contrib/admin/#django.contrib.admin.ModelAdmin) pour ajouter d'autres champs de l'objet `Author`.
+La liste des auteurs (objet `Author`) est affichée dans l'application _LocalLibrary_ à l'aide du nom généré par la méthode `__str__()`. Ceci fonctionne bien, judqu'à ce que vous aurez de nombreux auteurs et éventuellement des doublons parmi ces auteurs. Pour bien les différencier, ou simplement parce que vous souhaitez avoir directement plus d'information, vous allez utiliser la directive [list_display](https://docs.djangoproject.com/fr/2.2/ref/contrib/admin/#django.contrib.admin.ModelAdmin) pour ajouter d'autres champs de l'objet `Author`.
Modifiez votre classe `AuthorAdmin` comme décrit ci-dessous (vous pouvez copier et coller le code). Les noms de champs à afficher dans la liste sont déclarés dans un tuple dans l'ordre requis. Ils sont identiques à ceux spécifiés dans votre modèle d'objet `Author`.
@@ -211,7 +211,7 @@ Si vous accèdez à la page d'administration des auteurs, vous devriez obtenir u
![Admin Site - Improved Author List](admin_improved_author_list.png)
-Pour les livres, nous allons visulaiser les objets  `Book` en affichant les champs `author` and `genre`. Le champs `author` est de type `ForeignKey` décrivant une relation un à n. En conséquence, nous afficherons l'élément produit par la méthode `__str__()` de l'objet `Author` pour l'instance associée à votre livre. Le genre est une relation n à n, donc nous allons avoir à traiter son affichage de manière particulière. Modifiez la classe `BookAdmin` comme suit :
+Pour les livres, nous allons visulaiser les objets `Book` en affichant les champs `author` and `genre`. Le champs `author` est de type `ForeignKey` décrivant une relation un à n. En conséquence, nous afficherons l'élément produit par la méthode `__str__()` de l'objet `Author` pour l'instance associée à votre livre. Le genre est une relation n à n, donc nous allons avoir à traiter son affichage de manière particulière. Modifiez la classe `BookAdmin` comme suit :
```python
class BookAdmin(admin.ModelAdmin):
@@ -220,12 +220,12 @@ class BookAdmin(admin.ModelAdmin):
Le champ genre représente une relation n à n (`ManyToManyField`) qui ne peut pas être prise en charge par la directive `list_display`. Le coût d'accès à la base de donnée peut être important et donc le cadriciel se protège de ce phénomène. A la place, nous devons définir une fonction(`display_genre`) qui permettra de traiter l'affichage des informations souhaitées pour le genre.
-> **Note :** C'est dans un but pédagogique que nous recherchons ici l'affichage du `genre` qui n'a peut-être pas nécessaire d'intérêt et peut représenter un coût d'accès. Nous montrons, ici, comment appler les fonctions dans vos modèles ce qui sera très utile pour la suite de vos applications  — par exemple pour ajouter un lien de suppression de vos enregistrements en liste.
+> **Note :** C'est dans un but pédagogique que nous recherchons ici l'affichage du `genre` qui n'a peut-être pas nécessaire d'intérêt et peut représenter un coût d'accès. Nous montrons, ici, comment appler les fonctions dans vos modèles ce qui sera très utile pour la suite de vos applications — par exemple pour ajouter un lien de suppression de vos enregistrements en liste.
Ajoutez le code ci-dessous dans votre modèle d'objet `Book` (concrètement dans le fichier **locallibrary/catalog/models.py**). Cette fonction génère une chaîne de caractère contenant les trois premières valeurs de tous les genres (s'ils existent) et créer une courte destription (`short_description`) qui sera utilisé par le site d'administration avec cette méthode.
```python
-    def display_genre(self):
+ def display_genre(self):
"""Create a string for the Genre. This is required to display genre in Admin."""
return ', '.join(genre.name for genre in self.genre.all()[:3])
@@ -300,7 +300,7 @@ class BookInstanceAdmin(admin.ModelAdmin):
Chaque section peut avoir un titre (ou aucun si vous indiquez la valeur `None`) et des champs regroupés à l'aide de tuples enregistrés dans un dictionnaire — le schéma de déclaration peut paraître compliqué à décrire mais assez aisé à comprendre à la lecture du code ci-dessus formaté pour être plus compréhensible.
-Le résultat de cette description devrait vous apparaître de manière analogue à celle présente  ci-dessous :
+Le résultat de cette description devrait vous apparaître de manière analogue à celle présente ci-dessous :
![Admin Site - Improved BookInstance Detail with sections](admin_improved_bookinstance_detail_sections.png)
@@ -308,16 +308,16 @@ Le résultat de cette description devrait vous apparaître de manière analogue
Parfois, il peut être très utile d'ajouter à l'affichage des éléments associés en même temps. C'est le cas, par exemple, pour les copies d'ouvrage associés à un livre en bibliothèque. Il est utile pour le bibliothécaire de disposer à la fois des informations sur le livre et des copies présentes ou non en rayonnage..
-Pour cela, vous pouvez utiliser un d'objet pour un affichage horizontal ([TabularInline](https://docs.djangoproject.com/fr/2.2/ref/contrib/admin/#django.contrib.admin.TabularInline)) ou vertical ([StackedInline)](https://docs.djangoproject.com/fr/2.2/ref/contrib/admin/#django.contrib.admin.StackedInline) (qui n'est autre que l'affichage standard des données). Modifiez le code associé à votre modèle `BookInstance` dans le fichier **admin.py** pour disposer des informations _inline_ à l'affichage des informations sur votre objet `Book`. Gardez en mémoire que c'est l'objet  `BookAdmin` qui gère l'affichage les informations de l'objet `Book`; c'est donc `BookAdmin` il doit donc être modifié :
+Pour cela, vous pouvez utiliser un d'objet pour un affichage horizontal ([TabularInline](https://docs.djangoproject.com/fr/2.2/ref/contrib/admin/#django.contrib.admin.TabularInline)) ou vertical ([StackedInline)](https://docs.djangoproject.com/fr/2.2/ref/contrib/admin/#django.contrib.admin.StackedInline) (qui n'est autre que l'affichage standard des données). Modifiez le code associé à votre modèle `BookInstance` dans le fichier **admin.py** pour disposer des informations _inline_ à l'affichage des informations sur votre objet `Book`. Gardez en mémoire que c'est l'objet `BookAdmin` qui gère l'affichage les informations de l'objet `Book`; c'est donc `BookAdmin` il doit donc être modifié :
```python
class BooksInstanceInline(admin.TabularInline):
-    model = BookInstance
+ model = BookInstance
@admin.register(Book)
class BookAdmin(admin.ModelAdmin):
-    list_display = ('title', 'author', 'display_genre')
-    inlines = [BooksInstanceInline]
+ list_display = ('title', 'author', 'display_genre')
+ inlines = [BooksInstanceInline]
```
Si vous allez consulter un livre, vous devriez pouvoir, au bas de la page, consulter la liste des copies enregistrées :
@@ -341,7 +341,7 @@ Beaucoup de sujets ont été abordés dans ce chapitre... Vous avez acquis les b
## A voir aussi
-- [Ecrire sa première application Dajngo, 2ème partie](https://docs.djangoproject.com/fr/2.2/intro/tutorial02/#introducing-the-django-admin)  (Django docs)
+- [Ecrire sa première application Dajngo, 2ème partie](https://docs.djangoproject.com/fr/2.2/intro/tutorial02/#introducing-the-django-admin) (Django docs)
- [Le site d'administration de Django](https://docs.djangoproject.com/fr/2.2/ref/contrib/admin/) (Django Docs)
{{PreviousMenuNext("Learn/Server-side/Django/Models", "Learn/Server-side/Django/Home_page", "Learn/Server-side/Django")}}
diff --git a/files/fr/learn/server-side/django/generic_views/index.md b/files/fr/learn/server-side/django/generic_views/index.md
index ce38891f7d..edfe0820b7 100644
--- a/files/fr/learn/server-side/django/generic_views/index.md
+++ b/files/fr/learn/server-side/django/generic_views/index.md
@@ -53,12 +53,12 @@ La page de liste des livres va afficher une liste de tous les enregistrements de
### Mappage d'URL
-Ouvrez le fichier **/catalog/urls.py**, et copiez-y la ligne en gras ci-dessous. Comme pour la page d'index, cette fonction `path()` définit un pattern destiné à identifier l'URL (**'books/'**), une fonction vue qui sera appelée si l'URL correspond (`views.BookListView.as_view()`), et un nom pour ce mappage particulier.
+Ouvrez le fichier **/catalog/urls.py**, et copiez-y la ligne en gras ci-dessous. Comme pour la page d'index, cette fonction `path()` définit un pattern destiné à identifier l'URL (**'books/'**), une fonction vue qui sera appelée si l'URL correspond (`views.BookListView.as_view()`), et un nom pour ce mappage particulier.
```python
urlpatterns = [
path('', views.index, name='index'),
-  path('books/', views.BookListView.as_view(), name='books'),
+ path('books/', views.BookListView.as_view(), name='books'),
]
```
@@ -70,7 +70,7 @@ En Django, on accède à la fonction appropriée d'une vue basée sur classe en
### Vue (basée sur classe)
-Nous pourrions assez aisément écrire la vue "liste de livres" comme une fonction ordinaire (comme notre précédente vue "index"), qui interrogerait la base de données pour tous les livres, et qui ensuite appellerait `render()` pour passer la liste à un template spécifique. À la place, cependant, nous allons utiliser une vue "liste" générique, basée sur une classe (`ListView`), une classe qui hérite d'une vue existante. Parce que la vue générique implémente déjà la plupart des fonctionnalités dont nous avons besoin et suit les meilleures pratiques Django, nous pourrons créer une vue "liste" plus robuste avec moins de code, moins de répétition, et au final moins de maintenance.
+Nous pourrions assez aisément écrire la vue "liste de livres" comme une fonction ordinaire (comme notre précédente vue "index"), qui interrogerait la base de données pour tous les livres, et qui ensuite appellerait `render()` pour passer la liste à un template spécifique. À la place, cependant, nous allons utiliser une vue "liste" générique, basée sur une classe (`ListView`), une classe qui hérite d'une vue existante. Parce que la vue générique implémente déjà la plupart des fonctionnalités dont nous avons besoin et suit les meilleures pratiques Django, nous pourrons créer une vue "liste" plus robuste avec moins de code, moins de répétition, et au final moins de maintenance.
Ouvrez le fichier **catalog/views.py**, et copiez-y le code suivant à la fin :
@@ -81,9 +81,9 @@ class BookListView(generic.ListView):
model = Book
```
-C'est tout ! La vue générique va adresser une requête à la base de données pour obtenir tous les enregistrements du modèle spécifié (`Book`), et ensuite rendre un template situé à l'adresse **/locallibrary/catalog/templates/catalog/book_list.html** (que nous allons créer ci-dessous). À l'intérieur du template vous pouvez accéder à la liste de livres grâce à la variable de template appelée `object_list` OU `book_list` (c'est-à-dire l'appellation générique "`the_model_name_list`").
+C'est tout ! La vue générique va adresser une requête à la base de données pour obtenir tous les enregistrements du modèle spécifié (`Book`), et ensuite rendre un template situé à l'adresse **/locallibrary/catalog/templates/catalog/book_list.html** (que nous allons créer ci-dessous). À l'intérieur du template vous pouvez accéder à la liste de livres grâce à la variable de template appelée `object_list` OU `book_list` (c'est-à-dire l'appellation générique "`the_model_name_list`").
-> **Note :** Ce chemin étrange vers le lieu du template n'est pas une faute de frappe : les vues génériques cherchent les templates dans `/application_name/the_model_name_list.html` (`catalog/book_list.html` dans ce cas) à l'intérieur du répertoire `/application_name/templates/` (`/catalog/templates/`).
+> **Note :** Ce chemin étrange vers le lieu du template n'est pas une faute de frappe : les vues génériques cherchent les templates dans `/application_name/the_model_name_list.html` (`catalog/book_list.html` dans ce cas) à l'intérieur du répertoire `/application_name/templates/` (`/catalog/templates/`).
Vous pouvez ajouter des attributs pour changer le comportement par défaut utilisé ci-dessus. Par exemple, vous pouvez spécifier un autre fichier de template si vous souhaitez avoir plusieurs vues qui utilisent ce même modèle, ou bien vous pourriez vouloir utiliser un autre nom de variable de template, si book_list n'est pas intuitif par rapport à l'usage que vous faites de vos templates. Probablement, le changement le plus utile est de changer/filtrer le sous-ensemble des résultats retournés : au lieu de lister tous les livres, vous pourriez lister les 5 premiers livres lus par d'autres utilisateurs.
@@ -99,28 +99,28 @@ class BookListView(generic.ListView):
Bien que nous n'ayons pas besoin de le faire ici, sachez qu'il vous est possible de ré-écrire des méthodes de classe.
-Par exemple, nous pouvons ré-écrire la méthode `get_queryset()` pour changer la liste des enregistrements retournés. Cette façon de faire est plus flexible que simplement définir l'attribut `queryset`, comme nous l'avons fait dans le précédent fragment de code (bien qu'il n'y ait pas vraiment d'intérêt dans ce cas) :
+Par exemple, nous pouvons ré-écrire la méthode `get_queryset()` pour changer la liste des enregistrements retournés. Cette façon de faire est plus flexible que simplement définir l'attribut `queryset`, comme nous l'avons fait dans le précédent fragment de code (bien qu'il n'y ait pas vraiment d'intérêt dans ce cas) :
```python
class BookListView(generic.ListView):
model = Book
-    def get_queryset(self):
-        return Book.objects.filter(title__icontains='war')[:5] # Get 5 books containing the title war
+ def get_queryset(self):
+ return Book.objects.filter(title__icontains='war')[:5] # Get 5 books containing the title war
```
-Nous pourrions aussi réécrire `get_context_data()`, afin d'envoyer au template des variables de contexte supplémentaires (par défaut c'est la liste de livres qui est envoyée). Le bout de code ci-dessous montre comment ajouter une variable appelée "`some_data`" au contexte (elle sera alors accessible comme variable de template).
+Nous pourrions aussi réécrire `get_context_data()`, afin d'envoyer au template des variables de contexte supplémentaires (par défaut c'est la liste de livres qui est envoyée). Le bout de code ci-dessous montre comment ajouter une variable appelée "`some_data`" au contexte (elle sera alors accessible comme variable de template).
```python
class BookListView(generic.ListView):
model = Book
-    def get_context_data(self, **kwargs):
-        # Call the base implementation first to get the context
-        context = super(BookListView, self).get_context_data(**kwargs)
-        # Create any data and add it to the context
-        context['some_data'] = 'This is just some data'
-        return context
+ def get_context_data(self, **kwargs):
+ # Call the base implementation first to get the context
+ context = super(BookListView, self).get_context_data(**kwargs)
+ # Create any data and add it to the context
+ context['some_data'] = 'This is just some data'
+ return context
```
Quand vous faites cela, il est important de suivre la procédure indiquée ci-dessus :
@@ -142,25 +142,25 @@ Les templates pour vues génériques sont exactement comme les autres templates
{% block content %}
<h1>Book List</h1>
-  {% if book_list %}
-  <ul>
-  {% for book in book_list %}
-      <li>
-        <a href="\{{ book.get_absolute_url }}">\{{ book.title }}</a> (\{{book.author}})
-      </li>
-  {% endfor %}
-  </ul>
-  {% else %}
-  <p>There are no books in the library.</p>
-  {% endif %}
+ {% if book_list %}
+ <ul>
+ {% for book in book_list %}
+ <li>
+ <a href="\{{ book.get_absolute_url }}">\{{ book.title }}</a> (\{{book.author}})
+ </li>
+ {% endfor %}
+ </ul>
+ {% else %}
+ <p>There are no books in the library.</p>
+ {% endif %}
{% endblock %}
```
-La vue envoie le contexte (liste de livres), en utilisant par défaut les alias `object_list` et `book_list` ; l'un et l'autre fonctionnent.
+La vue envoie le contexte (liste de livres), en utilisant par défaut les alias `object_list` et `book_list` ; l'un et l'autre fonctionnent.
#### Exécution conditionnelle
-Nous utilisons les balises de templates [`if`](https://docs.djangoproject.com/en/2.1/ref/templates/builtins/#if), `else`, et `endif` pour vérifier que la `book_list` a été définie et n'est pas vide. Si `book_list` est vide, alors la condition `else` affiche un texte expliquant qu'il n'y a pas de livres à lister. Si `book_list` n'est pas vide, nous parcourons la liste de livres.
+Nous utilisons les balises de templates [`if`](https://docs.djangoproject.com/en/2.1/ref/templates/builtins/#if), `else`, et `endif` pour vérifier que la `book_list` a été définie et n'est pas vide. Si `book_list` est vide, alors la condition `else` affiche un texte expliquant qu'il n'y a pas de livres à lister. Si `book_list` n'est pas vide, nous parcourons la liste de livres.
```html
{% if book_list %}
@@ -170,11 +170,11 @@ Nous utilisons les balises de templates [`if`](https://docs.djangoproject.com/en
{% endif %}
```
-La condition ci-dessus ne vérifie qu'un seul cas, mais vous pouvez ajouter d'autres tests grâce à la balise de template `elif` (par exemple `{% elif var2 %}`). Pour plus d'information sur les opérateurs conditionnels, voyez ici :  [if](https://docs.djangoproject.com/en/2.1/ref/templates/builtins/#if), [ifequal/ifnotequal](https://docs.djangoproject.com/en/2.1/ref/templates/builtins/#ifequal-and-ifnotequal), et [ifchanged](https://docs.djangoproject.com/en/2.1/ref/templates/builtins/#ifchanged) dans [Built-in template tags and filters](https://docs.djangoproject.com/en/2.1/ref/templates/builtins) (Django Docs).
+La condition ci-dessus ne vérifie qu'un seul cas, mais vous pouvez ajouter d'autres tests grâce à la balise de template `elif` (par exemple `{% elif var2 %}`). Pour plus d'information sur les opérateurs conditionnels, voyez ici : [if](https://docs.djangoproject.com/en/2.1/ref/templates/builtins/#if), [ifequal/ifnotequal](https://docs.djangoproject.com/en/2.1/ref/templates/builtins/#ifequal-and-ifnotequal), et [ifchanged](https://docs.djangoproject.com/en/2.1/ref/templates/builtins/#ifchanged) dans [Built-in template tags and filters](https://docs.djangoproject.com/en/2.1/ref/templates/builtins) (Django Docs).
#### Boucles for
-Le template utilise les balises de template [for](https://docs.djangoproject.com/en/2.1/ref/templates/builtins/#for) et `endfor` pour boucler à travers la liste de livres, comme montré ci-dessous. Chaque itération peuple la variable de template `book` avec l'information concernant l'élément courant de la liste.
+Le template utilise les balises de template [for](https://docs.djangoproject.com/en/2.1/ref/templates/builtins/#for) et `endfor` pour boucler à travers la liste de livres, comme montré ci-dessous. Chaque itération peuple la variable de template `book` avec l'information concernant l'élément courant de la liste.
```html
{% for book in book_list %}
@@ -182,7 +182,7 @@ Le template utilise les balises de template [for](https://docs.djangoproject.com
{% endfor %}
```
-Bien que nous ne l'utilisions pas ici, Django, à l'intérieur de la boucle, va aussi créer d'autres variables que vous pouvez utiliser pour suivre l'itération. Par exemple, vous pouvez tester la variable `forloop.last` pour réaliser une action conditionnelle au dernier passage de la boucle.
+Bien que nous ne l'utilisions pas ici, Django, à l'intérieur de la boucle, va aussi créer d'autres variables que vous pouvez utiliser pour suivre l'itération. Par exemple, vous pouvez tester la variable `forloop.last` pour réaliser une action conditionnelle au dernier passage de la boucle.
#### Accéder aux variables
@@ -192,15 +192,15 @@ Le code à l'intérieur de la boucle crée un élément de liste pour chaque liv
<a href="\{{ book.get_absolute_url }}">\{{ book.title }}</a> (\{{book.author}})
```
-Nous accédons aux _champs_ de l'enregistrement "livre" associé, en utilisant la notation "à points" (par exemple `book.title` et `book.author`), où le texte suivant l'item `book` est le nom du champ (comme défini dans le modèle).
+Nous accédons aux _champs_ de l'enregistrement "livre" associé, en utilisant la notation "à points" (par exemple `book.title` et `book.author`), où le texte suivant l'item `book` est le nom du champ (comme défini dans le modèle).
-Nous pouvons aussi appeler des _fonctions_ contenues dans le modèle depuis l'intérieur de notre template — dans ce cas nous appelons `Book.get_absolute_url()` pour obtenir une URL que vous pouvez utiliser pour afficher dans la vue détail l'enregistrement associé. Cela fonctionne, pourvu que la fonction ne comporte pas d'arguments (il n'y a aucun moyen de passer des arguments !).
+Nous pouvons aussi appeler des _fonctions_ contenues dans le modèle depuis l'intérieur de notre template — dans ce cas nous appelons `Book.get_absolute_url()` pour obtenir une URL que vous pouvez utiliser pour afficher dans la vue détail l'enregistrement associé. Cela fonctionne, pourvu que la fonction ne comporte pas d'arguments (il n'y a aucun moyen de passer des arguments !).
> **Note :** Il nous faut être quelque peu attentifs aux "effets de bord" quand nous appelons des fonctions dans nos templates. Ici nous récupérons simplement une URL à afficher, mais une fonction peut faire à peu près n'importe quoi — nous ne voudrions pas effacer notre base de données (par exemple) juste parce que nous avons affiché notre template !
#### Mettre à jour le template de base
-Ouvrez le template de base (**/locallibrary/catalog/templates/_base_generic.html_**) et insérez **{% url 'books' %}** dans le lien URL pour **All books**, comme indiqué ci-dessous. Cela va afficher le lien dans toutes les pages (nous pouvons mettre en place ce lien avec succès, maintenant que nous avons créé le mappage d'URL "books").
+Ouvrez le template de base (**/locallibrary/catalog/templates/_base_generic.html_**) et insérez **{% url 'books' %}** dans le lien URL pour **All books**, comme indiqué ci-dessous. Cela va afficher le lien dans toutes les pages (nous pouvons mettre en place ce lien avec succès, maintenant que nous avons créé le mappage d'URL "books").
```python
<li><a href="{% url 'index' %}">Home</a></li>
@@ -214,25 +214,25 @@ Vous ne pouvez pas encore construire la liste des livres, car il nous manque tou
## Page de détail d'un livre
-La page de détail d'un livre va afficher les informations sur un livre précis, auquel on accède en utilisant l'URL `catalog/book/<id>` (où `<id>` est la clé primaire pour le livre). En plus des champs définis dans le modèle `Book` (auteur, résumé, ISBN, langue et genre), nous allons aussi lister les détails des copies disponibles (`BookInstances`), incluant le statut, la date de retour prévue, la marque d'éditeur et l'id. Cela permettra à nos lecteurs, non seulement de s'informer sur le livre, mais aussi de confirmer si et quand il sera disponible.
+La page de détail d'un livre va afficher les informations sur un livre précis, auquel on accède en utilisant l'URL `catalog/book/<id>` (où `<id>` est la clé primaire pour le livre). En plus des champs définis dans le modèle `Book` (auteur, résumé, ISBN, langue et genre), nous allons aussi lister les détails des copies disponibles (`BookInstances`), incluant le statut, la date de retour prévue, la marque d'éditeur et l'id. Cela permettra à nos lecteurs, non seulement de s'informer sur le livre, mais aussi de confirmer si et quand il sera disponible.
### Mappage d'URL
-Ouvrez **/catalog/urls.py** et ajoutez-y le mappeur d'URL '**book-detail**' indiqué en gras ci-dessous. Cette fonction `path()` définit un pattern, la vue générique basée sur classe qui lui est associée, ainsi qu'un nom.
+Ouvrez **/catalog/urls.py** et ajoutez-y le mappeur d'URL '**book-detail**' indiqué en gras ci-dessous. Cette fonction `path()` définit un pattern, la vue générique basée sur classe qui lui est associée, ainsi qu'un nom.
```python
urlpatterns = [
path('', views.index, name='index'),
-    path('books/', views.BookListView.as_view(), name='books'),
-  path('book/<int:pk>', views.BookDetailView.as_view(), name='book-detail'),
+ path('books/', views.BookListView.as_view(), name='books'),
+ path('book/<int:pk>', views.BookDetailView.as_view(), name='book-detail'),
]
```
-Pour le chemin *book-detail*, le pattern d'URL utilise une syntaxe spéciale pour capturer l'id exact du livre que nous voulons voir. La syntaxe est très simple : les chevrons ('<' et '>') définissent la partie de l'URL qui doit être capturée et encadrent le nom de la variable que la vue pourra utiliser pour accéder aux données capturées. Par exemple, **\<something>**  va capturer le pattern marqué et passer la valeur à la vue en tant que variable "something". De manière optionnelle, vous pouvez faire précéder le nom de variable d'une [spécification de convertisseur](https://docs.djangoproject.com/en/2.1/topics/http/urls/#path-converters), qui définit le type de la donnée (int, str, slug, uuid, path).
+Pour le chemin *book-detail*, le pattern d'URL utilise une syntaxe spéciale pour capturer l'id exact du livre que nous voulons voir. La syntaxe est très simple : les chevrons ('<' et '>') définissent la partie de l'URL qui doit être capturée et encadrent le nom de la variable que la vue pourra utiliser pour accéder aux données capturées. Par exemple, **\<something>** va capturer le pattern marqué et passer la valeur à la vue en tant que variable "something". De manière optionnelle, vous pouvez faire précéder le nom de variable d'une [spécification de convertisseur](https://docs.djangoproject.com/en/2.1/topics/http/urls/#path-converters), qui définit le type de la donnée (int, str, slug, uuid, path).
-Dans ce cas, nous utilisons `'<int:pk>'` pour capturer l'id du livre, qui doit être une chaîne formatée d'une certaine manière, et passer cet id à la vue en tant que paramètre nommé `pk` (abréviation pour primary key - clé primaire). C'est l'id qui doit être utilisé pour stocker le livre de manière unique dans la base de données, comme défini dans le modèle Book.
+Dans ce cas, nous utilisons `'<int:pk>'` pour capturer l'id du livre, qui doit être une chaîne formatée d'une certaine manière, et passer cet id à la vue en tant que paramètre nommé `pk` (abréviation pour primary key - clé primaire). C'est l'id qui doit être utilisé pour stocker le livre de manière unique dans la base de données, comme défini dans le modèle Book.
-> **Note :** Comme nous l'avons dit précédemment, notre URL correcte est en réalité `catalog/book/<digits>` (comme nous sommes dans l'application **catalog**, `/catalog/` est supposé).
+> **Note :** Comme nous l'avons dit précédemment, notre URL correcte est en réalité `catalog/book/<digits>` (comme nous sommes dans l'application **catalog**, `/catalog/` est supposé).
> **Attention :** La vue générique basée sur classe "détail" _s'attend_ à recevoir un paramètre appelé **pk**. Si vous écrivez votre propre fonction, vous pouvez utiliser le nom que vous voulez pour votre paramètre, ou même passer l'information avec un argument non nommé.
@@ -240,7 +240,7 @@ Dans ce cas, nous utilisons `'<int:pk>'` pour capturer l'id du livre, qui doit
> **Note :** Vous n'aurez pas besoin de cette section pour achever le tutoriel ! Nous en parlons parce que nous savons que cette option vous sera probablement utile dans votre avenir centré sur Django.
-La recherche de pattern fournie par `path()` est simple et utile pour les cas (très communs) où vous voulez seulement capturer _n'importe quelle_ chaîne ou entier. Si vous avez besoin d'un filtre plus affiné (par exemple pour filtrer seulement les chaînes qui ont un certain nombre de caractères), alors vous pouvez utiliser la méthode [re_path()](https://docs.djangoproject.com/en/2.1/ref/urls/#django.urls.re_path).
+La recherche de pattern fournie par `path()` est simple et utile pour les cas (très communs) où vous voulez seulement capturer _n'importe quelle_ chaîne ou entier. Si vous avez besoin d'un filtre plus affiné (par exemple pour filtrer seulement les chaînes qui ont un certain nombre de caractères), alors vous pouvez utiliser la méthode [re_path()](https://docs.djangoproject.com/en/2.1/ref/urls/#django.urls.re_path).
Cette méthode est utilisée exactement comme `path()`, sauf qu'elle vous permet de spécifier un pattern utilisant une [Expression régulière](https://docs.python.org/3/library/re.html). Par exemple, le chemin précédent pourrait avoir été écrit ainsi :
@@ -264,7 +264,7 @@ L'essentiel de ce que vous aurez besoin de savoir pour déclarer une recherche d
| \* | Recherche zéro ou plus occurrence(s) du caractère précédent. Par exemple, pour rechercher "rien ou un mot", vous pourriez utiliser `\w*`. |
| ( ) | Capture la partie du pattern contenue dans les parenthèses. Toutes les valeurs capturées seront passées à la vue en tant que paramètres non nommés (si plusieurs patterns sont capturés, les paramètres associés seront fournis dans l'ordre de déclaration des captures). |
| (?P<_name_>...) | Capture le pattern (indiqué par…) en tant que variable nommée (dans ce cas "name"). Les valeurs capturées sont passées à la vue avec le nom spécifié. Votre vue doit par conséquent déclarer un argument avec le même nom ! |
-| [  ] | Recherche l'un des caractères contenus dans cet ensemble. Par exemple, [abc] va rechercher "a" ou "b" ou "c". [-\w] va rechercher le caractère "-" ou tout caractère de mot. |
+| [ ] | Recherche l'un des caractères contenus dans cet ensemble. Par exemple, [abc] va rechercher "a" ou "b" ou "c". [-\w] va rechercher le caractère "-" ou tout caractère de mot. |
La plupart des autres caractères peuvent être pris littéralement.
@@ -283,13 +283,13 @@ Considérons quelques exemples réels de patterns :
<td>
<p>
C'est là l'expression régulière utilisée dans notre mappeur d'URL.
- Elle recherche une chaîne qui a <code>book/</code> au commencement de
+ Elle recherche une chaîne qui a <code>book/</code> au commencement de
la ligne (<strong>^book/</strong>), ensuite a au moins 1 digit
(<code>\d+</code>), et enfin se termine (avec aucun caractère
non-digit avant la fin du marqueur de ligne).
</p>
<p>
- Elle capture aussi tous les digits <strong>(?P&#x3C;pk>\d+)</strong>
+ Elle capture aussi tous les digits <strong>(?P&#x3C;pk>\d+)</strong>
et les passe à la vue dans un paramètre appelé 'pk'.
<strong
>Les valeurs capturées sont toujours passées comme des chaînes
@@ -299,7 +299,7 @@ Considérons quelques exemples réels de patterns :
<p>
Par exemple, cette expression régulière trouverait une correspondance
dans l'URL <code>book/1234</code>, et enverrait alors une
- variable <code>pk='1234'</code> à la vue.
+ variable <code>pk='1234'</code> à la vue.
</p>
</td>
</tr>
@@ -316,7 +316,7 @@ Considérons quelques exemples réels de patterns :
<td><strong>r'^book/(?P&#x3C;stub>[-\w]+)$'</strong></td>
<td>
<p>
- Ceci recherche une chaîne qui a <code>book/</code> au commencement de
+ Ceci recherche une chaîne qui a <code>book/</code> au commencement de
la ligne (<strong>^book/</strong>), ensuite a au moins 1 caractère
étant <em>soit</em> un '-', <em>soit</em> un caractère de mot
(<strong>[-\w]+</strong>), puis la fin. Ce pattern capture aussi cet
@@ -357,16 +357,16 @@ Ouvrez **catalog/views.py**, et copiez-y le code suivant à la fin du fichier :
```python
class BookDetailView(generic.DetailView):
-    model = Book
+ model = Book
```
-C'est tout ! La seule chose que vous avez à faire maintenant, c'est créer un template appelé **/locallibrary/catalog/templates/catalog/book_detail.html**, et la vue va lui passer les informations de la base de donnée concernant l'enregistrement `Book` spécifique, extrait par le mapper d'URL. À l'intérieur du template, vous pouvez accéder à la liste de livres via la variable de template appelée `object` OU `book` (c'est-à-dire, de manière générique, "le_nom_du_modèle").
+C'est tout ! La seule chose que vous avez à faire maintenant, c'est créer un template appelé **/locallibrary/catalog/templates/catalog/book_detail.html**, et la vue va lui passer les informations de la base de donnée concernant l'enregistrement `Book` spécifique, extrait par le mapper d'URL. À l'intérieur du template, vous pouvez accéder à la liste de livres via la variable de template appelée `object` OU `book` (c'est-à-dire, de manière générique, "le_nom_du_modèle").
Si vous en avez besoin, vous pouvez changer le template utilisé et le nom de l'objet-contexte utilisé pour désigner le livre dans le template. Vous pouvez aussi renommer les méthodes pour, par exemple, ajouter des informations supplémentaires au contexte.
#### Que se passe-t-il si l'enregistrement n'existe pas ?
-Si l'enregistrement demandé n'existe pas, alors la vue générique basée sur classe "détail" va lever automatiquement pour vous une exception `Http404` — en production, cela va automatiquement afficher une page appropriée "ressource non trouvée", que vous pouvez personnaliser si besoin.
+Si l'enregistrement demandé n'existe pas, alors la vue générique basée sur classe "détail" va lever automatiquement pour vous une exception `Http404` — en production, cela va automatiquement afficher une page appropriée "ressource non trouvée", que vous pouvez personnaliser si besoin.
Juste pour vous donner une idée de la manière dont tout cela fonctionne, le morceau de code ci-dessous montre comment vous implémenteriez cette vue comme une fonction si vous n'utilisiez **pas** la vue générique basée sur classe "détail".
@@ -380,21 +380,21 @@ def book_detail_view(request, primary_key):
return render(request, 'catalog/book_detail.html', context={'book': book})
```
-La vue essaie d'abord d'obtenir du modèle l'enregistrement correspondant au livre spécifié. Si cela échoue, la vue devrait lever une exception `Http404` pour indiquer que le livre est "non trouvé". L'étape finale est ensuite, comme d'habitude, d'appeler `render()` avec le nom du template et les données concernant le livre dans le paramètre `context` (comme un dictionnaire).
+La vue essaie d'abord d'obtenir du modèle l'enregistrement correspondant au livre spécifié. Si cela échoue, la vue devrait lever une exception `Http404` pour indiquer que le livre est "non trouvé". L'étape finale est ensuite, comme d'habitude, d'appeler `render()` avec le nom du template et les données concernant le livre dans le paramètre `context` (comme un dictionnaire).
-Une alternative consiste à utiliser la fonction `get_object_or_404()` comme un raccourci pour lever une exception `Http404` si l'enregistrement n'existe pas.
+Une alternative consiste à utiliser la fonction `get_object_or_404()` comme un raccourci pour lever une exception `Http404` si l'enregistrement n'existe pas.
```python
from django.shortcuts import get_object_or_404
def book_detail_view(request, primary_key):
-  book = get_object_or_404(Book, pk=primary_key)
+ book = get_object_or_404(Book, pk=primary_key)
return render(request, 'catalog/book_detail.html', context={'book': book})
```
### Créer le template de la Vue Détail
-Créez le fichier HTML **/locallibrary/catalog/templates/catalog/book_detail.html**, et copiez-y le code ci-dessous. Comme on l'a dit plus haut, c'est là le nom de template attendu par défaut par la vue générique basée sur classe _detail_ (pour un modèle appelé `Book` dans une application appelée `catalog`).
+Créez le fichier HTML **/locallibrary/catalog/templates/catalog/book_detail.html**, et copiez-y le code ci-dessous. Comme on l'a dit plus haut, c'est là le nom de template attendu par défaut par la vue générique basée sur classe _detail_ (pour un modèle appelé `Book` dans une application appelée `catalog`).
```html
{% extends "base_generic.html" %}
@@ -434,10 +434,10 @@ Bien qu'en un peu plus grand, presque tout ce qu'il y a dans ce template a été
- Nous étendons notre template de base et récrivons le block "content".
- Nous utilisons une procédure conditionnelle pour déterminer s'il faut ou non afficher tel contenu spécifique.
-- Nous utilisons une boucle `for` pour boucler sur des listes d'objets.
-- Nous accédons aux champs du contexte en utilisant la notation à point (parce que nous avons utilisé la vue générique _detail_, le contexte est nommé `book` ; nous pourrions aussi utiliser "`object`").
+- Nous utilisons une boucle `for` pour boucler sur des listes d'objets.
+- Nous accédons aux champs du contexte en utilisant la notation à point (parce que nous avons utilisé la vue générique _detail_, le contexte est nommé `book` ; nous pourrions aussi utiliser "`object`").
-Une chose intéressante que nous n'avons pas encore vue, c'est la fonction `book.bookinstance_set.all()`. Cette méthode est "automagiquement" construite par Django pour retourner l'ensemble des enregistrements `BookInstance` associés à un `Book` particulier.
+Une chose intéressante que nous n'avons pas encore vue, c'est la fonction `book.bookinstance_set.all()`. Cette méthode est "automagiquement" construite par Django pour retourner l'ensemble des enregistrements `BookInstance` associés à un `Book` particulier.
```python
{% for copy in book.bookinstance_set.all %}
@@ -445,9 +445,9 @@ Une chose intéressante que nous n'avons pas encore vue, c'est la fonction `book
{% endfor %}
```
-Cette méthode est requise parce que vous déclarez un champ `ForeignKey` (one-to-many) seulement du côté "one" de la relation. Comme vous ne faites rien pour déclarer la relation dans les modèles opposés ("many"), Django n'a pas de champ pour récupérer l'ensemble des enregistrements associés. Pour remédier à ce problème, Django construit une fonction justement nommée "recherche inversée", que vous pouvez utiliser. Le nom de la fonction est construit en mettant en minuscule le nom du modèle où a été déclarée la `ForeignKey`, suivi de `_set` (ainsi la fonction créée dans `Book` est `bookinstance_set()`).
+Cette méthode est requise parce que vous déclarez un champ `ForeignKey` (one-to-many) seulement du côté "one" de la relation. Comme vous ne faites rien pour déclarer la relation dans les modèles opposés ("many"), Django n'a pas de champ pour récupérer l'ensemble des enregistrements associés. Pour remédier à ce problème, Django construit une fonction justement nommée "recherche inversée", que vous pouvez utiliser. Le nom de la fonction est construit en mettant en minuscule le nom du modèle où a été déclarée la `ForeignKey`, suivi de `_set` (ainsi la fonction créée dans `Book` est `bookinstance_set()`).
-> **Note :** Ici nous utilisons `all()` pour récupérer tous les enregistrements (comportement par défaut). Bien que vous puissiez utiliser la méthode `filter()` pour obtenir un sous-ensemble d'enregistrements dans le code, vous ne pouvez faire cela directement dans le template, parce que vous ne pouvez pas spécifier d'arguments dans les fonctions.
+> **Note :** Ici nous utilisons `all()` pour récupérer tous les enregistrements (comportement par défaut). Bien que vous puissiez utiliser la méthode `filter()` pour obtenir un sous-ensemble d'enregistrements dans le code, vous ne pouvez faire cela directement dans le template, parce que vous ne pouvez pas spécifier d'arguments dans les fonctions.
>
> Prenez garde également que, si vous ne définissez pas un ordre (dans votre vue basée sur classe ou votre modèle), vous allez voir des erreurs de ce genre en provenance du serveur de développement :
>
@@ -457,13 +457,13 @@ Cette méthode est requise parce que vous déclarez un champ `ForeignKey` (one-
>
> Ceci vient du fait que l'[objet paginator](https://docs.djangoproject.com/en/2.1/topics/pagination/#paginator-objects) s'attend à ce qu'un ORDER BY soit exécuté sur votre base de données sous-jacente. Sans cela il ne peut pas être sûr que les enregistrements retournés sont vraiment dans le bon ordre !
>
-> Ce tutoriel n'a pas (encore !) traité de la **pagination**, mais comme vous ne pouvez pas utiliser `sort_by()` et passer un paramètre (pour la même raison que le `filter()` décrit précédemment), vous avez le choix entre trois options :
+> Ce tutoriel n'a pas (encore !) traité de la **pagination**, mais comme vous ne pouvez pas utiliser `sort_by()` et passer un paramètre (pour la même raison que le `filter()` décrit précédemment), vous avez le choix entre trois options :
>
-> 1. Ajouter un `ordering` lors de la déclaration de la `class Meta` dans votre modèle.
-> 2. Ajouter un attribut `queryset` dans votre vue personnalisée basée sur classe, en spécifiant un `order_by()`.
-> 3. Ajouter une méthode `get_queryset` à votre vue personnalisée basée sur classe, et préciser de même un `order_by()`.
+> 1. Ajouter un `ordering` lors de la déclaration de la `class Meta` dans votre modèle.
+> 2. Ajouter un attribut `queryset` dans votre vue personnalisée basée sur classe, en spécifiant un `order_by()`.
+> 3. Ajouter une méthode `get_queryset` à votre vue personnalisée basée sur classe, et préciser de même un `order_by()`.
>
-> Si vous décidez d'ajouter une `class Meta` au modèle `Author` (solution peut-être pas aussi flexible que personnaliser la vue basée sur classe, mais assez facile), vous allez vous retrouver avec quelque chose de ce genre :
+> Si vous décidez d'ajouter une `class Meta` au modèle `Author` (solution peut-être pas aussi flexible que personnaliser la vue basée sur classe, mais assez facile), vous allez vous retrouver avec quelque chose de ce genre :
>
> class Author(models.Model):
> first_name = models.CharField(max_length=100)
@@ -480,7 +480,7 @@ Cette méthode est requise parce que vous déclarez un champ `ForeignKey` (one-
> class Meta:
> ordering = ['last_name']
>
-> Bien sûr le champ n'est pas forcément `last_name` : ce pourrait être un autre champ.
+> Bien sûr le champ n'est pas forcément `last_name` : ce pourrait être un autre champ.
>
> Dernier point, mais non le moindre : vous devriez trier les données par un attribut/colonne qui a réellement un index (unique ou pas) dans votre base de données, afin d'éviter des problèmes de performance. Bien sûr ce n'est pas requis ici (ce serait un peu exagéré avec si peu de livres et d'utilisateurs), mais il vaut mieux avoir cela à l'esprit pour de futurs projets.
@@ -525,34 +525,34 @@ Ouvrez **/locallibrary/catalog/templates/_base_generic.html_**, et copiez-y, sou
```python
{% block content %}{% endblock %}
-  {% block pagination %}
-    {% if is_paginated %}
-        <div class="pagination">
-            <span class="page-links">
-                {% if page_obj.has_previous %}
-                    <a href="\{{ request.path }}?page=\{{ page_obj.previous_page_number }}">previous</a>
-                {% endif %}
-                <span class="page-current">
-                    Page \{{ page_obj.number }} of \{{ page_obj.paginator.num_pages }}.
-                </span>
-                {% if page_obj.has_next %}
-                    <a href="\{{ request.path }}?page=\{{ page_obj.next_page_number }}">next</a>
-                {% endif %}
-            </span>
-        </div>
-    {% endif %}
-  {% endblock %} 
+ {% block pagination %}
+ {% if is_paginated %}
+ <div class="pagination">
+ <span class="page-links">
+ {% if page_obj.has_previous %}
+ <a href="\{{ request.path }}?page=\{{ page_obj.previous_page_number }}">previous</a>
+ {% endif %}
+ <span class="page-current">
+ Page \{{ page_obj.number }} of \{{ page_obj.paginator.num_pages }}.
+ </span>
+ {% if page_obj.has_next %}
+ <a href="\{{ request.path }}?page=\{{ page_obj.next_page_number }}">next</a>
+ {% endif %}
+ </span>
+ </div>
+ {% endif %}
+ {% endblock %}
```
-Le `page_obj` est un objet [Paginator](https://docs.djangoproject.com/en/2.1/topics/pagination/#paginator-objects) qui n'existera que si une pagination est utilisée dans la page courante. Cet objet vous permet de récupérer toutes les informations sur la page courante, les pages précédentes, combien il y a de pages au total, etc.
+Le `page_obj` est un objet [Paginator](https://docs.djangoproject.com/en/2.1/topics/pagination/#paginator-objects) qui n'existera que si une pagination est utilisée dans la page courante. Cet objet vous permet de récupérer toutes les informations sur la page courante, les pages précédentes, combien il y a de pages au total, etc.
-Nous utilisons `\{{ request.path }}` pour récupérer l'URL de la page courante, afin de créer les liens de pagination. Cela est utile, car cette variable est indépendante de l'objet que nous sommes en train de paginer.
+Nous utilisons `\{{ request.path }}` pour récupérer l'URL de la page courante, afin de créer les liens de pagination. Cela est utile, car cette variable est indépendante de l'objet que nous sommes en train de paginer.
C'est tout !
### À quoi cela ressemble-t-il ?
-La capture d'écran ci-dessous montre à quoi ressemble la pagination. Si vous n'avez pas entré plus de 10 titres dans votre base de données, vous pouvez tester plus facilement cette pagination en diminuant le nombre spécifié à la ligne `paginate_by` dans votre fichier **catalog/views.py**. Pour obtenir le résultat ci-dessous, nous avons changé la ligne en `paginate_by = 2`.
+La capture d'écran ci-dessous montre à quoi ressemble la pagination. Si vous n'avez pas entré plus de 10 titres dans votre base de données, vous pouvez tester plus facilement cette pagination en diminuant le nombre spécifié à la ligne `paginate_by` dans votre fichier **catalog/views.py**. Pour obtenir le résultat ci-dessous, nous avons changé la ligne en `paginate_by = 2`.
Les liens de pagination sont affichés en bas de la page, avec les liens suivant/précédent affichés selon la page sur laquelle nous nous trouvons.
@@ -569,7 +569,7 @@ Le code requis pour le mappeur d'URL et les vues sera virtuellement identique au
> **Note :**
>
-> - Une fois que vous aurez créé le mappeur d'URL pour la page "liste d'auteurs", vous aurez besoin de mettre aussi à jour le lien **All authors** dans le template de base. Suivez la [même procédure](#Update_the_base_template) que celle adoptée quand nous avons mis à jour le lien **All books**.
+> - Une fois que vous aurez créé le mappeur d'URL pour la page "liste d'auteurs", vous aurez besoin de mettre aussi à jour le lien **All authors** dans le template de base. Suivez la [même procédure](#Update_the_base_template) que celle adoptée quand nous avons mis à jour le lien **All books**.
> - Une fois créé le mappeur d'URL pour la page de détails sur l'auteur, vous devrez aussi mettre à jour le [template de la vue détail d'un livre](#Creating_the_Detail_View_template) (**/locallibrary/catalog/templates/catalog/book_detail.html**), de sorte que le lien vers l'auteur pointe vers votre nouvelle page de détails sur l'auteur (au lieu d'être une URL vide). La ligne va avoir comme changement la balise montrée en gras ci-dessous.
>
> ```html
diff --git a/files/fr/learn/server-side/django/home_page/index.md b/files/fr/learn/server-side/django/home_page/index.md
index 7140bf6583..5ff1163613 100644
--- a/files/fr/learn/server-side/django/home_page/index.md
+++ b/files/fr/learn/server-side/django/home_page/index.md
@@ -67,8 +67,8 @@ La liste des URLs dont nous aurons besoin se résume à :
- `catalog/` — Pour la page d'accueil.
- `catalog/books/` — Pour la liste des livres.
-- `catalog/authors/` — Pour la liste des auteurs.
-- `catalog/book/<id>` — Pour disposer du détail de chacun des livres mis en prêt et identifié par identifiant `<id>` unique (le troisième livre enregistré est consultable dans le détail via l'URL `/catalog/book/3`).
+- `catalog/authors/` — Pour la liste des auteurs.
+- `catalog/book/<id>` — Pour disposer du détail de chacun des livres mis en prêt et identifié par identifiant `<id>` unique (le troisième livre enregistré est consultable dans le détail via l'URL `/catalog/book/3`).
- `catalog/author/<id>` — De la même manière, le détail de chacun des auteurs enregistrés, identifié de la même manière par sa clé primaire *`<id>`*.
Bien que les données dépendent du contenu de la base de données, les trois premières URLs retournent les résultats de requêtes sans informations supplémentaires ; c'est le cas de la page d'accueil qui donnera des décomptes de contenus et des pages sur la liste des livres ou des auteurs.
@@ -107,7 +107,7 @@ urlpatterns = [
La fonction `path()` sert à définir les éléments suivants :
-- Un modèle d'URL qui, dans le cas présent, est une chaîne vide : `''`. Nous évoquerons ultérieurement les modèles d'URL plus en détail quand nous travaillerons les autres vues.
+- Un modèle d'URL qui, dans le cas présent, est une chaîne vide : `''`. Nous évoquerons ultérieurement les modèles d'URL plus en détail quand nous travaillerons les autres vues.
- Une fonction de vue, ici `views.index`, qui sera sollicitée quand le modèle d'URL sera détecté et une fonction Python qui sera appelée pour traiter l'appel d'URL est présent dans le fichier **views.py** du module `catalog`.
Le paramètre `name` utilisé dans la fonction `path()` permet aussi de définir un identifiant unique qui sert à lier les pages vers celle-ci au sein de l'application. Vous pouvez alors l'utiliser à l'envers en routant dynamiquement des pages en lien vers cette ressource :
@@ -185,7 +185,7 @@ Vous pouvez en faire l'expérience dès à présent, après avoir redémarré vo
Django utilise un langage pour les gabarits qui permet de résoudre certains sujets liés aux pages HTML. En l'occurrence, dans le site web de la bibliothèque nous aurons des bandeaux de navigateur et autres codes d'en-tête à réutiliser. Dans une vision classique, il faudrait récrire dans chaque page le même code pour obtenir le même rendu. Si cela peut se concevoir pour quelques pages, ce procédé devient vite inopérant voire risqué avec un site dynamique complet.
-Le langage de gabarit de Django permet de définir un modèle de base puis de l'étendre ensuite. L'extrait de code ci-dessous vient du fichier de gabarit **base_generic.html**, vous constaterez qu'il s'y mélange du code HTML et des sections nommées contenu dans entre des marqueurs `block` et `endblock` qui peut contenir ou non des données.
+Le langage de gabarit de Django permet de définir un modèle de base puis de l'étendre ensuite. L'extrait de code ci-dessous vient du fichier de gabarit **base_generic.html**, vous constaterez qu'il s'y mélange du code HTML et des sections nommées contenu dans entre des marqueurs `block` et `endblock` qui peut contenir ou non des données.
> **Note :** Les marqueurs de gabarits sont des fonctions que vous pouvez utiliser dans un modèle pour parcourir des listes, effectuer des opérations conditionnelles en fonction de la valeur d'une variable, etc. Outre les balises de modèle, la syntaxe de gabarit vous permet de référencer les variables qui sont transmises au modèle à partir de la vue et d'utiliser des filtres de gabarit pour mettre en forme les variables (par exemple, pour convertir une chaîne en minuscule).
@@ -208,7 +208,7 @@ Dans l'extrait ci-dessous vous avec trois sections nommées qui pourront être r
</html>
```
-Lorsque l'on définit un gabarit pour une vue particulière, il convient de définir une base de gabarit et d'utiliser la balise `extends` dans une page complémentaire comme dans l'exemple ci-dessous. Ensuite, il est nécessaire de préciser les sections qui seront modifiées en utilisant les balises `block`/`endblock` qui définissent le début et la fin de section.
+Lorsque l'on définit un gabarit pour une vue particulière, il convient de définir une base de gabarit et d'utiliser la balise `extends` dans une page complémentaire comme dans l'exemple ci-dessous. Ensuite, il est nécessaire de préciser les sections qui seront modifiées en utilisant les balises `block`/`endblock` qui définissent le début et la fin de section.
À titre indicatif, l'extrait ci-dessous présente la manière d'activer à l'aide de la balise `extends` le remplacement de la section `content`. La page HTML générée inclura la structure de la page définie plus haut et le code généré à la fois pour la section `title`, mais avec les éléments nouveaux, ci-dessous, pour la section `content`.
@@ -223,7 +223,7 @@ Lorsque l'on définit un gabarit pour une vue particulière, il convient de déf
#### Le gabarit de base de la bibliothèque
-Nous allons nous appuyer sur le gabarit ci-dessous pour construire la page de base de la bibliothèque locale. Vous le constatez, il contient des éléments HTML et des blocs dédiés Django pour spécifier trois sections `title`, `sidebar`, et `content`. La section `title` contient un titre par défaut. De même la section `sidebar` contient un lien vers la liste des livres et des auteurs qui pourra être modifié ensuite.
+Nous allons nous appuyer sur le gabarit ci-dessous pour construire la page de base de la bibliothèque locale. Vous le constatez, il contient des éléments HTML et des blocs dédiés Django pour spécifier trois sections `title`, `sidebar`, et `content`. La section `title` contient un titre par défaut. De même la section `sidebar` contient un lien vers la liste des livres et des auteurs qui pourra être modifié ensuite.
> **Note :** Il y a aussi deux balises supplémentaires : `url` et `load static`. Elles seront étudiées dans le chapitre suivant.
@@ -298,9 +298,9 @@ Maintenant créez le fichier HTML **_index.html_** dans le dossier **/locallibra
Dans la section contenu dynamique, des emplacements réservés sont définis pour pouvoir y insérer le contenu de variable qui sont identifiées à l'intérieur de doubles accolades (ouvrantes et fermantes). Pour une meilleure visibilité ces emplacements et les variables nommées sont identifiées en caractères gras dans l'extrait de code ci-dessus.
-> **Note :** Vous pouvez constater simplement que les balises de gabarit (fonctions) et les balises de variables sont entre accolades ; double accolades pour une variable (`\{{ num_books }}`), et simple accolade avec le pourcentage (`{% extends "base_generic.html" %}`) pour les balises.
+> **Note :** Vous pouvez constater simplement que les balises de gabarit (fonctions) et les balises de variables sont entre accolades ; double accolades pour une variable (`\{{ num_books }}`), et simple accolade avec le pourcentage (`{% extends "base_generic.html" %}`) pour les balises.
-Gardez en mémoire que les variables utilisées dans les gabarits sont des clés d'un dictionnaire `context` transmis à la fonction `render()` de la vue (revenez à l'exemple plus haut, ou l'extrait ci-dessous). La fonction `render()` traitera le dictionnaire pour restituer une page HTML où les variables nommées auront été remplacées par leur valeur dans le dictionnaire.
+Gardez en mémoire que les variables utilisées dans les gabarits sont des clés d'un dictionnaire `context` transmis à la fonction `render()` de la vue (revenez à l'exemple plus haut, ou l'extrait ci-dessous). La fonction `render()` traitera le dictionnaire pour restituer une page HTML où les variables nommées auront été remplacées par leur valeur dans le dictionnaire.
```python
context = {
@@ -352,21 +352,21 @@ Par défaut Django ne sait pas où sont vos gabarits, vous devez lui indiquer oÃ
```python
TEMPLATES = [
-    {
-        'BACKEND': 'django.template.backends.django.DjangoTemplates',
-        'DIRS': [
-            os.path.join(BASE_DIR, 'templates'),
-        ],
-        'APP_DIRS': True,
-        'OPTIONS': {
-            'context_processors': [
-                'django.template.context_processors.debug',
-                'django.template.context_processors.request',
-                'django.contrib.auth.context_processors.auth',
-                'django.contrib.messages.context_processors.messages',
-            ],
-        },
-    },
+ {
+ 'BACKEND': 'django.template.backends.django.DjangoTemplates',
+ 'DIRS': [
+ os.path.join(BASE_DIR, 'templates'),
+ ],
+ 'APP_DIRS': True,
+ 'OPTIONS': {
+ 'context_processors': [
+ 'django.template.context_processors.debug',
+ 'django.template.context_processors.request',
+ 'django.contrib.auth.context_processors.auth',
+ 'django.contrib.messages.context_processors.messages',
+ ],
+ },
+ },
]
```
diff --git a/files/fr/learn/server-side/django/introduction/index.md b/files/fr/learn/server-side/django/introduction/index.md
index d5005c4b8e..6ce246058a 100644
--- a/files/fr/learn/server-side/django/introduction/index.md
+++ b/files/fr/learn/server-side/django/introduction/index.md
@@ -40,7 +40,7 @@ Dans ce premier article sur Django, nous allons répondre à la question suivant
## Qu'est ce que Django?
-Django est un framework Python de haut niveau, permettant un développement rapide de sites internet, sécurisés, et maintenables. Créé par des développeurs experimentés, Django prend en charge la plupart des tracas du développement web, vous pouvez donc vous concentrer sur l'écriture de votre application sans avoir besoin de réinventer la roue. Il est gratuit, open source, a une communauté active, une bonne documentation, et plusieurs options pour du support gratuit ou non.
+Django est un framework Python de haut niveau, permettant un développement rapide de sites internet, sécurisés, et maintenables. Créé par des développeurs experimentés, Django prend en charge la plupart des tracas du développement web, vous pouvez donc vous concentrer sur l'écriture de votre application sans avoir besoin de réinventer la roue. Il est gratuit, open source, a une communauté active, une bonne documentation, et plusieurs options pour du support gratuit ou non.
Django vous aide à écrire une application qui est:
@@ -48,7 +48,7 @@ Django vous aide à écrire une application qui est:
- : Django suit la philosophie "Piles incluses" et fournit presque tout ce que les développeurs pourraient vouloir faire. Comme tout ce dont vous avez besoin est une partie de ce "produit", tout fonctionne parfaitement ensemble, suivant des principes de conception cohérents, il possède également une [documentation complète](https://docs.djangoproject.com/en/2.0/) et à jour.
- Polyvalent
- - : Django peut être (et a été) utilisé pour créer presque tous les genres de sites — du gestionnaire de données aux wikis, jusqu'aux réseaux sociaux et aux sites d'actualités. Il peut fonctionner avec n'importe quelle infrastructure côté client, et peut renvoyer des données dans quasiment n'importe quel format (notamment HTML, RSS, JSON, XML, etc). Le site sur lequel vous lisez en ce moment est basé sur Django!
+ - : Django peut être (et a été) utilisé pour créer presque tous les genres de sites — du gestionnaire de données aux wikis, jusqu'aux réseaux sociaux et aux sites d'actualités. Il peut fonctionner avec n'importe quelle infrastructure côté client, et peut renvoyer des données dans quasiment n'importe quel format (notamment HTML, RSS, JSON, XML, etc). Le site sur lequel vous lisez en ce moment est basé sur Django!
Tandis qu'il fournit presque toutes les fonctionnalités dont vous pourriez avoir besoin (comme des base de données populaires, des moteurs de modélisation, etc.), il peut tout de même être étendu pour utiliser d'autres composants si besoin.
@@ -61,7 +61,7 @@ Django vous aide à écrire une application qui est:
Django active par défaut la protection contre beaucoup de vulnérabilités, comme les injections SQL, le cross-site scripting, le cross-site request forgery et le clickjacking (voir [Website security](/fr/docs/Learn/Server-side/First_steps/Website_security) pour plus de détails sur ce genre d'attaques).
- Scalable
- - : Django utilise une architecture composite "shared-nothing" (chaque composant de l'architecture est indépendant des autres, et peut ainsi être remplacé ou changé si besoin). En ayant des séparations nettes entres les différentes parties, Django peut se scaler lors d'une hausse de trafic en ajoutant du hardware à tous les niveaux : serveurs cache, serveurs de base de données, serveurs d'application. Certains des sites les plus fréquentés ont réussi à scaler Django pour répondre à leur demande (par exemple, Instagram et Disqus pour ne nommer qu'eux deux).
+ - : Django utilise une architecture composite "shared-nothing" (chaque composant de l'architecture est indépendant des autres, et peut ainsi être remplacé ou changé si besoin). En ayant des séparations nettes entres les différentes parties, Django peut se scaler lors d'une hausse de trafic en ajoutant du hardware à tous les niveaux : serveurs cache, serveurs de base de données, serveurs d'application. Certains des sites les plus fréquentés ont réussi à scaler Django pour répondre à leur demande (par exemple, Instagram et Disqus pour ne nommer qu'eux deux).
- Maintenable
- : Les principes de design du code Django encouragent la création d'un code simple à maintenir et réutilisable. Il fait notamment appel à la philosophie du Ne Vous Répétez Pas (DRY pour Don't Repeat Yourself en anglais), afin d'éviter toute duplication superflue, réduisant la taille de votre code. Django promeut aussi le regroupement de fonctionnalités reliées entre elles en "applications" réutilisables et, à un plus bas niveau, regroupe des lignes de code dépendantes entre elles en modules (suivant les lignes du motif d'architecture Modèle-vue-contrôleur (MVC)).
- Portable
@@ -73,17 +73,17 @@ Django vous aide à écrire une application qui est:
Django a continué à se développer et à s'améliorer, depuis sa première sortie (1.0) en Septembre 2008 jusqu'à la version 2.0 récemment sortie (2017). Chaque sortie a ajouté son lot de nouvelles fonctionnalités et de corrections de bugs, allant du support de nouveaux types de bases de données, de moteurs de templates et de cache, à l'addition de fonctions et de classes de vues 'génériques' (qui réduisent la quantité de code que doivent écrire les développeurs pour tout un tas de tâches de programmation).
-> **Note :** Consultez les [notes de publication](https://docs.djangoproject.com/en/1.10/releases/) sur le site web de Django pour voir les changements apportés dans les versions récentes, ainsi que tout le travail accompli pour améliorer Django.
+> **Note :** Consultez les [notes de publication](https://docs.djangoproject.com/en/1.10/releases/) sur le site web de Django pour voir les changements apportés dans les versions récentes, ainsi que tout le travail accompli pour améliorer Django.
Désormais, Django est un projet open-source collaboratif florissant, avec plusieurs milliers d'utilisateurs et de contributeurs. Bien que plusieurs fonctionnalités reflètent encore ses origines, Django a évolué en un framework versatile capable de développer n'importe quel type de site web.
## À quel point Django est-il populaire ?
-Il n'y a pas encore de mesure toute prête et définitive de la popularité des frameworks orientés serveur (bien que des sites comme [Hot Frameworks](http://hotframeworks.com/) tentent d'estimer cette popularité en utilisant des moyens comme le comptage de projets GitHub et de questions sur StackOverflow pour chaque plateforme). Une meilleure question serait plutôt est-ce que Django est "suffisamment populaire" pour éviter les problèmes des plateformes moins populaires. Va-t-il continuer d'évoluer ? Pourrez-vous obtenir de l'aide si vous en avez besoin ? Aurez-vous des opportunités d'emploi si vous apprenez Django ?
+Il n'y a pas encore de mesure toute prête et définitive de la popularité des frameworks orientés serveur (bien que des sites comme [Hot Frameworks](http://hotframeworks.com/) tentent d'estimer cette popularité en utilisant des moyens comme le comptage de projets GitHub et de questions sur StackOverflow pour chaque plateforme). Une meilleure question serait plutôt est-ce que Django est "suffisamment populaire" pour éviter les problèmes des plateformes moins populaires. Va-t-il continuer d'évoluer ? Pourrez-vous obtenir de l'aide si vous en avez besoin ? Aurez-vous des opportunités d'emploi si vous apprenez Django ?
Si l'on se base sur la quantité de sites web reconnus qui utilisent Django, la quantité de personnes contribuant à son code source, et la quantité de personnes fournissant du support libre ou payant, alors oui, Django est un framework populaire !
-Parmi les sites web qui utilisent Django, on retrouve : Disqus, Instagram, la Knight Foundation, la MacArthur Foundation, Mozilla, National Geographic, l'Open Knowledge Foundation, Pinterest et Open Stack (source : [Page d'accueil de Django](https://www.djangoproject.com/)).
+Parmi les sites web qui utilisent Django, on retrouve : Disqus, Instagram, la Knight Foundation, la MacArthur Foundation, Mozilla, National Geographic, l'Open Knowledge Foundation, Pinterest et Open Stack (source : [Page d'accueil de Django](https://www.djangoproject.com/)).
## Django est-il restrictif ?
@@ -103,10 +103,10 @@ Les applications web Django regroupent généralement le code qui gère chacune
![](basic-django.png)
-- **URLs :**  Bien qu'il soit possible de traiter les requêtes de chaque URL via une fonction unique, il est bien plus viable d'écrire une fonction de vue isolée qui gèrera chaque ressource. Un mapper URL est utilisé pour rediriger les requêtes HTTP à la vue appropriée d'après l'URL de requête. Le mapper URL peut aussi faire la correspondance entre des patterns de chaînes de caractères ou de nombres qui apparaissent dans une URL et passer ces derniers comme données dans une fonction de vue.
-- **Vues :** Une vue est une fonction de gestion des requêtes, qui reçoit des requêtes HTTP et renvoie des réponses HTTP. Les vues accèdent aux données requises pour satisfaire des requêtes via des _modèles_, et délèguent le formatage des réponses aux *templates*.
-- **Modèles :** Les modèles sont des objets Python, qui définissent la structure des données d'une application, et fournissent des mécanismes de gestion (ajout, modification, suppression) et requêtent les enregistrements d'une base de données.
-- **Templates:** Un template est un fichier texte qui définit la structure ou la mise en page d'un fichier (comme une page HTML), avec des balises utilisées pour représenter le contenu. Une *vue* peut créer une page HTML en dynamique en utilisant un template HTML, en la peuplant avec les données d'un *modèle*. Un template peut-être utilisé pour définir la structure de n'importe quel type de fichier; il n'est pas obligatoire que ce dernier soit un HTML !
+- **URLs :** Bien qu'il soit possible de traiter les requêtes de chaque URL via une fonction unique, il est bien plus viable d'écrire une fonction de vue isolée qui gèrera chaque ressource. Un mapper URL est utilisé pour rediriger les requêtes HTTP à la vue appropriée d'après l'URL de requête. Le mapper URL peut aussi faire la correspondance entre des patterns de chaînes de caractères ou de nombres qui apparaissent dans une URL et passer ces derniers comme données dans une fonction de vue.
+- **Vues :** Une vue est une fonction de gestion des requêtes, qui reçoit des requêtes HTTP et renvoie des réponses HTTP. Les vues accèdent aux données requises pour satisfaire des requêtes via des _modèles_, et délèguent le formatage des réponses aux *templates*.
+- **Modèles :** Les modèles sont des objets Python, qui définissent la structure des données d'une application, et fournissent des mécanismes de gestion (ajout, modification, suppression) et requêtent les enregistrements d'une base de données.
+- **Templates:** Un template est un fichier texte qui définit la structure ou la mise en page d'un fichier (comme une page HTML), avec des balises utilisées pour représenter le contenu. Une *vue* peut créer une page HTML en dynamique en utilisant un template HTML, en la peuplant avec les données d'un *modèle*. Un template peut-être utilisé pour définir la structure de n'importe quel type de fichier; il n'est pas obligatoire que ce dernier soit un HTML !
> **Note :** Django mentionne cette organisation sous le nom d'architecture "Modèle Vue Template". Elle a plusieurs similarités avec l'architecture [Modèle Vue Contrôleur](/fr/docs/Web/Apps/Fundamentals/Modern_web_app_architecture/MVC_architecture).
@@ -114,27 +114,27 @@ Les sections ci-dessous vous donneront une idée de ce à quoi ressemble ces dif
### Envoyer la requête à la bonne vue (urls.py)
-Le mapper URL est généralement stocké dans un fichier nommé **urls.py**. Dans l'exemple ci-dessous, le mapper (`urlpatterns`) définit une liste de mappings entre des *routes* (des *patterns* d'URL spécifiques*)* et leur fonction de vue correspondante. Si une requête HTTP est reçue dont l'URL correspond à un pattern spécifié, la fonction vue associée sera alors appelée et passée dans la requête.
+Le mapper URL est généralement stocké dans un fichier nommé **urls.py**. Dans l'exemple ci-dessous, le mapper (`urlpatterns`) définit une liste de mappings entre des *routes* (des *patterns* d'URL spécifiques*)* et leur fonction de vue correspondante. Si une requête HTTP est reçue dont l'URL correspond à un pattern spécifié, la fonction vue associée sera alors appelée et passée dans la requête.
urlpatterns = [
path('admin/', admin.site.urls),
-   path('book/<int:id>/', views.book-detail, name='book-detail'),
+ path('book/<int:id>/', views.book-detail, name='book-detail'),
path('catalog/', include('catalog.urls')),
re_path(r'^([0-9]+)/$', views.best),
]
-L'objet `urlpatterns` est une liste de fonctions `path()` et/ou `re_path()`(les listes en Python sont définies en utilisant des crochets), où des éléments sont séparés par des virgules et peuvent avoir une [virgule de traîne optionnelle](https://docs.python.org/2/faq/design.html#why-does-python-allow-commas-at-the-end-of-lists-and-tuples). Par exemple : `[item1, item2, item3,]`).
+L'objet `urlpatterns` est une liste de fonctions `path()` et/ou `re_path()`(les listes en Python sont définies en utilisant des crochets), où des éléments sont séparés par des virgules et peuvent avoir une [virgule de traîne optionnelle](https://docs.python.org/2/faq/design.html#why-does-python-allow-commas-at-the-end-of-lists-and-tuples). Par exemple : `[item1, item2, item3,]`).
Le premier argument de chaque méthode est une route (pattern) qui sera reconnu.
-La méthode `path()` utilise des chevrons pour définir les parties de l'URL qui seront capturées et passées dans les fonctions vues comme arguments nommés. La fonction `re_path()` utilise une approche de correspondance de pattern flexible, connue sous le nom d'expression régulière. Nous parlerons de ces dernières dans un prochain article !
+La méthode `path()` utilise des chevrons pour définir les parties de l'URL qui seront capturées et passées dans les fonctions vues comme arguments nommés. La fonction `re_path()` utilise une approche de correspondance de pattern flexible, connue sous le nom d'expression régulière. Nous parlerons de ces dernières dans un prochain article !
-Le second argument est une autre fonction qui sera appelée quand le pattern sera reconnu. La notation `views.book-detail` indique que la fonction s'appelle `book-detail()` , et qu'elle se trouve dans un module appelé `views` (i.e. dans un fichier intitulé `views.py`)
+Le second argument est une autre fonction qui sera appelée quand le pattern sera reconnu. La notation `views.book-detail` indique que la fonction s'appelle `book-detail()` , et qu'elle se trouve dans un module appelé `views` (i.e. dans un fichier intitulé `views.py`)
### Traiter la requête (views.py)
Les vues sont le coeur des applications web. Elles reçoivent des requêtes HTTP de clients web et renvoient des réponses HTTP. Entretemps, elles mobilisent les autres ressources du framework pour accéder aux bases de données, préparer le rendu des templates, etc.
-L'exemple ci-dessous montre une fonction vue minimale `index()`, qui pourrait être appelée par notre mapper URL de la section précédente. Comme toutes les fonctions vues, elle reçoit un objet  `HttpRequest` comme paramètre (`request`) et renvoie un objet `HttpResponse`. Dans notre cas on ne fait rien de spécial avec la requête; et notre réponse ne renvoie qu'une chaîne de caractères brute. Nous vons montrerons une requête plus intéressante dans une autre section.
+L'exemple ci-dessous montre une fonction vue minimale `index()`, qui pourrait être appelée par notre mapper URL de la section précédente. Comme toutes les fonctions vues, elle reçoit un objet `HttpRequest` comme paramètre (`request`) et renvoie un objet `HttpResponse`. Dans notre cas on ne fait rien de spécial avec la requête; et notre réponse ne renvoie qu'une chaîne de caractères brute. Nous vons montrerons une requête plus intéressante dans une autre section.
```python
## nom du fichier : view.py (fonction vue Django)
@@ -150,16 +150,16 @@ def index(request):
> **Note :** Un peu de Python :
>
-> - Les [modules Python](https://docs.python.org/3/tutorial/modules.html) sont des librairies de fonctions, stockés dans des fichiers séparés que l'on peut vouloir utiliser dans notre code. Ici, nous importons l'objet  `HttpResponse` du module `django.http` pour qu'on puisse l'utiliser dans notre vue : `from django.http import HttpResponse` . Il y a d'autres façons d'importer quelques objets (ou tous les objets) d'un module.
-> - Les fonctions sont déclarées en utilisant le mot-clé `def` comme indiqué ci-dessus, avec des paramètres nommés listés entre parenthèses après le nom de la fonction; la ligne se termine ensuite par deux points. Notez que les lignes suivantes sont **indentées**.  L'indentation est importante, car elle spécifie que les lignes de code sont contenues dans un bloc particulier (l'indentation obligatoire est un élément clé de Python, et une des raisons pour lesquelles le code Python est si simple à lire).
+> - Les [modules Python](https://docs.python.org/3/tutorial/modules.html) sont des librairies de fonctions, stockés dans des fichiers séparés que l'on peut vouloir utiliser dans notre code. Ici, nous importons l'objet `HttpResponse` du module `django.http` pour qu'on puisse l'utiliser dans notre vue : `from django.http import HttpResponse` . Il y a d'autres façons d'importer quelques objets (ou tous les objets) d'un module.
+> - Les fonctions sont déclarées en utilisant le mot-clé `def` comme indiqué ci-dessus, avec des paramètres nommés listés entre parenthèses après le nom de la fonction; la ligne se termine ensuite par deux points. Notez que les lignes suivantes sont **indentées**. L'indentation est importante, car elle spécifie que les lignes de code sont contenues dans un bloc particulier (l'indentation obligatoire est un élément clé de Python, et une des raisons pour lesquelles le code Python est si simple à lire).
Les vues sont généralement stockées dans un fichier nommé **views.py**.
### Définir les modèles de données (models.py)
-Les applications web Django gèrent et requêtent les données via des objets Python appelés modèles. Les modèles définissent la structure des données stockées, ce qui inclut le champ *types* ainsi qu'au besoin leur taille maximum, les valeurs par défaut, les options de listes pouvant être sélectionnées, le texte d'aide pour la documentation — vous pouvez choisir ce dont vous avez besoin par rapport aux spécifications de votre projet. Une fois que vous avez choisi la base de données que vous souhaitez utiliser, vous n'avez pas du tout besoin de communiquer avec elle directement — vous n'avez qu'à écrire la structure de votre modèle, Django s'occupe du sale boulot de la communication avec la base de données pour vous.
+Les applications web Django gèrent et requêtent les données via des objets Python appelés modèles. Les modèles définissent la structure des données stockées, ce qui inclut le champ *types* ainsi qu'au besoin leur taille maximum, les valeurs par défaut, les options de listes pouvant être sélectionnées, le texte d'aide pour la documentation — vous pouvez choisir ce dont vous avez besoin par rapport aux spécifications de votre projet. Une fois que vous avez choisi la base de données que vous souhaitez utiliser, vous n'avez pas du tout besoin de communiquer avec elle directement — vous n'avez qu'à écrire la structure de votre modèle, Django s'occupe du sale boulot de la communication avec la base de données pour vous.
-L'extrait de code ci-dessous montre un modèle Django très simple pour un objet `Team`. La classe `Team` est dérivée de la classe Django `models.Model`. Elle définit le nom et le niveau de l'équipe comme des chaînes de caractères et elle spécifie le nombre maximum de caractères pouvant être stockés pour chaque enregistrement. Le champ `team_level` peut avoir plusieurs valeurs, donc nous le définissons comme une liste de choix, puis on fournit à la classe un mapping entre les choix qui seront affichés et les données stockées, avec une valeur par défaut.
+L'extrait de code ci-dessous montre un modèle Django très simple pour un objet `Team`. La classe `Team` est dérivée de la classe Django `models.Model`. Elle définit le nom et le niveau de l'équipe comme des chaînes de caractères et elle spécifie le nombre maximum de caractères pouvant être stockés pour chaque enregistrement. Le champ `team_level` peut avoir plusieurs valeurs, donc nous le définissons comme une liste de choix, puis on fournit à la classe un mapping entre les choix qui seront affichés et les données stockées, avec une valeur par défaut.
```python
# nom du fichier : models.py
@@ -167,28 +167,28 @@ L'extrait de code ci-dessous montre un modèle Django très simple pour un objet
from django.db import models
class Team(models.Model):
-  team_name = models.CharField(max_length=40)
-
-    TEAM_LEVELS = (
-        ('U09', 'Under 09s'),
-        ('U10', 'Under 10s'),
-        ('U11', 'Under 11s'),
-  ... # lister les autres niveaux d'équipes
-    )
-    team_level = models.CharField(max_length=3,choices=TEAM_LEVELS,default='U11')
+ team_name = models.CharField(max_length=40)
+
+ TEAM_LEVELS = (
+ ('U09', 'Under 09s'),
+ ('U10', 'Under 10s'),
+ ('U11', 'Under 11s'),
+ ... # lister les autres niveaux d'équipes
+ )
+ team_level = models.CharField(max_length=3,choices=TEAM_LEVELS,default='U11')
```
> **Note :** Un peu de Python :
>
-> - Python supporte la "programmation orientée-objet", un type de programmation où l'on organise notre code en objets, ce qui inclut les données et fonctions liées qui agiront sur les données. Les objets peuvent être hérités/étendus/dérivés d'autres objets, ce qui permet à ces objets de partager un comportement commun. En Python, on utilise le mot-clé `class` pour définir le "squelette" d'un objet. On peut créer plusieurs *instances* spécifiques de ce type d'objet d'après le modèle d'une classe.
+> - Python supporte la "programmation orientée-objet", un type de programmation où l'on organise notre code en objets, ce qui inclut les données et fonctions liées qui agiront sur les données. Les objets peuvent être hérités/étendus/dérivés d'autres objets, ce qui permet à ces objets de partager un comportement commun. En Python, on utilise le mot-clé `class` pour définir le "squelette" d'un objet. On peut créer plusieurs *instances* spécifiques de ce type d'objet d'après le modèle d'une classe.
>
-> Ainsi par exemple, nous avons ici une classe `Team`, dérivée de la classe `Model`.  Cela signifie que c'est un modèle, et qu'elle contiendra toutes les méthodes d'un modèle, mais qu'on peut aussi lui donner des caractéristiques spécifiques. Dans notre modèle, nous définissons les champs dont aura besoin notre base de données, en leur donnant des noms spécifiques. Django utilisera ces définitions, ce qui inclut aussi le nom des champs, pour créer la base de données sous-jacente.
+> Ainsi par exemple, nous avons ici une classe `Team`, dérivée de la classe `Model`. Cela signifie que c'est un modèle, et qu'elle contiendra toutes les méthodes d'un modèle, mais qu'on peut aussi lui donner des caractéristiques spécifiques. Dans notre modèle, nous définissons les champs dont aura besoin notre base de données, en leur donnant des noms spécifiques. Django utilisera ces définitions, ce qui inclut aussi le nom des champs, pour créer la base de données sous-jacente.
### Requêter les données (views.py)
Le modèle Django fournit une API de requête simplifiée qui nous permet de faire des recherches dans une base de données. Cette API peut inclure plusieurs champs à la fois en supportant plusieurs critères (e.g. exactement, insensible à la casse, supérieur à, etc.), et peut supporter des déclarations complexes (par exemple, vous pouvez spécifier une recherche sur les équipes U11 ayant un nom d'équipe commençant par "Fr" ou se terminant par "al").
-L'extrait de code ci-dessous montre une fonction vue (gestionnaire de ressources) affichant toutes nos équipes U09. La ligne en gras montre comment on peut utiliser l'API de requête pour filtrer tous les enregistrements où le champ `team_level` comprend strictement le texte 'U09' (notez comment ce critère est passé dans la fonction `filter()` comme argument, où le nom du champ et le type de correspondance sont séparés par un double underscore : **team_level\_\_exact**).
+L'extrait de code ci-dessous montre une fonction vue (gestionnaire de ressources) affichant toutes nos équipes U09. La ligne en gras montre comment on peut utiliser l'API de requête pour filtrer tous les enregistrements où le champ `team_level` comprend strictement le texte 'U09' (notez comment ce critère est passé dans la fonction `filter()` comme argument, où le nom du champ et le type de correspondance sont séparés par un double underscore : **team_level\_\_exact**).
```python
## nom du fichier : views.py
@@ -197,18 +197,18 @@ from django.shortcuts import render
from .models import Team
def index(request):
-    list_teams = Team.objects.filter(team_level__exact="U09")
-    context = {'youngest_teams': list_teams}
-    return render(request, '/best/index.html', context)
+ list_teams = Team.objects.filter(team_level__exact="U09")
+ context = {'youngest_teams': list_teams}
+ return render(request, '/best/index.html', context)
```
-Cette fonction utilise la fonction `render()` pour créer la `HttpResponse` qui est renvoyée au navigateur. Cette fonction est un _raccourci_; elle créée un fichier HTML en combinant un template HTML spécifique et des données à insérer dans le template (fournies dans la variable appelée "`context`"). Dans la prochaine section, nous vous montrons comment des données sont insérées dans le template pour générer le HTML.
+Cette fonction utilise la fonction `render()` pour créer la `HttpResponse` qui est renvoyée au navigateur. Cette fonction est un _raccourci_; elle créée un fichier HTML en combinant un template HTML spécifique et des données à insérer dans le template (fournies dans la variable appelée "`context`"). Dans la prochaine section, nous vous montrons comment des données sont insérées dans le template pour générer le HTML.
### Renvoyer les données (templates HTML)
Les systèmes template vous permettent de spécifier la structure d'un document en output, en utilisant des paramètres fictifs qui seront substitués par les données lorsque la page est générée. Les templates sont souvent utilisées pour créer du HTML, mais ils peuvent aussi être utilisées pour créer d'autres types de documents. Django supporte à la fois son système natif de template ainsi qu'une autre librairie Python populaire prête à l'emploi appelée Jinja2 (il peut aussi supporter d'autres systèmes au besoin).
-L'extrait de code ci-dessous montre à quoi pourrait ressembler le template HTML de la section précédente une fois appelé par la fonction `render().` Ce template a été écrit avec l'hypothèse qu'il aurait accès à une liste de variables appelées `youngest_teams` lorsqu'il est généré (contenu dans la variable `context` dans la fonction `render()` ci-dessus). Dans le squelette HTML nous avons une expression qui vérifie tout d'abord que la variable `youngest_teams` existe, puis itère dessus dans une boucle `for` . À chaque itération, le template affiche la valeur du `team_name` de chaque équipe dans un élément {{htmlelement("li")}}.
+L'extrait de code ci-dessous montre à quoi pourrait ressembler le template HTML de la section précédente une fois appelé par la fonction `render().` Ce template a été écrit avec l'hypothèse qu'il aurait accès à une liste de variables appelées `youngest_teams` lorsqu'il est généré (contenu dans la variable `context` dans la fonction `render()` ci-dessus). Dans le squelette HTML nous avons une expression qui vérifie tout d'abord que la variable `youngest_teams` existe, puis itère dessus dans une boucle `for` . À chaque itération, le template affiche la valeur du `team_name` de chaque équipe dans un élément {{htmlelement("li")}}.
```python
## nom du fichier : best/templates/best/index.html
@@ -218,13 +218,13 @@ L'extrait de code ci-dessous montre à quoi pourrait ressembler le template HTML
<body>
{% if youngest_teams %}
-    <ul>
-    {% for team in youngest_teams %}
-        <li>\{\{ team.team_name \}\}</li>
-    {% endfor %}
-    </ul>
+ <ul>
+ {% for team in youngest_teams %}
+ <li>\{\{ team.team_name \}\}</li>
+ {% endfor %}
+ </ul>
{% else %}
-    <p>No teams are available.</p>
+ <p>No teams are available.</p>
{% endif %}
</body>
@@ -235,11 +235,11 @@ L'extrait de code ci-dessous montre à quoi pourrait ressembler le template HTML
Les sections précédentes présentent les caractéristiques principales que vous utiliserez dans presque toutes vos applications web : mapping URL, vues, modèles et templates. Parmi les autres caractéristiques offertes par Django, on peut aussi trouver :
-- **Formulaires** : Les formulaires HTML sont utilisés pour collecter des données utilisateurs qui seront traitées sur le serveur. Django simplifie la création, la validation et le traitement des formulaires.
-- **Authentification et permissions des utilisateurs**: Django inclut un système d'authentification utilisateur et de gestion des permissions robuste créé avec la sécurité comme priorité lors de sa conception.
-- **Cache** : Générer du contenu en dynamique demande bien plus de ressources computationnelles (et est plus lent) que de servir du contenu statique. Django fournit un système de cache flexible qui vous permet de stocker toute ou une partie d'une page afin qu'elle ne soit re-générée que lorsque c'est nécessaire.
-- **Administration du site** : L'administration du site avec Django est incluse par défaut lorsque vous créez une application en utilisant le squelette de base. Django permet de créer très simplement une page d'administration où les administrateurs peuvent créer, éditer et voir n'importe quel modèle de données sur votre site.
-- **Sérialisation des données** : Django permet de simplifier la sérialisation et de servir vos données en XML ou en JSON. Cela peut être utile si vous créez un service web (un site web dont le seul but est de servir des données qui seront utilisées par d'autres applications ou sites, mais n'affiche rien par lui-même), ou quand vous créez un site web où le code côté client s'occupe d'afficher les données.
+- **Formulaires**&nbsp;: Les formulaires HTML sont utilisés pour collecter des données utilisateurs qui seront traitées sur le serveur. Django simplifie la création, la validation et le traitement des formulaires.
+- **Authentification et permissions des utilisateurs**: Django inclut un système d'authentification utilisateur et de gestion des permissions robuste créé avec la sécurité comme priorité lors de sa conception.
+- **Cache**&nbsp;: Générer du contenu en dynamique demande bien plus de ressources computationnelles (et est plus lent) que de servir du contenu statique. Django fournit un système de cache flexible qui vous permet de stocker toute ou une partie d'une page afin qu'elle ne soit re-générée que lorsque c'est nécessaire.
+- **Administration du site**&nbsp;: L'administration du site avec Django est incluse par défaut lorsque vous créez une application en utilisant le squelette de base. Django permet de créer très simplement une page d'administration où les administrateurs peuvent créer, éditer et voir n'importe quel modèle de données sur votre site.
+- **Sérialisation des données**&nbsp;: Django permet de simplifier la sérialisation et de servir vos données en XML ou en JSON. Cela peut être utile si vous créez un service web (un site web dont le seul but est de servir des données qui seront utilisées par d'autres applications ou sites, mais n'affiche rien par lui-même), ou quand vous créez un site web où le code côté client s'occupe d'afficher les données.
## Sommaire
diff --git a/files/fr/learn/server-side/django/models/index.md b/files/fr/learn/server-side/django/models/index.md
index 97aefd906c..4ec6ede427 100644
--- a/files/fr/learn/server-side/django/models/index.md
+++ b/files/fr/learn/server-side/django/models/index.md
@@ -49,7 +49,7 @@ Vous pourriez aussi utiliser les modèles pour définir des listes d'options (co
Le choix du modèle étant posé, nous avons à considérer les relations entre les objets. Django permet d'établir trois types de relation : les relations un à un qui mettent en relation un et un seul objet avec un autre (`OneToOneField`), les relations un à n qui partage l'appartenance d'un objet à avec d'autres (`ForeignKey`) et les relations n à n qui associent des groupes d'objets entre-eux (`ManyToManyField`).
-Avec ces éléments présents à l'esprit, le diagramme  de classes UML ci-dessous décrit les objets de la bibliothèque.
+Avec ces éléments présents à l'esprit, le diagramme de classes UML ci-dessous décrit les objets de la bibliothèque.
![LocalLibrary Model UML](local_library_model_uml.png)
@@ -70,24 +70,24 @@ Les objets sont **toujours** définis dans le fichier **models.py** de chaque ap
from django.db import models
class MyModelName(models.Model):
-     """A typical class defining a model, derived from the Model class."""
+ """A typical class defining a model, derived from the Model class."""
-   # Fields
-     my_field_name = models.CharField(max_length=20, help_text='Enter field documentation')
-   ...
+ # Fields
+ my_field_name = models.CharField(max_length=20, help_text='Enter field documentation')
+ ...
-   # Metadata
+ # Metadata
class Meta:
-   ordering = ['-my_field_name']
+ ordering = ['-my_field_name']
-   # Methods
-     def get_absolute_url(self):
-        """Returns the url to access a particular instance of MyModelName."""
-        return reverse('model-detail-view', args=[str(self.id)])
+ # Methods
+ def get_absolute_url(self):
+ """Returns the url to access a particular instance of MyModelName."""
+ return reverse('model-detail-view', args=[str(self.id)])
-     def __str__(self):
-         """String for representing the MyModelName object (in Admin site etc.)."""
-         return self.my_field_name
+ def __str__(self):
+ """String for representing the MyModelName object (in Admin site etc.)."""
+ return self.my_field_name
Détaillons ce qu'il en retourne :
@@ -99,7 +99,7 @@ Chaque objet peut contenir autant d'attributs que de besoin et de quelque type q
my_field_name = models.CharField(max_length=20, help_text='Enter field documentation')
```
-Dans l'exemple ci-dessus, le champs est une chaîne de caractères — de type `models.CharField` —  dont le nom est `my_field_name`. Les champs ont des types pré-définis représentés par une classe d'objet Django qui va permettre de caractériser une champ du modèle de données. Cela permet aussi de valider les données qui seront fournies via les formulaires du site web décrits avec le langage HTML. Les classes caractérisant les type de champs peuvent accepter des paramètres pour préciser les contraintes appliquées à ce champ. Dans cet exemple, deux arguments sont indiqués :
+Dans l'exemple ci-dessus, le champs est une chaîne de caractères — de type `models.CharField` — dont le nom est `my_field_name`. Les champs ont des types pré-définis représentés par une classe d'objet Django qui va permettre de caractériser une champ du modèle de données. Cela permet aussi de valider les données qui seront fournies via les formulaires du site web décrits avec le langage HTML. Les classes caractérisant les type de champs peuvent accepter des paramètres pour préciser les contraintes appliquées à ce champ. Dans cet exemple, deux arguments sont indiqués :
- `max_length=20` — Défini que ce champs fait au plus 20 caractères.
- `help_text='Enter field documentation'` — attribue un label par défaut qui sera affiché dans la page web par le navigateur.
@@ -126,7 +126,7 @@ L'ensemble [des options de champs](https://docs.djangoproject.com/fr/2.2/ref/mod
Vous trouverez ci-dessous les arguments les principaux type de champs :
-- [CharField](https://docs.djangoproject.com/fr/2.2/ref/models/fields/#django.db.models.CharField) caractérise un champ de type chaîne de caractères de taille maximale fixe. Ce champ nécessite l'option obligatoire `max_length` pour définir la taille maximale de la chaîne de caractère.
+- [CharField](https://docs.djangoproject.com/fr/2.2/ref/models/fields/#django.db.models.CharField) caractérise un champ de type chaîne de caractères de taille maximale fixe. Ce champ nécessite l'option obligatoire `max_length` pour définir la taille maximale de la chaîne de caractère.
- [TextField](https://docs.djangoproject.com/fr/2.2/ref/models/fields/#django.db.models.TextField) caractérise un champs texte (de longeur non définit dans la base de données). Si l'option `max_length` est utilisé, celui-ci précisera la taille du champs texte des formulaires web mais n'aura pas d'impact dans la définition du champs en base de données.
- [IntegerField](https://docs.djangoproject.com/fr/2.2/ref/models/fields/#django.db.models.IntegerField "django.db.models.IntegerField") caractérise un champs de type nombre entier.
- [DateField](https://docs.djangoproject.com/fr/2.2/ref/models/fields/#datefield) et [DateTimeField](https://docs.djangoproject.com/fr/2.2/ref/models/fields/#datetimefield) sont des type utilisées pour caractériser une date et une heure comme les objets `datetime.date` et `datetime.datetime` en Python. Les options (incompatibles ensemble) les plus courantes pour ces champs sont l'enregistrement au moment de la sauvegarde (`auto_now=True`), l'enregistrement à la création de l'objet (`auto_now_add`) et une valeur par défaut (`default)` qui pourra être changée par l'utilisateur.
@@ -140,7 +140,7 @@ L'ensemble [des types de champs](https://docs.djangoproject.com/fr/2.2/ref/model
#### Métadonnées
-Vous avez la capacité de déclarer des métadonnées à l'aide de la classe  `class Meta`, comme précisé ci-dessous :
+Vous avez la capacité de déclarer des métadonnées à l'aide de la classe `class Meta`, comme précisé ci-dessous :
```python
class Meta:
@@ -157,7 +157,7 @@ ordering = ['title', '-pubdate']
Les livres sont présenté dans l'ordre alphabétique de leur titre, puis dans l'ordre chronologique du plus récent au plus ancien.
-Un autre attribut très utile est celui d'un nom vernaculaire pour la classe, `verbose_name`  peut être au singulier et au pluriel :
+Un autre attribut très utile est celui d'un nom vernaculaire pour la classe, `verbose_name` peut être au singulier et au pluriel :
```python
verbose_name = 'BetterName'
@@ -177,7 +177,7 @@ Comme tout objet Python, une classe héritée de `model` peut utiliser des méth
```python
def __str__(self):
-  return self.field_name
+ return self.field_name
```
Une seconde méthode très utilisée dans le cadriciel Django est `get_absolute_url()`. Elle permet de fournir un URL pour afficher dans le site web le contenu de de chacun des enregistrements associés au modèle de données décrit. Si vous utilisez cette méthode, Django ajoutera un bouton pour permet de visualiser le détail des enregistrements. Classiquement, une méthode `get_absolute_url()` est de la forme :
@@ -196,7 +196,7 @@ Vous pouvez aussi définir toute les méthodes dont vous aurez besoin pour manip
### Administration des données
-A partir du moment où vous avez créé votre modèle de données, vous pouvez manipuler les instances pour créer, mettre à jour ou supprimer les enregistrements en base de données. Vous pouvez aussi faire des requêtes pour obtenir tout ou parti des enregistrements de la base. L'objet de cette section est d'évoquer la manière de manipuler ces données et sera revu progressivement dans les avancées de l'application Bibliothèque.
+A partir du moment où vous avez créé votre modèle de données, vous pouvez manipuler les instances pour créer, mettre à jour ou supprimer les enregistrements en base de données. Vous pouvez aussi faire des requêtes pour obtenir tout ou parti des enregistrements de la base. L'objet de cette section est d'évoquer la manière de manipuler ces données et sera revu progressivement dans les avancées de l'application Bibliothèque.
#### Créer et modifier des enregistrements
@@ -252,7 +252,7 @@ Le marqueur "double souligné" permet de construire une chaîne de navigation à
books_containing_genre = Book.objects.filter(genre__name__icontains='fiction')
```
-> **Note :** Vous pouvez construire une chemin pour naviguer dans autant de niveaux de relation (`ForeignKey`/`ManyToManyField`) que vous en avez besoin en concaténant des noms de champs à l'aide  (\_\_) . Si par exemple vous souhaitez trouver un livre (`Book`) qui possède différents type (`type`) de couvertures (`cover`) identifiées par des noms (`name`) alors le chemin sera du type : `type__cover__name__exact='hard'.`
+> **Note :** Vous pouvez construire une chemin pour naviguer dans autant de niveaux de relation (`ForeignKey`/`ManyToManyField`) que vous en avez besoin en concaténant des noms de champs à l'aide (\_\_) . Si par exemple vous souhaitez trouver un livre (`Book`) qui possède différents type (`type`) de couvertures (`cover`) identifiées par des noms (`name`) alors le chemin sera du type : `type__cover__name__exact='hard'.`
La mise en oeuvre des requêtes est très riches en fonction des modèles et des relations, de sous-ensemble de données, etc. Pour une informations détaillées, vous devez consulter [les requêtes](https://docs.djangoproject.com/fr/2.2/topics/db/queries/) sur le site de référence de Django.
@@ -268,17 +268,17 @@ from django.db import models
### L'objet Genre
-Cet objet est utilisé pour décrire et enregistrer le genre littéraire des livres — par exemple une fiction, une polard ou un roman. Comme cela a été évoqué précédemment, nous créons un modèle de données plutôt que de gérer cela à l'aide de texte libre ou de codage en dur. Copiez le texte ci-dessous à la fin du fichier _models.py_.
+Cet objet est utilisé pour décrire et enregistrer le genre littéraire des livres — par exemple une fiction, une polard ou un roman. Comme cela a été évoqué précédemment, nous créons un modèle de données plutôt que de gérer cela à l'aide de texte libre ou de codage en dur. Copiez le texte ci-dessous à la fin du fichier _models.py_.
```python
class Genre(models.Model):
-    """Cet objet représente une catégorie ou un genre littéraire."""
-    name = models.CharField(max_length=200, help_text='Enter a book genre (e.g. Science Fiction)')
+ """Cet objet représente une catégorie ou un genre littéraire."""
+ name = models.CharField(max_length=200, help_text='Enter a book genre (e.g. Science Fiction)')
-    def __str__(self):
-        """Cette fonction est obligatoirement requise par Django.
+ def __str__(self):
+ """Cette fonction est obligatoirement requise par Django.
Elle retourne une chaîne de caractère pour identifier l'instance de la classe d'objet."""
-        return self.name
+ return self.name
```
L'objet, en relation avec la base de données, possède un seul attribut (`name`) de type chaîne de caractères (`CharField`), qui sera utilisé pour décrire le genre d'un livre (limité à 200 caractères). Une option (`help_text)` permet d'utiliser une étiquettes d'aide dans les pages et formulaires du site web. La méthode `__str__()`, qui retourne simplement le nom du genre littéraire de chaque enregistrement. Puisque qu'il n'y a pas de nom vernaculaire (`verbose_name`), le champ sera simplement nommé `Name` dans les formulaires.
@@ -291,37 +291,37 @@ Comme précédemment, vous pouvez copier le descriptif de l'objet Book à la fin
from django.urls import reverse # Cette fonction est utilisée pour formater les URL
class Book(models.Model):
-    """Cet objet représente un livre (mais ne traite pas les copies présentes en rayon)."""
-    title = models.CharField(max_length=200)
+ """Cet objet représente un livre (mais ne traite pas les copies présentes en rayon)."""
+ title = models.CharField(max_length=200)
# La clé étrangère (ForeignKey) est utilisée car elle représente correcte le modèle de relation en livre et son auteur :
# Un livre a un seul auteur, mais un auteur a écrit plusieurs livres.
# Le type de l'objet Author est déclré comme une chaîne de caractère car
# la classe d'objet Author n'a pas encore été déclarée dans le fichier
-    author = models.ForeignKey('Author', on_delete=models.SET_NULL, null=True)
+ author = models.ForeignKey('Author', on_delete=models.SET_NULL, null=True)
-    summary = models.TextField(max_length=1000, help_text='Enter a brief description of the book')
-    isbn = models.CharField('ISBN', max_length=13, help_text='13 Character <a href="https://www.isbn-international.org/content/what-isbn">ISBN number</a>')
+ summary = models.TextField(max_length=1000, help_text='Enter a brief description of the book')
+ isbn = models.CharField('ISBN', max_length=13, help_text='13 Character <a href="https://www.isbn-international.org/content/what-isbn">ISBN number</a>')
-    # Le type ManyToManyField décrit correctement le modèle de relation en un livre et un genre.
+ # Le type ManyToManyField décrit correctement le modèle de relation en un livre et un genre.
# un livre peut avoir plusieurs genres littéraire et réciproquement.
-    # Comme la classe d'objets Genre a été définit précédemment, nous pouvons manipuler l'objet.
+ # Comme la classe d'objets Genre a été définit précédemment, nous pouvons manipuler l'objet.
genre = models.ManyToManyField(Genre, help_text='Select a genre for this book')
-    def __str__(self):
-        """Fonction requise par Django pour manipuler les objets Book dans la base de données."""
-        return self.title
+ def __str__(self):
+ """Fonction requise par Django pour manipuler les objets Book dans la base de données."""
+ return self.title
-    def get_absolute_url(self):
-        """Cette fonction est requise pas Django, lorsque vous souhaitez détailler le contenu d'un objet."""
-        return reverse('book-detail', args=[str(self.id)])
+ def get_absolute_url(self):
+ """Cette fonction est requise pas Django, lorsque vous souhaitez détailler le contenu d'un objet."""
+ return reverse('book-detail', args=[str(self.id)])
```
Le genre littéraire est une relation n à n (`ManyToManyField`) car un livre peut avoir plusieurs genres et inversement. Bien que des livres soient écrits à plusieurs, dans le modèle de données présent un livre n'aura qu'un et un seul auteur. Un auteur est donc vu comme une clé étrangère `(ForeignKey`) de telle sorte qu'un livre n'a qu'un seul auteur et une auteur peut avoir écrit plusieurs livres.
-La modélisation des relations entre les objets, c'est le cas pour les deux champs décrits à l'instant, nécessite de manipuler les classes d'objet par leur nom de classe. Vous devez déclarer l'objet par son de classe dans la déclaration de la relation entre les objets, si celui-ci a déjà été déclaré vous pouvez l'utiliser comme un nom d'objet - à l'identique d'une variable Python - ou comme une chaîne de caractère si l'objet n'a pas déjà fait l'objet d'un déclaration. les autres paramètres dans la déclaration des relations permettent de spécifier les comportement des attributs : l'option `null` positionné à `True` permet d'avoir un contenu vide en base de données, la second option `on_delete=models.SET_NULL` qualifie le fonctionnement de cet attribut si l'objet est supprimé en base de données, en l'occurence il peut être positionné à vide en base de données.
+La modélisation des relations entre les objets, c'est le cas pour les deux champs décrits à l'instant, nécessite de manipuler les classes d'objet par leur nom de classe. Vous devez déclarer l'objet par son de classe dans la déclaration de la relation entre les objets, si celui-ci a déjà été déclaré vous pouvez l'utiliser comme un nom d'objet - à l'identique d'une variable Python - ou comme une chaîne de caractère si l'objet n'a pas déjà fait l'objet d'un déclaration. les autres paramètres dans la déclaration des relations permettent de spécifier les comportement des attributs : l'option `null` positionné à `True` permet d'avoir un contenu vide en base de données, la second option `on_delete=models.SET_NULL` qualifie le fonctionnement de cet attribut si l'objet est supprimé en base de données, en l'occurence il peut être positionné à vide en base de données.
-Deux méthodes sont déclarées pour cet objet. La méthode `__str__()` obligatoirement requise par Django pour manipuler les instances d'objet et les enregistrements associés en base. La seconde méthode, `get_absolute_url()`, retourne une URL formatée qui peut être utilisée par le cadriciel pour délivrer le détail de chaque instance d'objet de la classe. Le routage d'URL sera associé au nom  `book-detail`, et nous aurons à définir une vue et un gabarit.
+Deux méthodes sont déclarées pour cet objet. La méthode `__str__()` obligatoirement requise par Django pour manipuler les instances d'objet et les enregistrements associés en base. La seconde méthode, `get_absolute_url()`, retourne une URL formatée qui peut être utilisée par le cadriciel pour délivrer le détail de chaque instance d'objet de la classe. Le routage d'URL sera associé au nom `book-detail`, et nous aurons à définir une vue et un gabarit.
### L'objet BookInstance
@@ -368,7 +368,7 @@ class BookInstance(models.Model):
De nouveaux types de champs sont utilisés :
- Le type `UUIDField` est utilisé pour traiter d'un identifiant unique de livre comme clé primaire. Ce type de champ permet de générer un identifiant unique pour enregistrer et suivre chacune des copies de chacun des livres.
-- Le type `DateField` est utilisé pour enregistrer la date de retour d'un prêt. Ce champ peut-être vide pour gérer le cas des livres dans les rayonnages c'est-à-dire disponibles pour un prêt. Il est fait appel à la classe `Meta` pour permettre de classer les requêtes sur les objet par date de retr
+- Le type `DateField` est utilisé pour enregistrer la date de retour d'un prêt. Ce champ peut-être vide pour gérer le cas des livres dans les rayonnages c'est-à-dire disponibles pour un prêt. Il est fait appel à la classe `Meta` pour permettre de classer les requêtes sur les objet par date de retr
- our.
- Le champ `status` est un type connu (`CharField`) qui définit une liste de choix. Les choix sont définis dans la description de l'objet par l'usage de tuples (une paire clé-valeur) et transmis en option dans la déclaration du champs. Alors que l'utilisateur manipulera les valeurs, les clés seront enregistrées dans la base de données. Enfin, la valeur par défaut est la Maintenance car lorsqu'un ouvrage est créé il n'est pas immédiatement disponible au prêt et n'est pas directement positionné en rayon.
diff --git a/files/fr/learn/server-side/django/skeleton_website/index.md b/files/fr/learn/server-side/django/skeleton_website/index.md
index 24d0ecf3ef..c818f04939 100644
--- a/files/fr/learn/server-side/django/skeleton_website/index.md
+++ b/files/fr/learn/server-side/django/skeleton_website/index.md
@@ -59,8 +59,8 @@ Pour [le site web "Bibliothèque locale"](/fr/docs/Learn/Server-side/Django/Tuto
```bash
locallibrary/ # Website folder
-  manage.py # Script to run Django tools for this project (created using django-admin)
-  locallibrary/ # Website/project folder (created using django-admin)
+ manage.py # Script to run Django tools for this project (created using django-admin)
+ locallibrary/ # Website/project folder (created using django-admin)
catalog/ # Application folder (created using manage.py)
```
@@ -77,7 +77,7 @@ mkdir django_projects
cd django_projects
```
-Pour créer un nouveau projet avec le quadriciel Django, il suffit d'utiliser la commande  `django-admin startproject`. Le résultat de cette commande sera un sous-dossier du nom du projet dans lequel il suffit de s'y déplacer comme indiqué ci-dessous :
+Pour créer un nouveau projet avec le quadriciel Django, il suffit d'utiliser la commande `django-admin startproject`. Le résultat de cette commande sera un sous-dossier du nom du projet dans lequel il suffit de s'y déplacer comme indiqué ci-dessous :
```bash
django-admin startproject locallibrary
@@ -88,12 +88,12 @@ La commande `django-admin` crée une arboresence contenant des fichiers et un so
```bash
locallibrary/
-  manage.py
-  locallibrary/
+ manage.py
+ locallibrary/
__init__.py
-    settings.py
-    urls.py
-    wsgi.py
+ settings.py
+ urls.py
+ wsgi.py
```
Votre répertoire de travail est de la forme :
@@ -117,11 +117,11 @@ La commande ci-dessous va créer l'application _catalog_. Vous devez être dans
python3 manage.py startapp catalog
```
-> **Note :** La commande ci-dessus est exécutée dans un environnement Linux/macOS X. Sous Windows, il se peut que la commande soit : `py -3 manage.py startapp catalog`
+> **Note :** La commande ci-dessus est exécutée dans un environnement Linux/macOS X. Sous Windows, il se peut que la commande soit : `py -3 manage.py startapp catalog`
>
> Si vous travaillez dans un environnement Windows, l'ensemble de la série didactique est écrite pour un environnement Linux/MacOS. Pensez, alors, à remplacer les commandes `python3` par `py -3`.
>
-> Si vous utilisez une version postérieure à la version 3.7.0, la commande devrait désormais être  `py manage.py startapp catalog`
+> Si vous utilisez une version postérieure à la version 3.7.0, la commande devrait désormais être `py manage.py startapp catalog`
Cet outil crée un nouveau dossier, du nom de l'application, et le peuple de fichiers essentiels. La plupart des fichiers ont des noms caractéristiques de leur fonction dans le fonctionnement de Django (par exemple, les vues sont traitées dans **views.py**, le modèle de données dans **models.py**, etc.). Ces fichiers contiennent les éléments minimaux nécessaires à leur utilisation dans le projet.
@@ -129,16 +129,16 @@ Le dossier projet _locallibrary_ est agrémenté d'un nouveau sous-dossier _cata
```bash
locallibrary/
-  manage.py
-  locallibrary/
-  catalog/
-      admin.py
-      apps.py
-      models.py
-      tests.py
-      views.py
-      __init__.py
-  migrations/
+ manage.py
+ locallibrary/
+ catalog/
+ admin.py
+ apps.py
+ models.py
+ tests.py
+ views.py
+ __init__.py
+ migrations/
```
A ceci s'ajoute :
@@ -174,7 +174,7 @@ Le nouvel enregistrement défini l'objet pour cette nouvelle application avec le
Dès à présent, la base de données doit être décrite. Il est souvent recommandé pour minimiser une transition délicate d'utiliser la même base de données en développement et en production. La documentation concernant les [bases de données](https://docs.djangoproject.com/fr/2.2/ref/settings/#databases) prises en charge sont bien décrites sur le site du projet Django.
-Le système de gestion de base de données (SGBD) SQLite sera utilisé dans le projet de cette série didactique ; nous n'aurons pas d'accès concurents massifs et ce système ne requiert pas  de paramétrages complémentaires. Ci-dessous la définition dans **settings.py** est nécessaire pour utiliser ce SGBD :
+Le système de gestion de base de données (SGBD) SQLite sera utilisé dans le projet de cette série didactique ; nous n'aurons pas d'accès concurents massifs et ce système ne requiert pas de paramétrages complémentaires. Ci-dessous la définition dans **settings.py** est nécessaire pour utiliser ce SGBD :
```python
DATABASES = {
@@ -202,29 +202,29 @@ Il y a deux paramètres à connaître, même s'il ne seront pas modifiés pour l
La création du site web s'appuie sur un routage d'URL et une gestion de la cartographie des URLs dans le fichier **urls.py**) présent dans le dossier du projet. Même si vous pouvez directement utiliser ce fichier pour gérer le routage des URLs, il est recommandé d'utiliser un mécanisme de subsidiarité avec une gestion d'URLs par application. En outre cette méthode de délégation permet une meilleure poratbilité de vos développements dans vos différents projets.
-A l'ouverture du fichier **locallibrary/locallibrary/urls.py**,  vous pouvez remarquer les premières instructions sur la manière de gérer la cartographie des URLs.
+A l'ouverture du fichier **locallibrary/locallibrary/urls.py**, vous pouvez remarquer les premières instructions sur la manière de gérer la cartographie des URLs.
```python
"""locallibrary URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
-    https://docs.djangoproject.com/en/2.1/topics/http/urls/
+ https://docs.djangoproject.com/en/2.1/topics/http/urls/
Examples:
Function views
-    1. Add an import:  from my_app import views
-    2. Add a URL to urlpatterns:  path('', views.home, name='home')
+ 1. Add an import: from my_app import views
+ 2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
-    1. Add an import:  from other_app.views import Home
-    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
+ 1. Add an import: from other_app.views import Home
+ 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
-    1. Import the include() function: from django.urls import include, path
-    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
+ 1. Import the include() function: from django.urls import include, path
+ 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path
urlpatterns = [
-    path('admin/', admin.site.urls),
+ path('admin/', admin.site.urls),
]
```
@@ -232,7 +232,7 @@ Le routage des URLs est géré à l'aide de la variable `urlpatterns`. Elle cons
> **Note :** Dans la fonction `path()`, une route est une chaîne de caractères définissant une URL ou un motif d'URL. Cette chaîne peut inclure des variables nommées (entre < et >, par exemple `'catalog/<id>/'`). Ce motif correspondra à une URL du type **/catalog/*des_caractères*/**. La chaîne *des_caractères* sera transmis à la vue comme une chaîne de caractère associée à une variable nommée `id`. Ce point sera vu en détails plus loin dans la série didactique.
-Ajoutez les lignes ci-dessous à la fin du fichier de manière à ajouter dans la variable `urlpatterns` une nouvelle entrée à la liste des routes. Cette nouvelle entrée permet une nouvelle route pour `catalog/` dont la gestion est déléguée au fichier **urls.py** du module **catalog** (c'est-à-dire le fichier **catalog/urls.py**).
+Ajoutez les lignes ci-dessous à la fin du fichier de manière à ajouter dans la variable `urlpatterns` une nouvelle entrée à la liste des routes. Cette nouvelle entrée permet une nouvelle route pour `catalog/` dont la gestion est déléguée au fichier **urls.py** du module **catalog** (c'est-à-dire le fichier **catalog/urls.py**).
```python
# Use include() to add paths from the catalog application
@@ -240,7 +240,7 @@ from django.urls import include
from django.urls import path
urlpatterns += [
-    path('catalog/', include('catalog.urls')),
+ path('catalog/', include('catalog.urls')),
]
```
@@ -252,7 +252,7 @@ Ajoutez les lignes ci-dessous au bas du fichier **urls.py** :
#Add URL maps to redirect the base URL to our application
from django.views.generic import RedirectView
urlpatterns += [
-    path('', RedirectView.as_view(url='/catalog/', permanent=True)),
+ path('', RedirectView.as_view(url='/catalog/', permanent=True)),
]
```
@@ -281,8 +281,8 @@ Ajoutez les lignes ci-dessous au bas du fichier **urls.py** :
>
> ```python
> urlpatterns = [
->   path('admin/', admin.site.urls),
->   path('catalog/', include('catalog.urls')),
+> path('admin/', admin.site.urls),
+> path('catalog/', include('catalog.urls')),
> path('', RedirectView.as_view(url='/catalog/')),
> ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
> ```
@@ -329,7 +329,7 @@ L'option `migrate` applique les modifications sur la base de données (Django tr
Pendant la phase de développement, vous pouvez tester votre serveur sur un mode local et le consulter avec votre navigateur.
-> **Note :** Le serveur local n'est ni robuste ni performant, il n'est donc pas fait pour être utilisé en production, mais il permet d'être autonome pour les travaux de développement. La configuration par défaut de ce serveur est telle que votre site est accessible à l'URL `http://127.0.0.1:8000/`. Cependant, vous pouvez modifier ces paramètres et pour plus d'information vous pouvez consulter la documentation sur le site Django des commandes [django-admin and manage.py: runserver](https://docs.djangoproject.com/fr/2.2/ref/django-admin/#runserver).
+> **Note :** Le serveur local n'est ni robuste ni performant, il n'est donc pas fait pour être utilisé en production, mais il permet d'être autonome pour les travaux de développement. La configuration par défaut de ce serveur est telle que votre site est accessible à l'URL `http://127.0.0.1:8000/`. Cependant, vous pouvez modifier ces paramètres et pour plus d'information vous pouvez consulter la documentation sur le site Django des commandes [django-admin and manage.py: runserver](https://docs.djangoproject.com/fr/2.2/ref/django-admin/#runserver).
Pour démarrer le serveur local, il suffit d'exécuter la commande ci-dessous dans le répertoire du projet (dossier où se trouver **manage.py**) :
@@ -371,7 +371,7 @@ Maintenant que ceci est fait, [le site web Local Library](/fr/docs/Learn/Server-
## A voir aussi...
-- [Écriture de votre première application Django, 1ère partie](https://docs.djangoproject.com/fr/2.2/intro/tutorial01/)  (Django docs)
+- [Écriture de votre première application Django, 1ère partie](https://docs.djangoproject.com/fr/2.2/intro/tutorial01/) (Django docs)
- [Applications](https://docs.djangoproject.com/fr/2.2/ref/applications/#configuring-applications) (Django Docs). Contains information on configuring applications.
{{PreviousMenuNext("Learn/Server-side/Django/Tutorial_local_library_website", "Learn/Server-side/Django/Models", "Learn/Server-side/Django")}}
diff --git a/files/fr/learn/server-side/django/testing/index.md b/files/fr/learn/server-side/django/testing/index.md
index f1fbaa6eea..e83217446a 100644
--- a/files/fr/learn/server-side/django/testing/index.md
+++ b/files/fr/learn/server-side/django/testing/index.md
@@ -67,23 +67,23 @@ Tester un site web est une tâche complexe, car c'est une opération qui comport
Django fournit un framework de test avec une petite hiérarchie de classes construites sur la librairie standard de Python [`unittest`](https://docs.python.org/3/library/unittest.html#module-unittest "(in Python v3.5)"). Malgré son nom, ce framework de test est utilisable pour les tests unitaires aussi bien que pour les tests d'intégration. Le framework Django ajoute les méthodes et les outils d'une API pour aider à tester un site web et les comportements spécifiques à Django. Ces méthodes vous permettent de simuler des requêtes, d'insérer des données de test et d'inspecter la sortie de votre application. Django fournit aussi une API ([LiveServerTestCase](https://docs.djangoproject.com/en/2.1/topics/testing/tools/#liveservertestcase)) et des outils pour [utiliser d'autres frameworks de test](https://docs.djangoproject.com/en/2.1/topics/testing/advanced/#other-testing-frameworks). Par exemple vous pouvez intégrer le célèbre framework [Selenium](/fr/docs/Learn/Tools_and_testing/Cross_browser_testing/Your_own_automation_environment) pour simuler l'interaction entre un utilisateur et un vrai navigateur.
-Pour écrire un test, vous partez de l'une des classes de test de base fournies par Django (ou par _unittest_) ([SimpleTestCase](https://docs.djangoproject.com/en/2.1/topics/testing/tools/#simpletestcase), [TransactionTestCase](https://docs.djangoproject.com/en/2.1/topics/testing/tools/#transactiontestcase), [TestCase](https://docs.djangoproject.com/en/2.1/topics/testing/tools/#testcase), [LiveServerTestCase](https://docs.djangoproject.com/en/2.1/topics/testing/tools/#liveservertestcase)), et ensuite vous écrivez des méthodes séparées pour vérifier que telle fonctionnalité se comporte comme prévu (les tests utilisent des méthodes "assert" pour vérifier qu'une expression retourne `True` ou `False`, ou que deux valeurs sont égales, etc.). Quant vous commencez à lancer un test, le framework exécute les méthodes de test écrites dans vos classes dérivées. Les méthodes de test sont lancées de manière indépendante, avec en commun un réglage initial (_setUp_) et/ou un comportement de fin (_tearDown_) définis dans la classe, comme indiqué ci-dessous.
+Pour écrire un test, vous partez de l'une des classes de test de base fournies par Django (ou par _unittest_) ([SimpleTestCase](https://docs.djangoproject.com/en/2.1/topics/testing/tools/#simpletestcase), [TransactionTestCase](https://docs.djangoproject.com/en/2.1/topics/testing/tools/#transactiontestcase), [TestCase](https://docs.djangoproject.com/en/2.1/topics/testing/tools/#testcase), [LiveServerTestCase](https://docs.djangoproject.com/en/2.1/topics/testing/tools/#liveservertestcase)), et ensuite vous écrivez des méthodes séparées pour vérifier que telle fonctionnalité se comporte comme prévu (les tests utilisent des méthodes "assert" pour vérifier qu'une expression retourne `True` ou `False`, ou que deux valeurs sont égales, etc.). Quant vous commencez à lancer un test, le framework exécute les méthodes de test écrites dans vos classes dérivées. Les méthodes de test sont lancées de manière indépendante, avec en commun un réglage initial (_setUp_) et/ou un comportement de fin (_tearDown_) définis dans la classe, comme indiqué ci-dessous.
```python
class YourTestClass(TestCase):
-    def setUp(self):
-  # Setup run before every test method.
-        pass
+ def setUp(self):
+ # Setup run before every test method.
+ pass
-    def tearDown(self):
-  # Clean up run after every test method.
-        pass
+ def tearDown(self):
+ # Clean up run after every test method.
+ pass
-    def test_something_that_will_pass(self):
-        self.assertFalse(False)
+ def test_something_that_will_pass(self):
+ self.assertFalse(False)
-    def test_something_that_will_fail(self):
-        self.assertTrue(False)
+ def test_something_that_will_fail(self):
+ self.assertTrue(False)
```
La meilleure classe de base pour la plupart des tests est [django.test.TestCase](https://docs.djangoproject.com/en/2.1/topics/testing/tools/#testcase). Cette classe de test crée une base de données vide avant que ses tests ne soient lancés, et lance toutes les fonctions de test dans sa propre transaction. La classe possède aussi un [Client](https://docs.djangoproject.com/en/2.1/topics/testing/tools/#django.test.Client "django.test.Client") de test, que vous pouvez utiliser pour simuler l'interaction entre un utilisateur et le code au niveau de la vue. Dans les sections suivantes, nous allons nous concentrer sur les tests unitaires, créés en utilisant la classe de base [TestCase](https://docs.djangoproject.com/en/2.1/topics/testing/tools/#testcase).
@@ -94,25 +94,25 @@ La meilleure classe de base pour la plupart des tests est [django.test.TestCase]
Vous pouvez tester tous les aspects de votre code, mais non les librairies ou fonctionnalités faisant partie de Python ou de Django.
-Ainsi par exemple, considérez le modèle `Author` défini ci-dessous. Vous n'avez pas besoin de tester explicitement que `first_name` et `last_name` ont été stockés correctement comme `CharField` dans la base de données, car c'est là quelque chose de défini par Django (cependant, bien sûr, vous allez inévitablement tester cette fonctionnalité pendant le développement). Vous n'avez pas non plus besoin de tester que `date_of_birth` a été validé comme champ date, car, encore une fois, cela est implémenté par Django.
+Ainsi par exemple, considérez le modèle `Author` défini ci-dessous. Vous n'avez pas besoin de tester explicitement que `first_name` et `last_name` ont été stockés correctement comme `CharField` dans la base de données, car c'est là quelque chose de défini par Django (cependant, bien sûr, vous allez inévitablement tester cette fonctionnalité pendant le développement). Vous n'avez pas non plus besoin de tester que `date_of_birth` a été validé comme champ date, car, encore une fois, cela est implémenté par Django.
En revanche, vous pouvez tester que les textes utilisés pour les labels (_First name, Last name, Date of birth, Died_), ainsi que le respect de la taille allouée au champ texte (100 caractères), car c'est là une partie de votre propre design et quelque chose qui pourrait être cassé/modifié dans le futur.
```python
class Author(models.Model):
-    first_name = models.CharField(max_length=100)
-    last_name = models.CharField(max_length=100)
-    date_of_birth = models.DateField(null=True, blank=True)
-    date_of_death = models.DateField('Died', null=True, blank=True)
+ first_name = models.CharField(max_length=100)
+ last_name = models.CharField(max_length=100)
+ date_of_birth = models.DateField(null=True, blank=True)
+ date_of_death = models.DateField('Died', null=True, blank=True)
-    def get_absolute_url(self):
-        return reverse('author-detail', args=[str(self.id)])
+ def get_absolute_url(self):
+ return reverse('author-detail', args=[str(self.id)])
-    def __str__(self):
-        return '%s, %s' % (self.last_name, self.first_name)
+ def __str__(self):
+ return '%s, %s' % (self.last_name, self.first_name)
```
-De même, vous pouvez tester que les méthodes personnalisées `get_absolute_url()` et `__str__()` se comportent comme prévu, car elles appartiennent à votre logique code/métier. Dans le cas de `get_absolute_url()`, vous pouvez supposer que la méthode Django `reverse()` a été implémentée correctement, aussi ce que vous allez tester, c'est que la vue associée a été effectivement définie.
+De même, vous pouvez tester que les méthodes personnalisées `get_absolute_url()` et `__str__()` se comportent comme prévu, car elles appartiennent à votre logique code/métier. Dans le cas de `get_absolute_url()`, vous pouvez supposer que la méthode Django `reverse()` a été implémentée correctement, aussi ce que vous allez tester, c'est que la vue associée a été effectivement définie.
> **Note :** Les lecteurs attentifs auront noté que nous pourrions aussi vouloir limiter les dates de naissance et de décès à des valeurs sensibles, et vérifier que le décès intervient après la naissance. En Django, cette contrainte est ajoutée à vos classes de formulaires (bien que vous puissiez définir des validateurs pour les champs du modèle et des validateurs de modèles, ceux-ci ne sont utilisés qu'au niveau du formulaire s'ils sont appelés par la méthode clean() du modèle. Cela requière un ModelForm ou bien la méthode clean() du modèle a besoin d'être appelée explicitement.)
@@ -122,14 +122,14 @@ Avec cela en tête, commençons à voir comment définir et lancer des tests.
Avant d'entrer dans le détail de "que tester", voyons d'abord brièvement _où_ et _comment_ les tests sont définis.
-Django utilise le [built-in test discovery](https://docs.python.org/3/library/unittest.html#unittest-test-discovery "(in Python v3.5)") du module unittest, qui va chercher des tests, sous le répertoire de travail actuel, dans tous les fichiers dont le nom contient le pattern **test.py**. Du moment que vous nommez vos fichiers de manière appropriée, vous pouvez utiliser n'importe quelle structure. Nous vous recommandons de créer un module pour coder vos tests, et d'avoir des fichiers distincts pour les modèles, les vues, les formulaires et tout autre type de code que vous avez besoin de tester. Par exemple :
+Django utilise le [built-in test discovery](https://docs.python.org/3/library/unittest.html#unittest-test-discovery "(in Python v3.5)") du module unittest, qui va chercher des tests, sous le répertoire de travail actuel, dans tous les fichiers dont le nom contient le pattern **test.py**. Du moment que vous nommez vos fichiers de manière appropriée, vous pouvez utiliser n'importe quelle structure. Nous vous recommandons de créer un module pour coder vos tests, et d'avoir des fichiers distincts pour les modèles, les vues, les formulaires et tout autre type de code que vous avez besoin de tester. Par exemple :
catalog/
-   /tests/
-   __init__.py
-   test_models.py
-   test_forms.py
-   test_views.py
+ /tests/
+ __init__.py
+ test_models.py
+ test_forms.py
+ test_views.py
Créez une structure de fichier comme montré ci-dessus, dans votre projet _LocalLibrary_. Le ficheir **\_\_init\_\_.py** doit être vide (il dit simplement à Python que ce répertoire est un package). Vous pouvez créer les trois fichiers de test en copiant et renommant le fichier de test du squelette **/catalog/tests.py**.
@@ -139,7 +139,7 @@ Créez une structure de fichier comme montré ci-dessus, dans votre projet _Loca
Ouvrez le fichier **/catalog/tests/test_models.py**. Ce fichier doit importer `django.test.TestCase`, comme indiqué ci-après :
-Open **/catalog/tests/test_models.py**. The file should import `django.test.TestCase`, as shown:
+Open **/catalog/tests/test_models.py**. The file should import `django.test.TestCase`, as shown:
```python
from django.test import TestCase
@@ -153,26 +153,26 @@ Ajoutez la classe de test ci-dessous à la fin du fichier. La classe montre comm
```python
class YourTestClass(TestCase):
-    @classmethod
-    def setUpTestData(cls):
-        print("setUpTestData: Run once to set up non-modified data for all class methods.")
-        pass
-
-    def setUp(self):
-        print("setUp: Run once for every test method to setup clean data.")
-        pass
-
-    def test_false_is_false(self):
-        print("Method: test_false_is_false.")
-        self.assertFalse(False)
-
-    def test_false_is_true(self):
-        print("Method: test_false_is_true.")
-        self.assertTrue(False)
-
-    def test_one_plus_one_equals_two(self):
-        print("Method: test_one_plus_one_equals_two.")
-        self.assertEqual(1 + 1, 2)
+ @classmethod
+ def setUpTestData(cls):
+ print("setUpTestData: Run once to set up non-modified data for all class methods.")
+ pass
+
+ def setUp(self):
+ print("setUp: Run once for every test method to setup clean data.")
+ pass
+
+ def test_false_is_false(self):
+ print("Method: test_false_is_false.")
+ self.assertFalse(False)
+
+ def test_false_is_true(self):
+ print("Method: test_false_is_true.")
+ self.assertTrue(False)
+
+ def test_one_plus_one_equals_two(self):
+ print("Method: test_one_plus_one_equals_two.")
+ self.assertEqual(1 + 1, 2)
```
La nouvelle classe définit deux méthodes que vous pouvez utiliser pour une configuration pré-test (par exemple, pour créer des modèles ou d'autres objets dont vous aurez besoin pour les tests) :
@@ -180,11 +180,11 @@ La nouvelle classe définit deux méthodes que vous pouvez utiliser pour une con
- `setUpTestData()` est appelée une fois au début du lancement des tests pour un réglage au niveau de la classe. Vous pouvez l'utiliser pour créer des objets qui ne sont pas destinés à être modifiés ou changés dans les méthodes de test.
- `setUp()` est appelée avant chaque fonction de test pour définir des objets qui peuvent être modifiés par le test (chaque fonction de test obtiendra une version "fraîche" de ces objets).
-> **Note :** Les classes de test ont aussi une méthode `tearDown()`, que nous n'avons pas utilisée. Cette méthode n'est pas particulièrement utile pour les tests avec bases de données, dans la mesure où la classe de base `TestCase` prend soin pour vous de supprimer la base de données utilisées pour les tests.
+> **Note :** Les classes de test ont aussi une méthode `tearDown()`, que nous n'avons pas utilisée. Cette méthode n'est pas particulièrement utile pour les tests avec bases de données, dans la mesure où la classe de base `TestCase` prend soin pour vous de supprimer la base de données utilisées pour les tests.
En dessous de ces méthodes, nous avons un certain nombre de méthodes de test, qui utilisent des fonctions `Assert`, pour tester si certaines conditions sont vraies, fausses ou égales (`AssertTrue`, `AssertFalse`, `AssertEqual`). Si la condition ne renvoie pas le résultat escompté, le test plante et renvoie une erreur à votre console.
-Les méthodes `AssertTrue`, `AssertFalse` et `AssertEqual` sont des assertions standard fournies par **unittest**. Il y a d'autres assertions standard dans le framework, et aussi des [assertions spécifiques à Django](https://docs.djangoproject.com/en/2.1/topics/testing/tools/#assertions), pour tester si une vue redirige (`assertRedirects`), pour tester si tel template a été utilisé (`assertTemplateUsed`), etc.
+Les méthodes `AssertTrue`, `AssertFalse` et `AssertEqual` sont des assertions standard fournies par **unittest**. Il y a d'autres assertions standard dans le framework, et aussi des [assertions spécifiques à Django](https://docs.djangoproject.com/en/2.1/topics/testing/tools/#assertions), pour tester si une vue redirige (`assertRedirects`), pour tester si tel template a été utilisé (`assertTemplateUsed`), etc.
> **Note :** Normallement vous ne devriez **pas** inclure de fonctions **print()** dans vos tests, comme montré ci-dessus. Nous avons fait cela uniquement pour que vous puissiez voir dans la console (dans la section suivante) l'ordre dans lequel les fonctions de setup sont appelées.
@@ -198,7 +198,7 @@ python3 manage.py test
Cette commande va lancer la recherche de tous les fichiers ayant la forme **test.py** sous le répertoire courant, et lancer tous les tests définis, en utilisant les classes de base appropriées (ici nous avons un certain nombre de fichiers de test, mais pour le moment seul **/catalog/tests/test_models.py** contient des tests). Par défaut, chaque test ne fera de rapport qu'en cas d'échec, avec ensuite un résumé du test.
-> **Note :** Si vous obtenez des erreurs telles que : `ValueError: Missing staticfiles manifest entry ...`, cela peut être dû au fait que le test ne lance pas *collectstatic* par défaut, et que votre application utilise une classe de storage qui le requiert (voyez [manifest_strict](https://docs.djangoproject.com/en/2.1/ref/contrib/staticfiles/#django.contrib.staticfiles.storage.ManifestStaticFilesStorage.manifest_strict) pour plus d'information). Il y a plusieurs façons de remédier à ce problème - la plus facile est de lancer tout simplement *collectstatic* avant de lancer les tests :
+> **Note :** Si vous obtenez des erreurs telles que : `ValueError: Missing staticfiles manifest entry ...`, cela peut être dû au fait que le test ne lance pas *collectstatic* par défaut, et que votre application utilise une classe de storage qui le requiert (voyez [manifest_strict](https://docs.djangoproject.com/en/2.1/ref/contrib/staticfiles/#django.contrib.staticfiles.storage.ManifestStaticFilesStorage.manifest_strict) pour plus d'information). Il y a plusieurs façons de remédier à ce problème - la plus facile est de lancer tout simplement *collectstatic* avant de lancer les tests :
>
> ```bash
> python3 manage.py collectstatic
@@ -222,8 +222,8 @@ Method: test_one_plus_one_equals_two.
FAIL: test_false_is_true (catalog.tests.tests_models.YourTestClass)
----------------------------------------------------------------------
Traceback (most recent call last):
-  File "D:\Github\django_tmp\library_w_t_2\locallibrary\catalog\tests\tests_models.py", line 22, in test_false_is_true
-    self.assertTrue(False)
+ File "D:\Github\django_tmp\library_w_t_2\locallibrary\catalog\tests\tests_models.py", line 22, in test_false_is_true
+ self.assertTrue(False)
AssertionError: False is not true
----------------------------------------------------------------------
@@ -233,11 +233,11 @@ FAILED (failures=1)
Destroying test database for alias 'default'...
```
-Ici nous voyons que nous avons eu un échec pour un test, et nous pouvons voir exactement quelle fonction a planté et pourquoi (cet échec était prévu, car `False` n'est pas `True` !).
+Ici nous voyons que nous avons eu un échec pour un test, et nous pouvons voir exactement quelle fonction a planté et pourquoi (cet échec était prévu, car `False` n'est pas `True` !).
> **Note :** La chose la plus importante à apprendre de la sortie de test ci-dessus est qu'il est bien mieux d'utiliser des noms descriptifs/informatifs pour vos objets et méthodes.
-Le texte en **gras** ci-dessus n'apparaîtra pas normalement dans la sortie de test (elle est générée par les fonctions `print()` dans nos tests). Cela montre comment la méthode `setUpTestData()` est appelée une fois pour l'ensemble de classe, tandis que `setUp()` est appelée avant chaque méthode.
+Le texte en **gras** ci-dessus n'apparaîtra pas normalement dans la sortie de test (elle est générée par les fonctions `print()` dans nos tests). Cela montre comment la méthode `setUpTestData()` est appelée une fois pour l'ensemble de classe, tandis que `setUp()` est appelée avant chaque méthode.
La section suivante mnotre comment vous pouvez lancer des test spécifiques, et comment contrôler la quantité d'information fournie par les tests.
@@ -253,7 +253,7 @@ Les niveaux de verbosité sont 0, 1, 2 et 3, avec "1" comme valeur par défaut.
### Lancer des tests spécifiques
-Si vous voulez lancer une sous-sélection parmi vos tests, vous pouvez le faire en spécifiant le chemin complet (avec des points) vers le ou les package(s), module, sous-classe de `TestCase` ou méthode :
+Si vous voulez lancer une sous-sélection parmi vos tests, vous pouvez le faire en spécifiant le chemin complet (avec des points) vers le ou les package(s), module, sous-classe de `TestCase` ou méthode :
```bash
# Run the specified module
@@ -279,25 +279,25 @@ Maintenant que nous savons comment lancer nos tests et quel genre de choses nous
Comme nous l'avons dit ci-dessus, nous devrions tester tout ce qui relève de notre design, ou tout ce qui est défini par du code que nous avons écrit nous-mêmes, mais pas les bibliothèques ou le code qui est déjà testé par Django ou par l'équipe qui développe Python.
-Par exemple, considérez le modèle `Author` ci-dessous. Ici nous devrions tester les labels de tous les champs, car, bien que nous n'ayons pas explicitement spécifié la plupart d'entre eux, nous avons un design qui dit ce que ces valeurs devraient être. Si nous ne testons pas ces valeurs, nous ne savons pas que les labels des champs ont les valeurs souhaitées. De même, alors que nous sommes tranquilles sur le fait que Django créera un champ de la longueur indiquée, il est intéressant de lancer un test spécifique pour s'assurer qu'il a été implémenté comme prévu.
+Par exemple, considérez le modèle `Author` ci-dessous. Ici nous devrions tester les labels de tous les champs, car, bien que nous n'ayons pas explicitement spécifié la plupart d'entre eux, nous avons un design qui dit ce que ces valeurs devraient être. Si nous ne testons pas ces valeurs, nous ne savons pas que les labels des champs ont les valeurs souhaitées. De même, alors que nous sommes tranquilles sur le fait que Django créera un champ de la longueur indiquée, il est intéressant de lancer un test spécifique pour s'assurer qu'il a été implémenté comme prévu.
```python
class Author(models.Model):
-    first_name = models.CharField(max_length=100)
-    last_name = models.CharField(max_length=100)
-    date_of_birth = models.DateField(null=True, blank=True)
-    date_of_death = models.DateField('Died', null=True, blank=True)
+ first_name = models.CharField(max_length=100)
+ last_name = models.CharField(max_length=100)
+ date_of_birth = models.DateField(null=True, blank=True)
+ date_of_death = models.DateField('Died', null=True, blank=True)
-    def get_absolute_url(self):
-        return reverse('author-detail', args=[str(self.id)])
+ def get_absolute_url(self):
+ return reverse('author-detail', args=[str(self.id)])
-    def __str__(self):
-        return f'{self.last_name}, {self.first_name}'
+ def __str__(self):
+ return f'{self.last_name}, {self.first_name}'
```
Ouvrez notre **/catalog/tests/test_models.py**, et remplacez tout le code qui s'y trouve par le code de test ci-après pour le modèle `Author`.
-Vous voyez que nous importons d'abord `TestCase` et faisons dériver d'elle notre classe de test (`AuthorModelTest`) en utilisant un nom descriptif, de façon à pouvoir identifier aisément dans la sortie tout test qui échoue. Nous appelons ensuite `setUpTestData()` afin de créer un objet _author_, que nous utiliserons mais que nous ne modifierons jamais dans aucun de nos tests.
+Vous voyez que nous importons d'abord `TestCase` et faisons dériver d'elle notre classe de test (`AuthorModelTest`) en utilisant un nom descriptif, de façon à pouvoir identifier aisément dans la sortie tout test qui échoue. Nous appelons ensuite `setUpTestData()` afin de créer un objet _author_, que nous utiliserons mais que nous ne modifierons jamais dans aucun de nos tests.
```python
from django.test import TestCase
@@ -351,26 +351,26 @@ self.assertEquals(field_label, 'first name')
Les choses intéressantes à noter sont :
-- Nous ne pouvons obtenir le `verbose_name` directement en utilisant `author.first_name.verbose_name`, car `author.first_name` est une _chaîne_ (non un moyen d'accéder à l'objet `first_name`, que nous pourrions utiliser pour accéder à ses propriétés). À la place nous devons utiliser l'attribut `_meta` de author afin d'obtenir une instance de ses champs et utiliser celle-ci pour demander l'information que nous cherchons.
-- Nous avons fait le choix d'utiliser `assertEquals(field_label,'first name')` plutôt que `assertTrue(field_label == 'first name')`. La raison en est que, en cas d'échec du test, la sortie vous dira, dans le premier cas, quel est effectivement le label du champ, ce qui rend un peu plus facile le débogage du problème.
+- Nous ne pouvons obtenir le `verbose_name` directement en utilisant `author.first_name.verbose_name`, car `author.first_name` est une _chaîne_ (non un moyen d'accéder à l'objet `first_name`, que nous pourrions utiliser pour accéder à ses propriétés). À la place nous devons utiliser l'attribut `_meta` de author afin d'obtenir une instance de ses champs et utiliser celle-ci pour demander l'information que nous cherchons.
+- Nous avons fait le choix d'utiliser `assertEquals(field_label,'first name')` plutôt que `assertTrue(field_label == 'first name')`. La raison en est que, en cas d'échec du test, la sortie vous dira, dans le premier cas, quel est effectivement le label du champ, ce qui rend un peu plus facile le débogage du problème.
-> **Note :** Les tests pour les labels de `last_name` et `date_of_birth`, ainsi que le test de la longueur du champ `last_name`, ont été omis. Ajoutez vos propres versions maintenant, en suivant les conventions de nommage et les approches que nous vous avons montrées ci-dessus.
+> **Note :** Les tests pour les labels de `last_name` et `date_of_birth`, ainsi que le test de la longueur du champ `last_name`, ont été omis. Ajoutez vos propres versions maintenant, en suivant les conventions de nommage et les approches que nous vous avons montrées ci-dessus.
-Il nous faut également tester nos méthodes personnalisées. Essentiellement, celles-ci vérifient uniquement que le nom de l'objet a été construit comme prévu, en utilisant le format "Last name", "First name", et que l'URL que nous obtenons pour un élément `Author` est telle que nous l'attendons.
+Il nous faut également tester nos méthodes personnalisées. Essentiellement, celles-ci vérifient uniquement que le nom de l'objet a été construit comme prévu, en utilisant le format "Last name", "First name", et que l'URL que nous obtenons pour un élément `Author` est telle que nous l'attendons.
```python
def test_object_name_is_last_name_comma_first_name(self):
-    author = Author.objects.get(id=1)
-    expected_object_name = f'{author.last_name}, {author.first_name}'
-    self.assertEquals(expected_object_name, str(author))
+ author = Author.objects.get(id=1)
+ expected_object_name = f'{author.last_name}, {author.first_name}'
+ self.assertEquals(expected_object_name, str(author))
def test_get_absolute_url(self):
-    author = Author.objects.get(id=1)
-    # This will also fail if the urlconf is not defined.
-    self.assertEquals(author.get_absolute_url(), '/catalog/author/1')
+ author = Author.objects.get(id=1)
+ # This will also fail if the urlconf is not defined.
+ self.assertEquals(author.get_absolute_url(), '/catalog/author/1')
```
-Maintenant lancez les tests. Si vous avez créé le modèle `Author` comme décrit dans le tutoriel sur les modèles, il est assez probable que vous allez obtenir une erreur pour le label `date_of_death`, comme montré ci-dessous. Le test plante parce qu'il a été écrit en s'attendant à ce que la définition du label suive cette convention de Django : ne pas mettre en capitale la première lettre du label (Django le fait pour vous).
+Maintenant lancez les tests. Si vous avez créé le modèle `Author` comme décrit dans le tutoriel sur les modèles, il est assez probable que vous allez obtenir une erreur pour le label `date_of_death`, comme montré ci-dessous. Le test plante parce qu'il a été écrit en s'attendant à ce que la définition du label suive cette convention de Django : ne pas mettre en capitale la première lettre du label (Django le fait pour vous).
```bash
======================================================================
@@ -431,38 +431,38 @@ from django.utils import timezone
from catalog.forms import RenewBookForm
class RenewBookFormTest(TestCase):
-    def test_renew_form_date_field_label(self):
-        form = RenewBookForm()
-        self.assertTrue(form.fields['renewal_date'].label == None or form.fields['renewal_date'].label == 'renewal date')
-
-    def test_renew_form_date_field_help_text(self):
-        form = RenewBookForm()
-        self.assertEqual(form.fields['renewal_date'].help_text, 'Enter a date between now and 4 weeks (default 3).')
-
-    def test_renew_form_date_in_past(self):
-        date = datetime.date.today() - datetime.timedelta(days=1)
-        form = RenewBookForm(data={'renewal_date': date})
-        self.assertFalse(form.is_valid())
-
-    def test_renew_form_date_too_far_in_future(self):
-        date = datetime.date.today() + datetime.timedelta(weeks=4) + datetime.timedelta(days=1)
-        form = RenewBookForm(data={'renewal_date': date})
-        self.assertFalse(form.is_valid())
-
-    def test_renew_form_date_today(self):
-        date = datetime.date.today()
-        form = RenewBookForm(data={'renewal_date': date})
-        self.assertTrue(form.is_valid())
-
-    def test_renew_form_date_max(self):
-        date = timezone.localtime() + datetime.timedelta(weeks=4)
-        form = RenewBookForm(data={'renewal_date': date})
-        self.assertTrue(form.is_valid())
+ def test_renew_form_date_field_label(self):
+ form = RenewBookForm()
+ self.assertTrue(form.fields['renewal_date'].label == None or form.fields['renewal_date'].label == 'renewal date')
+
+ def test_renew_form_date_field_help_text(self):
+ form = RenewBookForm()
+ self.assertEqual(form.fields['renewal_date'].help_text, 'Enter a date between now and 4 weeks (default 3).')
+
+ def test_renew_form_date_in_past(self):
+ date = datetime.date.today() - datetime.timedelta(days=1)
+ form = RenewBookForm(data={'renewal_date': date})
+ self.assertFalse(form.is_valid())
+
+ def test_renew_form_date_too_far_in_future(self):
+ date = datetime.date.today() + datetime.timedelta(weeks=4) + datetime.timedelta(days=1)
+ form = RenewBookForm(data={'renewal_date': date})
+ self.assertFalse(form.is_valid())
+
+ def test_renew_form_date_today(self):
+ date = datetime.date.today()
+ form = RenewBookForm(data={'renewal_date': date})
+ self.assertTrue(form.is_valid())
+
+ def test_renew_form_date_max(self):
+ date = timezone.localtime() + datetime.timedelta(weeks=4)
+ form = RenewBookForm(data={'renewal_date': date})
+ self.assertTrue(form.is_valid())
```
-Les deux premières fonctions testent que le `label` et le `help_text` du champ sont tels qu'on les attend. Nous devons accéder au champ en utilisant le dictionnaire du champ (p. ex. `form.fields['renewal_date']`). Notez bien ici que nous devons aussi tester si la valeur du label est `None`, car même si Django rend le label correct, il retournera `None` si la valeur n'est pas définie _explicitement_.
+Les deux premières fonctions testent que le `label` et le `help_text` du champ sont tels qu'on les attend. Nous devons accéder au champ en utilisant le dictionnaire du champ (p. ex. `form.fields['renewal_date']`). Notez bien ici que nous devons aussi tester si la valeur du label est `None`, car même si Django rend le label correct, il retournera `None` si la valeur n'est pas définie _explicitement_.
-Les autres fonctions testent que le formulaire est valide avec des dates de renouvellement situées à l'intérieur des limites acceptables, et invalide avec des valeurs en dehors de ces limites. Notez comment nous construisons des valeurs de dates de test autour de notre date actuelle (`datetime.date.today()`) en utilisant `datetime.timedelta()` (dans ce cas en spécifiant un nombre de jours ou de semaines). Ensuite nous créons juste le formulaire en lui passant nos données, et nous testons s'il est valide.
+Les autres fonctions testent que le formulaire est valide avec des dates de renouvellement situées à l'intérieur des limites acceptables, et invalide avec des valeurs en dehors de ces limites. Notez comment nous construisons des valeurs de dates de test autour de notre date actuelle (`datetime.date.today()`) en utilisant `datetime.timedelta()` (dans ce cas en spécifiant un nombre de jours ou de semaines). Ensuite nous créons juste le formulaire en lui passant nos données, et nous testons s'il est valide.
> **Note :** Ici nous n'utilisons pas réellement la base de données ni le client de test. Envisagez de modifier ces tests pour utiliser [SimpleTestCase](https://docs.djangoproject.com/en/2.1/topics/testing/tools/#django.test.SimpleTestCase).
>
@@ -472,9 +472,9 @@ C'est tout pour les formulaires; nous en avons d'autres, mais ils sont automatiq
### Vues
-Pour valider le comportement de notre vue, nous utilisons le [client](https://docs.djangoproject.com/en/2.1/topics/testing/tools/#django.test.Client) de test de Django. Cette classe se comporte comme un navigateur web fictif que nous pouvons utiliser pour simuler des requêtes `GET` et `POST` à une URL donnée, et observer la réponse. Nous pouvons voir à peu près tout au sujet de la réponse, depuis le HTTP bas-niveau (entêtes et codes de statut résultants) jusqu'au template que nous utilisons pour rendre le HTML et aux données de contexte que nous lui passons. Nous pouvons aussi voir la chaîne des redirections (s'il y en a) et vérifier l'URL et le code de statut à chaque étape. Cela nous permet de vérifier que chaque vue se comporte comme prévu.
+Pour valider le comportement de notre vue, nous utilisons le [client](https://docs.djangoproject.com/en/2.1/topics/testing/tools/#django.test.Client) de test de Django. Cette classe se comporte comme un navigateur web fictif que nous pouvons utiliser pour simuler des requêtes `GET` et `POST` à une URL donnée, et observer la réponse. Nous pouvons voir à peu près tout au sujet de la réponse, depuis le HTTP bas-niveau (entêtes et codes de statut résultants) jusqu'au template que nous utilisons pour rendre le HTML et aux données de contexte que nous lui passons. Nous pouvons aussi voir la chaîne des redirections (s'il y en a) et vérifier l'URL et le code de statut à chaque étape. Cela nous permet de vérifier que chaque vue se comporte comme prévu.
-Commençons avec l'une de nos vues les plus simples, celle qui fournit une liste de tous les auteurs. Elle est affichée à l'URL **/catalog/authors/** (une URL nommée 'authors' dans la configuration des URL).
+Commençons avec l'une de nos vues les plus simples, celle qui fournit une liste de tous les auteurs. Elle est affichée à l'URL **/catalog/authors/** (une URL nommée 'authors' dans la configuration des URL).
```python
class AuthorListView(generic.ListView):
@@ -493,47 +493,47 @@ from django.urls import reverse
from catalog.models import Author
class AuthorListViewTest(TestCase):
-    @classmethod
-    def setUpTestData(cls):
-        # Create 13 authors for pagination tests
-        number_of_authors = 13
+ @classmethod
+ def setUpTestData(cls):
+ # Create 13 authors for pagination tests
+ number_of_authors = 13
-        for author_id in range(number_of_authors):
-            Author.objects.create(
+ for author_id in range(number_of_authors):
+ Author.objects.create(
first_name=f'Christian {author_id}',
last_name=f'Surname {author_id}',
)
-    def test_view_url_exists_at_desired_location(self):
-        response = self.client.get('/catalog/authors/')
-        self.assertEqual(response.status_code, 200)
-
-    def test_view_url_accessible_by_name(self):
-        response = self.client.get(reverse('authors'))
-        self.assertEqual(response.status_code, 200)
-
-    def test_view_uses_correct_template(self):
-        response = self.client.get(reverse('authors'))
-        self.assertEqual(response.status_code, 200)
-        self.assertTemplateUsed(response, 'catalog/author_list.html')
-
-    def test_pagination_is_ten(self):
-        response = self.client.get(reverse('authors'))
-        self.assertEqual(response.status_code, 200)
-        self.assertTrue('is_paginated' in response.context)
-        self.assertTrue(response.context['is_paginated'] == True)
-        self.assertTrue(len(response.context['author_list']) == 10)
-
-    def test_lists_all_authors(self):
-        # Get second page and confirm it has (exactly) remaining 3 items
-        response = self.client.get(reverse('authors')+'?page=2')
-        self.assertEqual(response.status_code, 200)
-        self.assertTrue('is_paginated' in response.context)
-        self.assertTrue(response.context['is_paginated'] == True)
-        self.assertTrue(len(response.context['author_list']) == 3)
+ def test_view_url_exists_at_desired_location(self):
+ response = self.client.get('/catalog/authors/')
+ self.assertEqual(response.status_code, 200)
+
+ def test_view_url_accessible_by_name(self):
+ response = self.client.get(reverse('authors'))
+ self.assertEqual(response.status_code, 200)
+
+ def test_view_uses_correct_template(self):
+ response = self.client.get(reverse('authors'))
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'catalog/author_list.html')
+
+ def test_pagination_is_ten(self):
+ response = self.client.get(reverse('authors'))
+ self.assertEqual(response.status_code, 200)
+ self.assertTrue('is_paginated' in response.context)
+ self.assertTrue(response.context['is_paginated'] == True)
+ self.assertTrue(len(response.context['author_list']) == 10)
+
+ def test_lists_all_authors(self):
+ # Get second page and confirm it has (exactly) remaining 3 items
+ response = self.client.get(reverse('authors')+'?page=2')
+ self.assertEqual(response.status_code, 200)
+ self.assertTrue('is_paginated' in response.context)
+ self.assertTrue(response.context['is_paginated'] == True)
+ self.assertTrue(len(response.context['author_list']) == 3)
```
-Tous les tests utilisent le client (qui appartient à notre classe dérivée de `TestCase`), afin de simuler une requête `GET` et d'obtenir une réponse. La première version vérifie une URL spécifique (note : seulement le chemin spécifique, sans le domaine), tandis que la seconde génère une URL à partir de son nom tel qu'il se trouve dans la configuration des URL.
+Tous les tests utilisent le client (qui appartient à notre classe dérivée de `TestCase`), afin de simuler une requête `GET` et d'obtenir une réponse. La première version vérifie une URL spécifique (note : seulement le chemin spécifique, sans le domaine), tandis que la seconde génère une URL à partir de son nom tel qu'il se trouve dans la configuration des URL.
```python
response = self.client.get('/catalog/authors/')
@@ -542,7 +542,7 @@ response = self.client.get(reverse('authors'))
Une fois que nous avons la réponse, nous lui demandons son code de statut, le template utilisé, si la réponse est paginée ou non, le nombre d'éléments retournés et le nombre total d'éléments.
-> **Note :** Si, dans votre fichier **/catalog/views.py**, vous mettez la variable `paginate_by` à un nombre autre que 10, assurez-vous de mettre à jour les lignes qui testent le nombre correct d'éléments affichés dans les templates paginés, ci-dessus et dans les sections qui suivent. Par exemple, si vous mettez à 5 la variable pour la liste des auteurs, changez ainsi la ligne ci-dessus :
+> **Note :** Si, dans votre fichier **/catalog/views.py**, vous mettez la variable `paginate_by` à un nombre autre que 10, assurez-vous de mettre à jour les lignes qui testent le nombre correct d'éléments affichés dans les templates paginés, ci-dessus et dans les sections qui suivent. Par exemple, si vous mettez à 5 la variable pour la liste des auteurs, changez ainsi la ligne ci-dessus :
>
> ```python
> self.assertTrue(len(response.context['author_list']) == 5)
@@ -552,7 +552,7 @@ La variable la plus intéressante que nous montrons ci-dessus est `response.cont
#### Vues limitées aux utilisateurs connectés
-Dans certains cas, vous voudrez tester une vue qui est limitée aux seuls utilisateurs connectés. Par exemple notre vue `LoanedBooksByUserListView` est très semblable à notre vue précédente, mais n'est accessible qu'aux utilisateurs connectés, et n'affiche que des enregistrements de type `BookInstance` qui sont empruntés par l'utilisateur courant, ont le statut 'on loan', et sont triés 'le plus ancien en premier'.
+Dans certains cas, vous voudrez tester une vue qui est limitée aux seuls utilisateurs connectés. Par exemple notre vue `LoanedBooksByUserListView` est très semblable à notre vue précédente, mais n'est accessible qu'aux utilisateurs connectés, et n'affiche que des enregistrements de type `BookInstance` qui sont empruntés par l'utilisateur courant, ont le statut 'on loan', et sont triés 'le plus ancien en premier'.
```python
from django.contrib.auth.mixins import LoginRequiredMixin
@@ -567,9 +567,9 @@ class LoanedBooksByUserListView(LoginRequiredMixin, generic.ListView):
return BookInstance.objects.filter(borrower=self.request.user).filter(status__exact='o').order_by('due_back')
```
-Ajoutez le code de test suivant à **/catalog/tests/test_views.py**. Ici nous utilisons d'abord la méthode `SetUp()` pour créer des logins de comptes d'utilisateurs et des objets de type `BookInstance` (avec leurs livres et autres enregistrements associés), que nous utiliserons plus tard dans les tests. La moitié des livres sont empruntés par chaque utilisateur-test, mais nous avons initialement mis le statut de tous les livres à "maintenance". Nous avons utilisé `SetUp()` plutôt que `setUpTestData()`, parce que nous allons modifier plus tard certains de ces objets.
+Ajoutez le code de test suivant à **/catalog/tests/test_views.py**. Ici nous utilisons d'abord la méthode `SetUp()` pour créer des logins de comptes d'utilisateurs et des objets de type `BookInstance` (avec leurs livres et autres enregistrements associés), que nous utiliserons plus tard dans les tests. La moitié des livres sont empruntés par chaque utilisateur-test, mais nous avons initialement mis le statut de tous les livres à "maintenance". Nous avons utilisé `SetUp()` plutôt que `setUpTestData()`, parce que nous allons modifier plus tard certains de ces objets.
-> **Note :** Le code de `setUp()` ci-dessous crée un livre avec un `Language` spécifique, mais _votre_ code n'inclut peut-être pas le modèle `Language`, étant donné que celui-ci a été créé lors d'un _défi_. Si c'est le cas, commentez simplement les bouts de code qui créent ou importent des objets de type Language. Vous devrez aussi le faire dans la section `RenewBookInstancesViewTest` qui suit.
+> **Note :** Le code de `setUp()` ci-dessous crée un livre avec un `Language` spécifique, mais _votre_ code n'inclut peut-être pas le modèle `Language`, étant donné que celui-ci a été créé lors d'un _défi_. Si c'est le cas, commentez simplement les bouts de code qui créent ou importent des objets de type Language. Vous devrez aussi le faire dans la section `RenewBookInstancesViewTest` qui suit.
```python
import datetime
@@ -580,19 +580,19 @@ from django.contrib.auth.models import User # Required to assign User as a borro
from catalog.models import BookInstance, Book, Genre, Language
class LoanedBookInstancesByUserListViewTest(TestCase):
-    def setUp(self):
-        # Create two users
-        test_user1 = User.objects.create_user(username='testuser1', password='1X<ISRUkw+tuK')
-        test_user2 = User.objects.create_user(username='testuser2', password='2HJ1vRV0Z&3iD')
+ def setUp(self):
+ # Create two users
+ test_user1 = User.objects.create_user(username='testuser1', password='1X<ISRUkw+tuK')
+ test_user2 = User.objects.create_user(username='testuser2', password='2HJ1vRV0Z&3iD')
test_user1.save()
-        test_user2.save()
+ test_user2.save()
-        # Create a book
-        test_author = Author.objects.create(first_name='John', last_name='Smith')
-        test_genre = Genre.objects.create(name='Fantasy')
-        test_language = Language.objects.create(name='English')
-        test_book = Book.objects.create(
+ # Create a book
+ test_author = Author.objects.create(first_name='John', last_name='Smith')
+ test_genre = Genre.objects.create(name='Fantasy')
+ test_language = Language.objects.create(name='English')
+ test_book = Book.objects.create(
title='Book Title',
summary='My book summary',
isbn='ABCDEFG',
@@ -601,17 +601,17 @@ class LoanedBookInstancesByUserListViewTest(TestCase):
)
# Create genre as a post-step
-        genre_objects_for_book = Genre.objects.all()
-  test_book.genre.set(genre_objects_for_book) # Direct assignment of many-to-many types not allowed.
-        test_book.save()
-
-        # Create 30 BookInstance objects
-        number_of_book_copies = 30
-        for book_copy in range(number_of_book_copies):
-            return_date = timezone.localtime() + datetime.timedelta(days=book_copy%5)
-            the_borrower = test_user1 if book_copy % 2 else test_user2
-            status = 'm'
-            BookInstance.objects.create(
+ genre_objects_for_book = Genre.objects.all()
+ test_book.genre.set(genre_objects_for_book) # Direct assignment of many-to-many types not allowed.
+ test_book.save()
+
+ # Create 30 BookInstance objects
+ number_of_book_copies = 30
+ for book_copy in range(number_of_book_copies):
+ return_date = timezone.localtime() + datetime.timedelta(days=book_copy%5)
+ the_borrower = test_user1 if book_copy % 2 else test_user2
+ status = 'm'
+ BookInstance.objects.create(
book=test_book,
imprint='Unlikely Imprint, 2016',
due_back=return_date,
@@ -619,86 +619,86 @@ class LoanedBookInstancesByUserListViewTest(TestCase):
status=status,
)
-    def test_redirect_if_not_logged_in(self):
-        response = self.client.get(reverse('my-borrowed'))
-        self.assertRedirects(response, '/accounts/login/?next=/catalog/mybooks/')
+ def test_redirect_if_not_logged_in(self):
+ response = self.client.get(reverse('my-borrowed'))
+ self.assertRedirects(response, '/accounts/login/?next=/catalog/mybooks/')
-    def test_logged_in_uses_correct_template(self):
-        login = self.client.login(username='testuser1', password='1X<ISRUkw+tuK')
-        response = self.client.get(reverse('my-borrowed'))
+ def test_logged_in_uses_correct_template(self):
+ login = self.client.login(username='testuser1', password='1X<ISRUkw+tuK')
+ response = self.client.get(reverse('my-borrowed'))
-        # Check our user is logged in
-        self.assertEqual(str(response.context['user']), 'testuser1')
-        # Check that we got a response "success"
-        self.assertEqual(response.status_code, 200)
+ # Check our user is logged in
+ self.assertEqual(str(response.context['user']), 'testuser1')
+ # Check that we got a response "success"
+ self.assertEqual(response.status_code, 200)
-        # Check we used correct template
-        self.assertTemplateUsed(response, 'catalog/bookinstance_list_borrowed_user.html')
+ # Check we used correct template
+ self.assertTemplateUsed(response, 'catalog/bookinstance_list_borrowed_user.html')
```
-Pour vérifier que la vue redirige à une page de login si l'utilisateur n'est pas connecté, nous utilisons `assertRedirects`, comme montré dans `test_redirect_if_not_logged_in()`. Pour vérifier que la page est affichée pour un utilisateur connecté, nous connectons d'abord notre utilisateur-test, et ensuite nous accédons de nouveau à la page et vérifions que nous obtenons un `status_code` de 200 (succès).
+Pour vérifier que la vue redirige à une page de login si l'utilisateur n'est pas connecté, nous utilisons `assertRedirects`, comme montré dans `test_redirect_if_not_logged_in()`. Pour vérifier que la page est affichée pour un utilisateur connecté, nous connectons d'abord notre utilisateur-test, et ensuite nous accédons de nouveau à la page et vérifions que nous obtenons un `status_code` de 200 (succès).
Le reste des test vérifie que notre vue ne retourne que les livres qui sont prêtés à notre emprunteur courant. Copiez ce code et collez le à la fin de la classe de test ci-dessus.
```python
-    def test_only_borrowed_books_in_list(self):
-        login = self.client.login(username='testuser1', password='1X<ISRUkw+tuK')
-        response = self.client.get(reverse('my-borrowed'))
-
-        # Check our user is logged in
-        self.assertEqual(str(response.context['user']), 'testuser1')
-        # Check that we got a response "success"
-        self.assertEqual(response.status_code, 200)
-
-        # Check that initially we don't have any books in list (none on loan)
-        self.assertTrue('bookinstance_list' in response.context)
-        self.assertEqual(len(response.context['bookinstance_list']), 0)
-
-        # Now change all books to be on loan
-        books = BookInstance.objects.all()[:10]
-
-        for book in books:
-            book.status = 'o'
-            book.save()
-
-        # Check that now we have borrowed books in the list
-        response = self.client.get(reverse('my-borrowed'))
-        # Check our user is logged in
-        self.assertEqual(str(response.context['user']), 'testuser1')
-        # Check that we got a response "success"
-        self.assertEqual(response.status_code, 200)
-
-        self.assertTrue('bookinstance_list' in response.context)
-
-        # Confirm all books belong to testuser1 and are on loan
-        for bookitem in response.context['bookinstance_list']:
-            self.assertEqual(response.context['user'], bookitem.borrower)
-            self.assertEqual('o', bookitem.status)
-
-    def test_pages_ordered_by_due_date(self):
-        # Change all books to be on loan
-        for book in BookInstance.objects.all():
-            book.status='o'
-            book.save()
-
-        login = self.client.login(username='testuser1', password='1X<ISRUkw+tuK')
-        response = self.client.get(reverse('my-borrowed'))
-
-        # Check our user is logged in
-        self.assertEqual(str(response.context['user']), 'testuser1')
-        # Check that we got a response "success"
-        self.assertEqual(response.status_code, 200)
-
-        # Confirm that of the items, only 10 are displayed due to pagination.
-        self.assertEqual(len(response.context['bookinstance_list']), 10)
-
-        last_date = 0
-        for book in response.context['bookinstance_list']:
-            if last_date == 0:
-                last_date = book.due_back
-            else:
+ def test_only_borrowed_books_in_list(self):
+ login = self.client.login(username='testuser1', password='1X<ISRUkw+tuK')
+ response = self.client.get(reverse('my-borrowed'))
+
+ # Check our user is logged in
+ self.assertEqual(str(response.context['user']), 'testuser1')
+ # Check that we got a response "success"
+ self.assertEqual(response.status_code, 200)
+
+ # Check that initially we don't have any books in list (none on loan)
+ self.assertTrue('bookinstance_list' in response.context)
+ self.assertEqual(len(response.context['bookinstance_list']), 0)
+
+ # Now change all books to be on loan
+ books = BookInstance.objects.all()[:10]
+
+ for book in books:
+ book.status = 'o'
+ book.save()
+
+ # Check that now we have borrowed books in the list
+ response = self.client.get(reverse('my-borrowed'))
+ # Check our user is logged in
+ self.assertEqual(str(response.context['user']), 'testuser1')
+ # Check that we got a response "success"
+ self.assertEqual(response.status_code, 200)
+
+ self.assertTrue('bookinstance_list' in response.context)
+
+ # Confirm all books belong to testuser1 and are on loan
+ for bookitem in response.context['bookinstance_list']:
+ self.assertEqual(response.context['user'], bookitem.borrower)
+ self.assertEqual('o', bookitem.status)
+
+ def test_pages_ordered_by_due_date(self):
+ # Change all books to be on loan
+ for book in BookInstance.objects.all():
+ book.status='o'
+ book.save()
+
+ login = self.client.login(username='testuser1', password='1X<ISRUkw+tuK')
+ response = self.client.get(reverse('my-borrowed'))
+
+ # Check our user is logged in
+ self.assertEqual(str(response.context['user']), 'testuser1')
+ # Check that we got a response "success"
+ self.assertEqual(response.status_code, 200)
+
+ # Confirm that of the items, only 10 are displayed due to pagination.
+ self.assertEqual(len(response.context['bookinstance_list']), 10)
+
+ last_date = 0
+ for book in response.context['bookinstance_list']:
+ if last_date == 0:
+ last_date = book.due_back
+ else:
self.assertTrue(last_date <= book.due_back)
-                last_date = book.due_back
+ last_date = book.due_back
```
Vous pourriez aussi ajouter les tests de pagination, si vous voulez !
@@ -714,38 +714,38 @@ from catalog.forms import RenewBookForm
@permission_required('catalog.can_mark_returned')
def renew_book_librarian(request, pk):
-    """View function for renewing a specific BookInstance by librarian."""
-    book_instance = get_object_or_404(BookInstance, pk=pk)
+ """View function for renewing a specific BookInstance by librarian."""
+ book_instance = get_object_or_404(BookInstance, pk=pk)
-    # If this is a POST request then process the Form data
-    if request.method == 'POST':
+ # If this is a POST request then process the Form data
+ if request.method == 'POST':
-        # Create a form instance and populate it with data from the request (binding):
-        book_renewal_form = RenewBookForm(request.POST)
+ # Create a form instance and populate it with data from the request (binding):
+ book_renewal_form = RenewBookForm(request.POST)
-        # Check if the form is valid:
-        if form.is_valid():
-            # process the data in form.cleaned_data as required (here we just write it to the model due_back field)
-            book_instance.due_back = form.cleaned_data['renewal_date']
-            book_instance.save()
+ # Check if the form is valid:
+ if form.is_valid():
+ # process the data in form.cleaned_data as required (here we just write it to the model due_back field)
+ book_instance.due_back = form.cleaned_data['renewal_date']
+ book_instance.save()
-            # redirect to a new URL:
-            return HttpResponseRedirect(reverse('all-borrowed'))
+ # redirect to a new URL:
+ return HttpResponseRedirect(reverse('all-borrowed'))
-    # If this is a GET (or any other method) create the default form
-    else:
-        proposed_renewal_date = datetime.date.today() + datetime.timedelta(weeks=3)
-        book_renewal_form = RenewBookForm(initial={'renewal_date': proposed_renewal_date})
+ # If this is a GET (or any other method) create the default form
+ else:
+ proposed_renewal_date = datetime.date.today() + datetime.timedelta(weeks=3)
+ book_renewal_form = RenewBookForm(initial={'renewal_date': proposed_renewal_date})
context = {
'book_renewal_form': book_renewal_form,
'book_instance': book_instance,
}
-    return render(request, 'catalog/book_renew_librarian.html', context)
+ return render(request, 'catalog/book_renew_librarian.html', context)
```
-Nous allons devoir tester que la vue n'est disponible qu'aux utilisateurs ayant la permission `can_mark_returned`, et que les utilisateurs sont bien redirigés vers une page d'erreur HTTP 404 s'ils essaient de renouveler une `BookInstance` inexistante. Nous devons vérifier que la valeur initiale du formulaire est remplie avec une date de trois semaines dans le futur, et que si la validation réussit, nous sommes redirigés vers la vue "tous les livres empruntés". Dans le cadre des tests sur l'échec de la validation, nous allons aussi vérifier que notre formulaire envoie les bons messages d'erreur.
+Nous allons devoir tester que la vue n'est disponible qu'aux utilisateurs ayant la permission `can_mark_returned`, et que les utilisateurs sont bien redirigés vers une page d'erreur HTTP 404 s'ils essaient de renouveler une `BookInstance` inexistante. Nous devons vérifier que la valeur initiale du formulaire est remplie avec une date de trois semaines dans le futur, et que si la validation réussit, nous sommes redirigés vers la vue "tous les livres empruntés". Dans le cadre des tests sur l'échec de la validation, nous allons aussi vérifier que notre formulaire envoie les bons messages d'erreur.
Ajoutez la première partie de la classe de test ci-dessous à la fin du fichier **/catalog/tests/test_views.py**. Cela crée deux utilisateurs et deux instances de livres, mais ne donne qu'à un seul utilisateur la permission d'accéder à la vue. Le code pour autoriser les permissions durant les tests est montrée en gras :
@@ -805,75 +805,75 @@ class RenewBookInstancesViewTest(TestCase):
)
```
-Ajoutez les tests suivants à la fin de la classe de test. Ils vérifient que seuls les utilisateurs avec les bonnes permissions (_testuser2_) peuvent accéder à la vue. Nous vérifions tous les cas : quand l'utilisateur n'est pas connecté, quand un utilisateur est connecté mais n'a pas les permissions requises, quand l'utilisateur a les permissions mais n'est pas l'emprunteur (ce test devrait réussir), et ce qui se passe quand ils tentent d'accéder à une `BookInstance` inexistante. Nous vérifions aussi que le bon template est utilisé.
+Ajoutez les tests suivants à la fin de la classe de test. Ils vérifient que seuls les utilisateurs avec les bonnes permissions (_testuser2_) peuvent accéder à la vue. Nous vérifions tous les cas : quand l'utilisateur n'est pas connecté, quand un utilisateur est connecté mais n'a pas les permissions requises, quand l'utilisateur a les permissions mais n'est pas l'emprunteur (ce test devrait réussir), et ce qui se passe quand ils tentent d'accéder à une `BookInstance` inexistante. Nous vérifions aussi que le bon template est utilisé.
```python
def test_redirect_if_not_logged_in(self):
-        response = self.client.get(reverse('renew-book-librarian', kwargs={'pk': self.test_bookinstance1.pk}))
-        # Manually check redirect (Can't use assertRedirect, because the redirect URL is unpredictable)
-        self.assertEqual(response.status_code, 302)
-        self.assertTrue(response.url.startswith('/accounts/login/'))
+ response = self.client.get(reverse('renew-book-librarian', kwargs={'pk': self.test_bookinstance1.pk}))
+ # Manually check redirect (Can't use assertRedirect, because the redirect URL is unpredictable)
+ self.assertEqual(response.status_code, 302)
+ self.assertTrue(response.url.startswith('/accounts/login/'))
-    def test_redirect_if_logged_in_but_not_correct_permission(self):
-        login = self.client.login(username='testuser1', password='1X<ISRUkw+tuK')
-        response = self.client.get(reverse('renew-book-librarian', kwargs={'pk': self.test_bookinstance1.pk}))
-        self.assertEqual(response.status_code, 403)
+ def test_redirect_if_logged_in_but_not_correct_permission(self):
+ login = self.client.login(username='testuser1', password='1X<ISRUkw+tuK')
+ response = self.client.get(reverse('renew-book-librarian', kwargs={'pk': self.test_bookinstance1.pk}))
+ self.assertEqual(response.status_code, 403)
-    def test_logged_in_with_permission_borrowed_book(self):
-        login = self.client.login(username='testuser2', password='2HJ1vRV0Z&3iD')
-        response = self.client.get(reverse('renew-book-librarian', kwargs={'pk': self.test_bookinstance2.pk}))
+ def test_logged_in_with_permission_borrowed_book(self):
+ login = self.client.login(username='testuser2', password='2HJ1vRV0Z&3iD')
+ response = self.client.get(reverse('renew-book-librarian', kwargs={'pk': self.test_bookinstance2.pk}))
-        # Check that it lets us login - this is our book and we have the right permissions.
-        self.assertEqual(response.status_code, 200)
+ # Check that it lets us login - this is our book and we have the right permissions.
+ self.assertEqual(response.status_code, 200)
-    def test_logged_in_with_permission_another_users_borrowed_book(self):
-        login = self.client.login(username='testuser2', password='2HJ1vRV0Z&3iD')
-        response = self.client.get(reverse('renew-book-librarian', kwargs={'pk': self.test_bookinstance1.pk}))
+ def test_logged_in_with_permission_another_users_borrowed_book(self):
+ login = self.client.login(username='testuser2', password='2HJ1vRV0Z&3iD')
+ response = self.client.get(reverse('renew-book-librarian', kwargs={'pk': self.test_bookinstance1.pk}))
-        # Check that it lets us login. We're a librarian, so we can view any users book
-        self.assertEqual(response.status_code, 200)
+ # Check that it lets us login. We're a librarian, so we can view any users book
+ self.assertEqual(response.status_code, 200)
-    def test_HTTP404_for_invalid_book_if_logged_in(self):
+ def test_HTTP404_for_invalid_book_if_logged_in(self):
# unlikely UID to match our bookinstance!
-        test_uid = uuid.uuid4()
-        login = self.client.login(username='testuser2', password='2HJ1vRV0Z&3iD')
-        response = self.client.get(reverse('renew-book-librarian', kwargs={'pk':test_uid}))
-        self.assertEqual(response.status_code, 404)
-
-    def test_uses_correct_template(self):
-        login = self.client.login(username='testuser2', password='2HJ1vRV0Z&3iD')
-        response = self.client.get(reverse('renew-book-librarian', kwargs={'pk': self.test_bookinstance1.pk}))
-        self.assertEqual(response.status_code, 200)
-
-        # Check we used correct template
-        self.assertTemplateUsed(response, 'catalog/book_renew_librarian.html')
+ test_uid = uuid.uuid4()
+ login = self.client.login(username='testuser2', password='2HJ1vRV0Z&3iD')
+ response = self.client.get(reverse('renew-book-librarian', kwargs={'pk':test_uid}))
+ self.assertEqual(response.status_code, 404)
+
+ def test_uses_correct_template(self):
+ login = self.client.login(username='testuser2', password='2HJ1vRV0Z&3iD')
+ response = self.client.get(reverse('renew-book-librarian', kwargs={'pk': self.test_bookinstance1.pk}))
+ self.assertEqual(response.status_code, 200)
+
+ # Check we used correct template
+ self.assertTemplateUsed(response, 'catalog/book_renew_librarian.html')
```
Ajoutez la méthode de test suivante, comme montré ci-dessous. Elle vérifie que la date initiale pour le formulaire est trois semaines dans le futur. Notez comment nous pouvons accéder à la valeur initiale de ce champ de formulaire (en gras).
```python
-    def test_form_renewal_date_initially_has_date_three_weeks_in_future(self):
-        login = self.client.login(username='testuser2', password='2HJ1vRV0Z&3iD')
-        response = self.client.get(reverse('renew-book-librarian', kwargs={'pk': self.test_bookinstance1.pk}))
-        self.assertEqual(response.status_code, 200)
+ def test_form_renewal_date_initially_has_date_three_weeks_in_future(self):
+ login = self.client.login(username='testuser2', password='2HJ1vRV0Z&3iD')
+ response = self.client.get(reverse('renew-book-librarian', kwargs={'pk': self.test_bookinstance1.pk}))
+ self.assertEqual(response.status_code, 200)
-        date_3_weeks_in_future = datetime.date.today() + datetime.timedelta(weeks=3)
-        self.assertEqual(response.context['form'].initial['renewal_date'], date_3_weeks_in_future)
+ date_3_weeks_in_future = datetime.date.today() + datetime.timedelta(weeks=3)
+ self.assertEqual(response.context['form'].initial['renewal_date'], date_3_weeks_in_future)
```
-> **Attention :** Si vous utilisez la class de formulaire `RenewBookModelForm(forms.ModelForm)` à la place de la classe `RenewBookForm(forms.Form)`, le nom du champ est **'due_back'** et non **'renewal_date'**.
+> **Attention :** Si vous utilisez la class de formulaire `RenewBookModelForm(forms.ModelForm)` à la place de la classe `RenewBookForm(forms.Form)`, le nom du champ est **'due_back'** et non **'renewal_date'**.
Le test suivant (ajoutez-le à la classe également) vérifie que la vue redirige vers une liste de tous les livres empruntés si le renouvellement réussit. Ce qui diffère ici est que, pour la première fois, nous montrons comment vous pouvez `POST`er des données en utilisant le client. Les données postées forment le second argument de la fonction post, et elles sont spécifiées comme un dictionnaire de clés/valeurs.
```python
-    def test_redirects_to_all_borrowed_book_list_on_success(self):
-        login = self.client.login(username='testuser2', password='2HJ1vRV0Z&3iD')
-        valid_date_in_future = datetime.date.today() + datetime.timedelta(weeks=2)
-        response = self.client.post(reverse('renew-book-librarian', kwargs={'pk':self.test_bookinstance1.pk,}), {'renewal_date':valid_date_in_future})
-        self.assertRedirects(response, reverse('all-borrowed'))
+ def test_redirects_to_all_borrowed_book_list_on_success(self):
+ login = self.client.login(username='testuser2', password='2HJ1vRV0Z&3iD')
+ valid_date_in_future = datetime.date.today() + datetime.timedelta(weeks=2)
+ response = self.client.post(reverse('renew-book-librarian', kwargs={'pk':self.test_bookinstance1.pk,}), {'renewal_date':valid_date_in_future})
+ self.assertRedirects(response, reverse('all-borrowed'))
```
-> **Attention :** La vue _all-borrowed_ a été ajoutée comme _défi_, et votre code peut, à la place, rediriger vers la page d'accueil '/'. Si c'est le cas, modifiez les deux dernières lignes du code de test pour qu'elles ressemblent au code ci-dessous. L'expression `follow=True` dans la requête s'assure que la requête retourne l'URL de la destination finale (donc vérifie `/catalog/` plutôt que `/`).
+> **Attention :** La vue _all-borrowed_ a été ajoutée comme _défi_, et votre code peut, à la place, rediriger vers la page d'accueil '/'. Si c'est le cas, modifiez les deux dernières lignes du code de test pour qu'elles ressemblent au code ci-dessous. L'expression `follow=True` dans la requête s'assure que la requête retourne l'URL de la destination finale (donc vérifie `/catalog/` plutôt que `/`).
>
> ```python
> response = self.client.post(reverse('renew-book-librarian', kwargs={'pk':self.test_bookinstance1.pk,}), {'renewal_date':valid_date_in_future}, follow=True )
@@ -883,19 +883,19 @@ Le test suivant (ajoutez-le à la classe également) vérifie que la vue redirig
Copiez les deux dernières fonctions dans la classe, comme indiqué ci-dessous. Elles testent de nouveau des requêtes POST, mais dans ce cas avec des dates de renouvellement invalides. Nous utilisons la méthode assertFormError() pour vérifier que les messages d'erreur sont ceux que nous attendons.
```python
-    def test_form_invalid_renewal_date_past(self):
-        login = self.client.login(username='testuser2', password='2HJ1vRV0Z&3iD')
-        date_in_past = datetime.date.today() - datetime.timedelta(weeks=1)
-        response = self.client.post(reverse('renew-book-librarian', kwargs={'pk': self.test_bookinstance1.pk}), {'renewal_date': date_in_past})
-        self.assertEqual(response.status_code, 200)
-        self.assertFormError(response, 'form', 'renewal_date', 'Invalid date - renewal in past')
-
-    def test_form_invalid_renewal_date_future(self):
-        login = self.client.login(username='testuser2', password='2HJ1vRV0Z&3iD')
-        invalid_date_in_future = datetime.date.today() + datetime.timedelta(weeks=5)
-        response = self.client.post(reverse('renew-book-librarian', kwargs={'pk': self.test_bookinstance1.pk}), {'renewal_date': invalid_date_in_future})
-        self.assertEqual(response.status_code, 200)
-        self.assertFormError(response, 'form', 'renewal_date', 'Invalid date - renewal more than 4 weeks ahead')
+ def test_form_invalid_renewal_date_past(self):
+ login = self.client.login(username='testuser2', password='2HJ1vRV0Z&3iD')
+ date_in_past = datetime.date.today() - datetime.timedelta(weeks=1)
+ response = self.client.post(reverse('renew-book-librarian', kwargs={'pk': self.test_bookinstance1.pk}), {'renewal_date': date_in_past})
+ self.assertEqual(response.status_code, 200)
+ self.assertFormError(response, 'form', 'renewal_date', 'Invalid date - renewal in past')
+
+ def test_form_invalid_renewal_date_future(self):
+ login = self.client.login(username='testuser2', password='2HJ1vRV0Z&3iD')
+ invalid_date_in_future = datetime.date.today() + datetime.timedelta(weeks=5)
+ response = self.client.post(reverse('renew-book-librarian', kwargs={'pk': self.test_bookinstance1.pk}), {'renewal_date': invalid_date_in_future})
+ self.assertEqual(response.status_code, 200)
+ self.assertFormError(response, 'form', 'renewal_date', 'Invalid date - renewal more than 4 weeks ahead')
```
Le même genre de technique peut être utilisé pour tester les autres vues.
@@ -938,7 +938,7 @@ Le prochain (et dernier) tutoriel montre comment vous pouvez déployer votre mer
## À voir également
- [Writing and running tests](https://docs.djangoproject.com/en/2.1/topics/testing/overview/) (Django docs)
-- [Writing your first Django app, part 5 > Introducing automated testing](https://docs.djangoproject.com/en/2.1/intro/tutorial05/) (Django docs)
+- [Writing your first Django app, part 5 > Introducing automated testing](https://docs.djangoproject.com/en/2.1/intro/tutorial05/) (Django docs)
- [Testing tools reference](https://docs.djangoproject.com/en/2.1/topics/testing/tools/) (Django docs)
- [Advanced testing topics](https://docs.djangoproject.com/en/2.1/topics/testing/advanced/) (Django docs)
- [A Guide to Testing in Django](http://toastdriven.com/blog/2011/apr/10/guide-to-testing-in-django/) (Toast Driven Blog, 2011)
diff --git a/files/fr/learn/server-side/django/tutorial_local_library_website/index.md b/files/fr/learn/server-side/django/tutorial_local_library_website/index.md
index 9a02d03bdd..924f54361f 100644
--- a/files/fr/learn/server-side/django/tutorial_local_library_website/index.md
+++ b/files/fr/learn/server-side/django/tutorial_local_library_website/index.md
@@ -1,5 +1,5 @@
---
-title: 'Django Didactique: Site web "Bibliothèque locale"'
+title: 'Django Didactique: Site web "Bibliothèque locale"'
slug: Learn/Server-side/Django/Tutorial_local_library_website
tags:
- Apprentissage
@@ -23,7 +23,7 @@ Le premier article de cette série didactique explique ce que vous apprendrez et
<a href="/fr/docs/Learn/Server-side/Django/Introduction"
>l'introduction</a
>. Pour les articles suivants avoir mis à jour l'environnement comme
- décrit précédemment. 
+ décrit précédemment.
</td>
</tr>
<tr>