diff options
Diffstat (limited to 'files/fr/learn/server-side')
14 files changed, 660 insertions, 660 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  -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 :  @@ -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<pk>\d+)</strong> + Elle capture aussi tous les digits <strong>(?P<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<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  -- **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** : 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. ## 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.  @@ -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> diff --git a/files/fr/learn/server-side/express_nodejs/introduction/index.md b/files/fr/learn/server-side/express_nodejs/introduction/index.md index 378a85ce83..32a95acc67 100644 --- a/files/fr/learn/server-side/express_nodejs/introduction/index.md +++ b/files/fr/learn/server-side/express_nodejs/introduction/index.md @@ -13,12 +13,12 @@ translation_of: Learn/Server-side/Express_Nodejs/Introduction --- {{LearnSidebar}}{{NextMenu("Learn/Server-side/Express_Nodejs/development_environment", "Learn/Server-side/Express_Nodejs")}} -Dans ce tout premier article consacré à Express, nous répondons aux questions « Qu'est-ce que Node ? » et « Qu'est-ce que Express ? », et vous donnons un aperçu de ce qui fait d'Express un framework web si spécial. Nous décrivons les principales fonctionnalités et montrons quelques-uns des principaux composants d'une application Express (bien que vous ne disposiez pas encore d'un environnement de développement pour le tester). +Dans ce tout premier article consacré à Express, nous répondons aux questions « Qu'est-ce que Node ? » et « Qu'est-ce que Express ? », et vous donnons un aperçu de ce qui fait d'Express un framework web si spécial. Nous décrivons les principales fonctionnalités et montrons quelques-uns des principaux composants d'une application Express (bien que vous ne disposiez pas encore d'un environnement de développement pour le tester). <table class="standard-table"> <tbody> <tr> - <th scope="row">Prérequis :</th> + <th scope="row">Prérequis :</th> <td> Une culture de base en informatique, une compréhension globale de la <a href="/fr/docs/Learn/Server-side/First_steps" @@ -31,7 +31,7 @@ Dans ce tout premier article consacré à Express, nous répondons aux questions </td> </tr> <tr> - <th scope="row">Objectif :</th> + <th scope="row">Objectif :</th> <td> Devenir familier avec ce qu'est Express et comment il s'intègre dans Node, les fonctionnalités qu'il apporte, et les principales étapes pour @@ -45,7 +45,7 @@ Dans ce tout premier article consacré à Express, nous répondons aux questions [Node](https://nodejs.org/) (ou plus formellement _Node.js_) est un environnement d'exécution open-source, multi-plateforme, qui permet aux développeuses et développeurs de créer toutes sortes d'applications et d'outils côté serveur en [JavaScript](/fr/docs/Glossary/JavaScript). Cet environnement est destiné à être utilisé en dehors du navigateur (il s'exécute directement sur son ordinateur ou dans le système d'exploitation du serveur). Aussi, Node ne permet pas d'utiliser les API JavaScript liées au navigateur mais des API plus traditionnellement utilisées sur un serveur dont notamment celles pour HTTP ou la manipulation de systèmes de fichier. -Dans une perspective de développement de serveur web, Node présente un certain nombre d'avantages : +Dans une perspective de développement de serveur web, Node présente un certain nombre d'avantages : - D'excellentes performances ! Node a été créé pour optimiser le rendement et l'évolution des applications web. Il s'agit d'une bonne solution à de nombreux problèmes de développement web (par exemple, les applications en temps réel). - Le code est intégralement écrit en JavaScript ce qui signifie que l'on dépense moins d'énergie à basculer d'un langage à l'autre quand on code côté client et côté serveur. @@ -117,13 +117,13 @@ Bien qu'Express soit assez minimaliste, des _middlewares_ (fonctions intermédia > **Note :** Cette flexibilité est à double tranchant. Il y a une multitude de paquets pour résoudre chaque problème mais trouver le bon paquet à utiliser peut vite devenir un défi. Il n'y a pas non plus de « bonne manière » pour structurer une application et beaucoup d'exemples que vous trouverez sur le net ne sont pas optimisés ou montrent seulement une infime partie de ce que vous devez faire pour développer une application web. -## D'où viennent Node et Express ? +## D'où viennent Node et Express ? À ses débuts en 2009, Node a été publié pour Linux uniquement. Le gestionnaire de paquets NPM est sorti en 2010, et le support natif de Windows fut ajouté en 2012. Ceci est un très court aperçu d'une aventure riche en rebondissements. Allez creuser ça sur [Wikipédia](https://fr.wikipedia.org/wiki/Node.js#Historique) si vous voulez en savoir plus. Express est sorti pour la première fois en novembre 2010. Vous pouvez consulter la [liste des modifications](https://expressjs.com/en/changelog/4x.html) pour plus d'informations sur la version courante et [GitHub](https://github.com/expressjs/express/blob/master/History.md) pour plus de détails sur l'historique des publications. -## Quelle popularité pour Node et Express ? +## Quelle popularité pour Node et Express ? La popularité d'un _framework_ web est importante car elle conditionne la maintenance dans le temps et les ressources qu'il est raisonnable de mettre à disposition dans la documentation, les bibliothèques d'extensions et le support technique. diff --git a/files/fr/learn/server-side/first_steps/client-server_overview/index.md b/files/fr/learn/server-side/first_steps/client-server_overview/index.md index 9cf9f2b4e0..a66e0ec417 100644 --- a/files/fr/learn/server-side/first_steps/client-server_overview/index.md +++ b/files/fr/learn/server-side/first_steps/client-server_overview/index.md @@ -6,7 +6,7 @@ original_slug: Learn/Server-side/Premiers_pas/Client-Serveur --- {{LearnSidebar}}{{PreviousMenuNext("Learn/Server-side/First_steps/Introduction", "Learn/Server-side/First_steps/Web_frameworks", "Learn/Server-side/First_steps")}} -Maintenant que vous connaissez le but et le bénéfice de la programmation côté serveur, nous allons analyser en détails ce qui se passe quand un serveur reçoit une requête dynamique de la part d'un navigateur. Comme la plupart des sites gèrent le code côté serveur (requêtes et réponses) de la même manière, cela vous aidera à comprendre ce que vous devrez faire ensuite en écrivant votre propre code. +Maintenant que vous connaissez le but et le bénéfice de la programmation côté serveur, nous allons analyser en détails ce qui se passe quand un serveur reçoit une requête dynamique de la part d'un navigateur. Comme la plupart des sites gèrent le code côté serveur (requêtes et réponses) de la même manière, cela vous aidera à comprendre ce que vous devrez faire ensuite en écrivant votre propre code. <table class="standard-table"> <tbody> @@ -28,13 +28,13 @@ Maintenant que vous connaissez le but et le bénéfice de la programmation côtà </tbody> </table> -Il n'y a pas de code "réel" dans la suite de cette présentation parce que nous n'avons pas encore choisi un framework web à utiliser pour écrire notre code ! Ce tutoriel est quand même trés pertinent car les comportements décrits doivent être implémentés par votre code côté serveur, sans qu'il ait à se soucier (le serveur...)  de quel langage de programmation ou framework vous vous servez. +Il n'y a pas de code "réel" dans la suite de cette présentation parce que nous n'avons pas encore choisi un framework web à utiliser pour écrire notre code ! Ce tutoriel est quand même trés pertinent car les comportements décrits doivent être implémentés par votre code côté serveur, sans qu'il ait à se soucier (le serveur...) de quel langage de programmation ou framework vous vous servez. ## Serveurs Web et HTTP (un avant-goût) Les navigateurs web communiquent avec les serveurs web avec le protocole [HTTP](/fr/docs/Web/HTTP) **: H**yper**T**ext**T**ransfer **P**rotocol. Quand vous cliquez un lien sur une page, soumettez un formulaire ou lancez une recherche, le navigateur envoie une requête HTTP (_HTTP Request)_ au serveur. -Cette requête inclut : +Cette requête inclut : - Une URL identifiant la cible et la ressource (un fichier HTML, un point particulier de données sur le serveur ou un outil à lancer). - Une méthode qui définit l'action requise ( par exemple récupérer un fichier ou sauvegarder certaines données ou mises à jour). Les différentes méthodes/verbes et les actions associées sont listées ci-dessous : @@ -48,22 +48,22 @@ Cette requête inclut : - Des informations complémentaires peuvent être encodées avec la requête (des données de formulaire HTML par exemple). Ces informations peuvent être encodées comme : - - Paramètres URL : les requêtes `GET` encodent les données dans l'URL envoyée au serveur en ajoutant des paires nom/valeur en fin de celle-ci. Exemple : `http://mysite.com?name=Fred&age=11`.   Il y a toujours un point d'interrogation (`?`) séparant le début de l'URL des paramètres passés. Ainsi qu'un signe égal (`=`) séparant le nom de la valeur associée et une esperluette (`&`) séparant chaque paire. Les paramètres URL ne sont pas sécurisés car ils peuvent être changés et soumis une deuxième fois par l'utilisateur. Pour cette raison, les requêtes URL paramètres/`GET` requests ne sont pas utilisées pour des requêtes mettant à jour des données sur un serveur. + - Paramètres URL : les requêtes `GET` encodent les données dans l'URL envoyée au serveur en ajoutant des paires nom/valeur en fin de celle-ci. Exemple : `http://mysite.com?name=Fred&age=11`. Il y a toujours un point d'interrogation (`?`) séparant le début de l'URL des paramètres passés. Ainsi qu'un signe égal (`=`) séparant le nom de la valeur associée et une esperluette (`&`) séparant chaque paire. Les paramètres URL ne sont pas sécurisés car ils peuvent être changés et soumis une deuxième fois par l'utilisateur. Pour cette raison, les requêtes URL paramètres/`GET` requests ne sont pas utilisées pour des requêtes mettant à jour des données sur un serveur. -- `POST` data. Les requêtes `POST` ajoutent de nouvelles ressources dont les données sont encodées dans le corps de la requête. -- Cookies côté Client. Contient les données de session du client, incluant les clés dont peut se servir le serveur pour déterminer le statut de login et les accés/permissions aux ressources. +- `POST` data. Les requêtes `POST` ajoutent de nouvelles ressources dont les données sont encodées dans le corps de la requête. +- Cookies côté Client. Contient les données de session du client, incluant les clés dont peut se servir le serveur pour déterminer le statut de login et les accés/permissions aux ressources. -Les serveurs Web attendent une requête du client puis la traitent quand elle arrive. Il répond ensuite au navigateur avec un message HTTP Response. La réponse contient un statut [HTTP Response](/fr/docs/Web/HTTP/Status) indiquant si, oui ou non, la requête a abouti. (ex : "`200 OK`" pour un succés, "`404 Not Found`" si la ressource ne peut être trouvée, "`403 Forbidden`" si l'utilisateur n'est pas autorisé à voir la ressource etc. Le corps d'une réponse aboutie à une requête `GET` contiendrait la ressource demandée. +Les serveurs Web attendent une requête du client puis la traitent quand elle arrive. Il répond ensuite au navigateur avec un message HTTP Response. La réponse contient un statut [HTTP Response](/fr/docs/Web/HTTP/Status) indiquant si, oui ou non, la requête a abouti. (ex : "`200 OK`" pour un succés, "`404 Not Found`" si la ressource ne peut être trouvée, "`403 Forbidden`" si l'utilisateur n'est pas autorisé à voir la ressource etc. Le corps d'une réponse aboutie à une requête `GET` contiendrait la ressource demandée. Quand une page HTML est retournée, elle est affichée par le navigateur. Le navigateur, nativement, pourra découvrir des liens vers d'autres ressources (ex : une page HTML intégre habituellement des pages JavaScript et CSS ), et enverra des requêtes séparées pour télécharger ces fichiers. Les sites web dynamiques ou statiques (voir sections suivantes) utilisent les mêmes protocoles/modèles de communication. -### Exemple de requête/réponse GET +### Exemple de requête/réponse GET Vous faites une simple requête `GET` en cliquant sur un lien ou en faisant une recherche sur un site (sur une page de moteur de recherche par exemple). Une requête HTTP envoyée lorsque vous effectuez une recherche sur MDN pour les termes : "La relation Client-Serveur" ressemblera beaucoup à ce qui suit mais ne sera pas identique car des parties du message dépendent des paramètres de votre navigateur. -> **Note :** Le format des messsages HTTP est défini par un standard web ([RFC7230](http://www.rfc-editor.org/rfc/rfc7230.txt)). Vous n'avez pas besoin de connaître ce niveau de détails mais vous saurez au moins d'où vient tout ça ! +> **Note :** Le format des messsages HTTP est défini par un standard web ([RFC7230](http://www.rfc-editor.org/rfc/rfc7230.txt)). Vous n'avez pas besoin de connaître ce niveau de détails mais vous saurez au moins d'où vient tout ça ! #### La requête @@ -86,19 +86,19 @@ Chaque ligne de la requête contient des informations sur celle-ci. La première Les premières et secondes lignes contiennent la plupart des données déjà évoquées précédemment : - Le type de la requête (`GET`). -- La cible de la ressource URL (`/en-US/search`). +- La cible de la ressource URL (`/en-US/search`). - Les paramètres URL (`q=La%2relation%2Client%2-%2Bserveur&topic=apps&topic=html&topic=css&topic=js&topic=api&topic=webdev`). - Le site web cible/hôte (developer.mozilla.org). - La fin de la première ligne inclut aussi une petite chaîne identifiant la version spécifique du protocole (`HTTP/1.1`). -La dernière ligne contient des données sur les cookies côté client — vous observerez que dans ce cas, le cookie a une id pour gérer la session :   (`Cookie: sessionid=6ynxs23n521lu21b1t136rhbv7ezngie; ...`). +La dernière ligne contient des données sur les cookies côté client — vous observerez que dans ce cas, le cookie a une id pour gérer la session : (`Cookie: sessionid=6ynxs23n521lu21b1t136rhbv7ezngie; ...`). Les lignes restantes concernent le navigateur utilisé et les sortes de réponses qu'il peut accepter. Par exemple, vous pouvez voir ceci : - Mon navigateur (`User-Agent`) est Mozilla Firefox (`Mozilla/5.0`). - Il accepte les données compressées (`Accept-Encoding: gzip`). -- Il accepte les familles de caractères suivantes : (`Accept-Charset: ISO-8859-1,UTF-8;q=0.7,*;q=0.7`) et pour les langages : (`Accept-Language: de,en;q=0.7,en-us;q=0.3`). -- La ligne `Referer` indique l'adresse de la page web qui contenait le lien vers cette ressource (Par ex. l'origine de la requête : `https://developer.mozilla.org/en-US/`). +- Il accepte les familles de caractères suivantes : (`Accept-Charset: ISO-8859-1,UTF-8;q=0.7,*;q=0.7`) et pour les langages : (`Accept-Language: de,en;q=0.7,en-us;q=0.3`). +- La ligne `Referer` indique l'adresse de la page web qui contenait le lien vers cette ressource (Par ex. l'origine de la requête : `https://developer.mozilla.org/en-US/`). Les requêtes HTTP peuvent aussi avoir un corps mais dans ce cas précis, il est vide. @@ -107,11 +107,11 @@ Les requêtes HTTP peuvent aussi avoir un corps mais dans ce cas précis, il est La première partie de la réponse à cette requête est détaillée ci-dessous. L'en-tête contient les données suivantes : - La première ligne embarque le code `200 OK`, qui nous dit que la requête a abouti. -- Nous pouvons voir que la réponse est formatée en `text/html` (`Content-Type`). +- Nous pouvons voir que la réponse est formatée en `text/html` (`Content-Type`). - On remarque qu'elle utilise l'ensemble des caractères UTF-8 (`Content-Type: text/html; charset=utf-8`). - L'en-tête indique aussi la taille (`Content-Length: 41823`). -À la fin du message nous avons le contenu du corps — lequel contient le "vrai" HTML demandé par la requête. +À la fin du message nous avons le contenu du corps — lequel contient le "vrai" HTML demandé par la requête. ```html HTTP/1.1 200 OK @@ -130,15 +130,15 @@ Content-Length: 41823 <!DOCTYPE html> -<html lang="en-US" dir="ltr" class="redesign no-js"  data-ffo-opensanslight=false data-ffo-opensans=false > +<html lang="en-US" dir="ltr" class="redesign no-js" data-ffo-opensanslight=false data-ffo-opensans=false > <head prefix="og: http://ogp.me/ns#"> - <meta charset="utf-8"> - <meta http-equiv="X-UA-Compatible" content="IE=Edge"> - <script>(function(d) { d.className = d.className.replace(/\bno-js/, ''); })(document.documentElement);</script> - ... + <meta charset="utf-8"> + <meta http-equiv="X-UA-Compatible" content="IE=Edge"> + <script>(function(d) { d.className = d.className.replace(/\bno-js/, ''); })(document.documentElement);</script> + ... ``` -Le reste de l'en-tête de la réponse contient des informations sur la réponse elle-même (quand elle a été générée), sur le serveur et comment le navigateur doit gérer la page ( `X-Frame-Options: DENY` cette ligne dit au navigateur de ne pas autoriser cette page a être intégrée dans une {{htmlelement("iframe")}} dans un autre site). +Le reste de l'en-tête de la réponse contient des informations sur la réponse elle-même (quand elle a été générée), sur le serveur et comment le navigateur doit gérer la page ( `X-Frame-Options: DENY` cette ligne dit au navigateur de ne pas autoriser cette page a être intégrée dans une {{htmlelement("iframe")}} dans un autre site). ### Exemple de requête/réponse POST @@ -168,11 +168,11 @@ Cookie: sessionid=6ynxs23n521lu21b1t136rhbv7ezngie; _gat=1; csrftoken=zIPUJsAZv6 csrfmiddlewaretoken=zIPUJsAZv6pcgCBJSCj1zU6pQZbfMUAT&user-username=hamishwillee&user-fullname=Hamish+Willee&user-title=&user-organization=&user-location=Australia&user-locale=en-US&user-timezone=Australia%2FMelbourne&user-irc_nickname=&user-interests=&user-expertise=&user-twitter_url=&user-stackoverflow_url=&user-linkedin_url=&user-mozillians_url=&user-facebook_url= ``` -La principale différence est que l'URL ne comporte pas de paramètres. Comme vous voyez, l'information du formulaire est encodée dans le corps de la requête (par exemple : le nom complet du nouvel utilisateur est paramétré avec  `&user-fullname=Hamish+Willee`). +La principale différence est que l'URL ne comporte pas de paramètres. Comme vous voyez, l'information du formulaire est encodée dans le corps de la requête (par exemple : le nom complet du nouvel utilisateur est paramétré avec `&user-fullname=Hamish+Willee`). #### La réponse -La réponse à la requête est expliquée dessous. Le statut "`302 Found`" dit au navigateur que le post a abouti et qu'il peut délivrer une deuxième requête HTTP pour charger la page spécifiée dans le champ `Location`. L'information est donc en cela similaire à une réponse de requête `GET`. +La réponse à la requête est expliquée dessous. Le statut "`302 Found`" dit au navigateur que le post a abouti et qu'il peut délivrer une deuxième requête HTTP pour charger la page spécifiée dans le champ `Location`. L'information est donc en cela similaire à une réponse de requête `GET`. ```html HTTP/1.1 302 FOUND @@ -190,11 +190,11 @@ X-Cache-Info: not cacheable; request wasn't a GET or HEAD Content-Length: 0 ``` -> **Note :** Les requêtes et réponses montrées dans ces exemples ont été capturées avec l'application [Fiddler](https://www.telerik.com/download/fiddler) , mais vous pouvez avoir des informations similaires en utilisant des "renifleurs" web (e.g. [Websniffer](http://websniffer.cc/), [Wireshark](https://www.wireshark.org/)) ou des extensions de navigateur comme [HttpFox](https://addons.mozilla.org/en-US/firefox/addon/httpfox/). Vous pouvez essayer seul. Utilisez tous les outils recommandés, naviguez sur des sites et éditez des profils de données pour explorer les différentes requêtes et réponses. La plupart des navigateurs modernes ont aussi des outils qui gérent les requêtes réseau, par exemple le [Network Monitor](/fr/docs/Tools/Network_Monitor) dans Firefox). +> **Note :** Les requêtes et réponses montrées dans ces exemples ont été capturées avec l'application [Fiddler](https://www.telerik.com/download/fiddler) , mais vous pouvez avoir des informations similaires en utilisant des "renifleurs" web (e.g. [Websniffer](http://websniffer.cc/), [Wireshark](https://www.wireshark.org/)) ou des extensions de navigateur comme [HttpFox](https://addons.mozilla.org/en-US/firefox/addon/httpfox/). Vous pouvez essayer seul. Utilisez tous les outils recommandés, naviguez sur des sites et éditez des profils de données pour explorer les différentes requêtes et réponses. La plupart des navigateurs modernes ont aussi des outils qui gérent les requêtes réseau, par exemple le [Network Monitor](/fr/docs/Tools/Network_Monitor) dans Firefox). ## Les sites statiques -Un site statique renvoie le même contenu codé en dur depuis le serveur quelle que soit la ressource demandée. Si vous avez une page concernant un produit à l'adresse `/static/myproduct1.html`, cette même page sera retournée à chaque utilisateur. Si vous ajoutez un nouveau produit, vous devez ajouter une nouvelle page (par ex : `myproduct2.html`) et ainsi de suite. Cela peut être vraiment inefficace — Comment faire quand vous avez des milliers de pages "produit" à faire ? Vous allez répéter beaucoup de code identique dans chaque page (le modèle de base de la page, sa structure, etc.) et si vous voulez changer quoique ce soit dans la structure de la page — comme une section "produits dérivés" par exemple — alors, il faudra changer chaque page individuellement.. +Un site statique renvoie le même contenu codé en dur depuis le serveur quelle que soit la ressource demandée. Si vous avez une page concernant un produit à l'adresse `/static/myproduct1.html`, cette même page sera retournée à chaque utilisateur. Si vous ajoutez un nouveau produit, vous devez ajouter une nouvelle page (par ex : `myproduct2.html`) et ainsi de suite. Cela peut être vraiment inefficace — Comment faire quand vous avez des milliers de pages "produit" à faire ? Vous allez répéter beaucoup de code identique dans chaque page (le modèle de base de la page, sa structure, etc.) et si vous voulez changer quoique ce soit dans la structure de la page — comme une section "produits dérivés" par exemple — alors, il faudra changer chaque page individuellement.. > **Note :** Les sites statiques sont trés efficace quand vous avez un petit nombre de pages et que vous voulez envoyer le même contenu à chaque utilisateur. De toutes façons, ils peuvent avoir un coût certain de maintenance au fur et à mesure de l'augmentation du nombre de pages. @@ -202,19 +202,19 @@ Voyons comment tout cela marche en révisant un diagramme d'architecture de site  -Quand un utilisateur veut naviguer jusqu'à une page, le navigateur envoie une requête HTTP `GET` spécifiant l'URL de sa page HTML. Le serveur retourne le document demandé depuis son système de fichiers et retourne une réponse HTTP contenant le document et un [HTTP Response status code](/fr/docs/Web/HTTP/Status) ( statut codé de la réponse HTTP) qui est "`200 OK`" (indiquant le succés de l'opération). Le serveur peut retourner un statut différent, par exemple "`404 Not Found`" si le fichier est absent sur le serveur , ou bien "`301 Moved Permanently`" si le fichier existe mais a été déplacé vers une nouvelle localisation. +Quand un utilisateur veut naviguer jusqu'à une page, le navigateur envoie une requête HTTP `GET` spécifiant l'URL de sa page HTML. Le serveur retourne le document demandé depuis son système de fichiers et retourne une réponse HTTP contenant le document et un [HTTP Response status code](/fr/docs/Web/HTTP/Status) ( statut codé de la réponse HTTP) qui est "`200 OK`" (indiquant le succés de l'opération). Le serveur peut retourner un statut différent, par exemple "`404 Not Found`" si le fichier est absent sur le serveur , ou bien "`301 Moved Permanently`" si le fichier existe mais a été déplacé vers une nouvelle localisation. -Le serveur d'un site statique n'aura à faire face qu'à des requêtes GET vu qu'il ne stocke aucune donnée modifiable. Il ne change pas non plus ses réponses basées sur les données des requêtes HTTP (c'est à dire les paramètres URL ou les cookies). +Le serveur d'un site statique n'aura à faire face qu'à des requêtes GET vu qu'il ne stocke aucune donnée modifiable. Il ne change pas non plus ses réponses basées sur les données des requêtes HTTP (c'est à dire les paramètres URL ou les cookies). Comprendre comment fonctionnent les sites statiques est sans aucun doute trés utile à l'apprentissage de la programmation côté serveur car les sites dynamiques gèrent les requêtes pour les fichiers statiques (CSS, JavaScript, images statiques , etc.) exactement de la même manière. ## Les sites dynamiques -Un site dynamique peut générer et retourner du contenu basé sur une requête URL spécifique et les données (plutôt que de toujours renvoyer le même fichier codé en dur à une URL particulière). Toujours avec l'exemple d'un site "produits", le serveur stockera les données du produit dans une base de données plutôt que dans un fichier HTML individuel. Quand il reçoit une requête HTTP `GET` pour un produit, le serveur détermine l'ID du produit, va chercher les données dans la base de données puis construit la page HTML pour la réponse en intégrant les données dans un gabarit (template) HTML. C'est un avantage indéniable sur un site statique : +Un site dynamique peut générer et retourner du contenu basé sur une requête URL spécifique et les données (plutôt que de toujours renvoyer le même fichier codé en dur à une URL particulière). Toujours avec l'exemple d'un site "produits", le serveur stockera les données du produit dans une base de données plutôt que dans un fichier HTML individuel. Quand il reçoit une requête HTTP `GET` pour un produit, le serveur détermine l'ID du produit, va chercher les données dans la base de données puis construit la page HTML pour la réponse en intégrant les données dans un gabarit (template) HTML. C'est un avantage indéniable sur un site statique : Utiliser une base de données permet à l'information "produit" d'être stockée efficacement, en étant modifiable, extensible et bien indexée. -Employer des gabarits HTML facilite la façon de changer la structure HTML parce que c'est fait en un seul endroit, dans un seul gabarit (template) et non pas sur potentiellement des milliers de pages statiques. +Employer des gabarits HTML facilite la façon de changer la structure HTML parce que c'est fait en un seul endroit, dans un seul gabarit (template) et non pas sur potentiellement des milliers de pages statiques. ### Anatomie d'un requête dynamique @@ -240,7 +240,7 @@ Une opération de mise à jour d'un enregistrement dans la base de données sera Le travail d'une application Web consiste à recevoir des requêtes HTTP et à renvoyer des réponses HTTP. Bien que l'interaction avec une base de données pour obtenir ou mettre à jour des informations soit une tâche très courante, le code peut faire d'autres choses en même temps, ou ne pas interagir du tout avec une base de données.Un bon exemple de tâche supplémentaire qu'une application Web pourrait exécuter serait l'envoi d'un courrier électronique aux utilisateurs pour confirmer leur inscription sur le site. Le site peut également effectuer une journalisation ou d’autres opérations. -### Renvoyer autre chose que du HTML +### Renvoyer autre chose que du HTML Le code de site Web côté serveur ne doit pas nécessairement renvoyer des extraits / fichiers HTML dans la réponse. Au lieu de cela, il peut créer et renvoyer de manière dynamique d'autres types de fichiers (texte, PDF, CSV, etc.) ou même des données (JSON, XML, etc.).L'idée de renvoyer des données à un navigateur Web afin qu'il puisse mettre à jour de manière dynamique son propre contenu ({{glossary ("AJAX")}}) existe depuis un certain temps. Plus récemment, les "applications à page unique" sont devenues populaires, le site Web entier étant écrit avec un seul fichier HTML mis à jour de manière dynamique en cas de besoin. Les sites Web créés à l'aide de ce style d'application génèrent des coûts de calcul considérables entre le serveur et le navigateur Web, ce qui peut donner l'impression que les sites Web se comportent beaucoup plus comme des applications natives (très réactives, etc.). @@ -266,7 +266,7 @@ urlpatterns = [ > **Note :** Les premiers paramètres des fonctions url () peuvent paraître un peu bizarres (par exemple, r '^ junior / $') car ils utilisent une technique de correspondance de modèle appelée "expressions régulières" (RegEx ou RE). Vous n'avez pas besoin de savoir comment fonctionnent les expressions régulières à ce stade, car elles nous permettent également de faire correspondre les modèles de l'URL (plutôt que les valeurs codées en dur ci-dessus) et de les utiliser comme paramètres dans nos fonctions d'affichage. À titre d'exemple, un RegEx très simple pourrait dire "faire correspondre une seule lettre majuscule, suivie de 4 à 7 lettres minuscules". -L'infrastructure Web permet également à une fonction d'affichage d'extraire facilement des informations de la base de données. La structure de nos données est définie dans des modèles, qui sont des classes Python qui définissent les champs à stocker dans la base de données sous-jacente. Si nous avons un modèle nommé Team avec un champ "team_type", nous pouvons utiliser une syntaxe de requête simple pour récupérer toutes les équipes ayant un type particulier.L’exemple ci-dessous donne la liste de toutes les équipes ayant le type d’équipe exact (sensible à la casse) de "junior" - notez le format: nom du champ (team_type) suivi du double underscore, puis du type de match à utiliser (ici nous utilisons: exact). ). Il existe de nombreux autres types de match et nous pouvons les enchaîner. Nous pouvons également contrôler l'ordre et le nombre de résultats retournés. +L'infrastructure Web permet également à une fonction d'affichage d'extraire facilement des informations de la base de données. La structure de nos données est définie dans des modèles, qui sont des classes Python qui définissent les champs à stocker dans la base de données sous-jacente. Si nous avons un modèle nommé Team avec un champ "team_type", nous pouvons utiliser une syntaxe de requête simple pour récupérer toutes les équipes ayant un type particulier.L’exemple ci-dessous donne la liste de toutes les équipes ayant le type d’équipe exact (sensible à la casse) de "junior" - notez le format: nom du champ (team_type) suivi du double underscore, puis du type de match à utiliser (ici nous utilisons: exact). ). Il existe de nombreux autres types de match et nous pouvons les enchaîner. Nous pouvons également contrôler l'ordre et le nombre de résultats retournés. ```python #best/views.py @@ -277,9 +277,9 @@ from .models import Team def junior(request): -  list_teams = Team.objects.filter(team_type__exact="junior") -  context = {'list': list_teams} -  return render(request, 'best/index.html', context) + list_teams = Team.objects.filter(team_type__exact="junior") + context = {'list': list_teams} + return render(request, 'best/index.html', context) ``` Une fois que la fonction junior () a obtenu la liste des équipes juniors, elle appelle la fonction render () en transmettant la requête HttpRequest d'origine, un modèle HTML et un objet "context" définissant les informations à inclure dans le modèle. La fonction render () est une fonction pratique qui génère du HTML à l'aide d'un context et d'un template HTML, puis le renvoie dans un objet HttpResponse.De toute évidence, les frameworks Web peuvent vous aider dans de nombreuses autres tâches. Nous discutons beaucoup plus d'avantages et de choix de frameworks Web populaires dans le prochain article. diff --git a/files/fr/learn/server-side/first_steps/index.md b/files/fr/learn/server-side/first_steps/index.md index 020b17c1f0..30840e4c56 100644 --- a/files/fr/learn/server-side/first_steps/index.md +++ b/files/fr/learn/server-side/first_steps/index.md @@ -12,11 +12,11 @@ original_slug: Learn/Server-side/Premiers_pas --- {{LearnSidebar}} -Dans ce module nous répondrons à quelques questions fondamentales sur la programmation côté serveur — "Qu'est-ce que c'est ?", "En quoi diffère-t-elle de la programmation côté client ?" et "Pourquoi est-ce si utile ?". Nous fournirons ensuite un aperçu de certaines infrastructures d'applications web (aussi appelé frameworks) côté serveurs parmi les plus populaires, ainsi que des conseils pour sélectionner la plus approprié pour créer votre premier site. Enfin un article vous présentera les questions de sécurité pour un serveur web. +Dans ce module nous répondrons à quelques questions fondamentales sur la programmation côté serveur — "Qu'est-ce que c'est ?", "En quoi diffère-t-elle de la programmation côté client ?" et "Pourquoi est-ce si utile ?". Nous fournirons ensuite un aperçu de certaines infrastructures d'applications web (aussi appelé frameworks) côté serveurs parmi les plus populaires, ainsi que des conseils pour sélectionner la plus approprié pour créer votre premier site. Enfin un article vous présentera les questions de sécurité pour un serveur web. ## Prérequis -Pour suivre ce module, aucune connaissance en programmation web côté serveur ou tout autre type de programmation n'est nécessaire. +Pour suivre ce module, aucune connaissance en programmation web côté serveur ou tout autre type de programmation n'est nécessaire. Vous aurez besoin de comprendre "comment fonctionne le web". Nous vous recommandons de lire d'abord les sujets suivants : @@ -33,9 +33,9 @@ Avec cette compréhension de base, vous serez prêts à parcourir les modules de - [Présentation client-serveur](/fr/docs/Learn/Server-side/Premiers_pas/Client-Serveur) - : Maintenant que vous connaissez le but et les avantages potentiels de la programmation côté serveur, nous allons examiner en détail ce qui se passe lorsqu'un serveur reçoit une "requête dynamique" d'un navigateur. Comme la plupart des codes côté serveur traitent les demandes et les réponses de la même manière, cela vous aidera à comprendre ce que vous devez faire lorsque vous écrivez votre propre code. - [Frameworks web côté serveur](/fr/docs/Learn/Server-side/Premiers_pas/Web_frameworks) - - : Le dernier article vous a montré ce qu'une application web côté serveur doit faire pour répondre aux demandes d'un navigateur web. Nous allons maintenant vous montrer comment les frameworks web peuvent simplifier ces tâches ; nous vous aiderons à choisir le bon framework pour coder votre première application web côté serveur. + - : Le dernier article vous a montré ce qu'une application web côté serveur doit faire pour répondre aux demandes d'un navigateur web. Nous allons maintenant vous montrer comment les frameworks web peuvent simplifier ces tâches ; nous vous aiderons à choisir le bon framework pour coder votre première application web côté serveur. - [Sécurité de votre site web](/fr/docs/Learn/Server-side/First_steps/Website_security) - - : La sécurité de votre site web requiert une vigilance dans tous les aspects de sa conception et de son utilisation. Cet article d'introduction ne fera pas de vous un gourou de la sécurité des sites web, mais il vous aidera à comprendre les premières mesures importantes à prendre pour mettre votre application web à l'abri des menaces les plus courantes. + - : La sécurité de votre site web requiert une vigilance dans tous les aspects de sa conception et de son utilisation. Cet article d'introduction ne fera pas de vous un gourou de la sécurité des sites web, mais il vous aidera à comprendre les premières mesures importantes à prendre pour mettre votre application web à l'abri des menaces les plus courantes. ## Évaluations diff --git a/files/fr/learn/server-side/first_steps/introduction/index.md b/files/fr/learn/server-side/first_steps/introduction/index.md index 9763f1cba6..2fe460b0f2 100644 --- a/files/fr/learn/server-side/first_steps/introduction/index.md +++ b/files/fr/learn/server-side/first_steps/introduction/index.md @@ -13,7 +13,7 @@ original_slug: Learn/Server-side/Premiers_pas/Introduction --- {{LearnSidebar}}{{NextMenu("Learn/Server-side/First_steps/Client-Server_overview", "Learn/Server-side/First_steps")}} -Bienvenue au cours pour débutant de MDN sur la programmation côté serveur. Dans ce premier article, nous allons regarder la programmation côté serveur sans rentrer dans les détails techniques, en répondant aux questions telles que "qu'est-ce que c'est?", "quelle est la différence avec la programmation côté client?", et "pourquoi est-ce utile?". Après avoir lu cet article vous comprendrez commment la programmation côté serveur donne toute leur puissance aux sites web. +Bienvenue au cours pour débutant de MDN sur la programmation côté serveur. Dans ce premier article, nous allons regarder la programmation côté serveur sans rentrer dans les détails techniques, en répondant aux questions telles que "qu'est-ce que c'est?", "quelle est la différence avec la programmation côté client?", et "pourquoi est-ce utile?". Après avoir lu cet article vous comprendrez commment la programmation côté serveur donne toute leur puissance aux sites web. <table class="standard-table"> <tbody> @@ -34,9 +34,9 @@ Bienvenue au cours pour débutant de MDN sur la programmation côté serveur. Da </tbody> </table> -La plupart des sites web à grande échelle utilisent du code côté serveur pour afficher dynamiquement différentes données lorsque nécessaire ; ces données sont généralement extraites d'une base de données stockée sur un serveur et envoyées au client pour être affichées avec du code (HTML et/ou JavaScript). +La plupart des sites web à grande échelle utilisent du code côté serveur pour afficher dynamiquement différentes données lorsque nécessaire ; ces données sont généralement extraites d'une base de données stockée sur un serveur et envoyées au client pour être affichées avec du code (HTML et/ou JavaScript). -L'avantage le plus significatif du code côté serveur est sans doute qu'il permet d'adapter le contenu du site web à chaque utilisateur. Les sites dynamiques peuvent mettre en évidence du contenu pertinent en fonction des préférences et des habitudes de l'utilisateur. Il peut également faciliter l'utililsation des sites web en stockant des données, des informations personnelles — par exemple donner la possibilité d'enregistrer une carte de crédit pour les achats suivants. +L'avantage le plus significatif du code côté serveur est sans doute qu'il permet d'adapter le contenu du site web à chaque utilisateur. Les sites dynamiques peuvent mettre en évidence du contenu pertinent en fonction des préférences et des habitudes de l'utilisateur. Il peut également faciliter l'utililsation des sites web en stockant des données, des informations personnelles — par exemple donner la possibilité d'enregistrer une carte de crédit pour les achats suivants. Cela peut même permettre une interaction avec les utilisateurs du site, en envoyant des notifications et mises à jour par email ou via d'autres canaux. Toutes ces capacités rendent possible un engagement plus important avec l'utilisateur. @@ -44,9 +44,9 @@ Dans le monde moderne du développement web, apprendre le développement côté ## Qu'est-ce que la programmation côté serveur? -Les navigateurs web communiquent avec les [serveurs web](/fr/Apprendre/Qu_est-ce_qu_un_serveur_web) en utilisant le protocole {{glossary("HTTP")}} (**H**yper**T**ext **T**ransfer **P**rotocol). Quand vous cliquez sur un lien dans une page web, envoyez un formulaire, ou encore lancez une recherche, une **requête HTTP** est envoyée du navigateur au serveur cible. +Les navigateurs web communiquent avec les [serveurs web](/fr/Apprendre/Qu_est-ce_qu_un_serveur_web) en utilisant le protocole {{glossary("HTTP")}} (**H**yper**T**ext **T**ransfer **P**rotocol). Quand vous cliquez sur un lien dans une page web, envoyez un formulaire, ou encore lancez une recherche, une **requête HTTP** est envoyée du navigateur au serveur cible. -Une requête inclut _une URL_ pour identifier la ressource demandée, _une méthode_ pour définir l'action désirée (comme GET pour obtenir, DELETE pour supprimer ou POST pour publier) et peut également inclure des informations supplémentaires encodées dans _les paramètres_ de l'URL (des paires clés/valeurs envoyées via une chaîne de recherche — [query string](https://en.wikipedia.org/wiki/Query_string) en anglais), les données POST (données envoyées par la [méthode HTTP POST](/fr/docs/Web/HTTP/M%C3%A9thode/POST)), ou les {{glossary("Cookie", "cookies associés")}}. +Une requête inclut _une URL_ pour identifier la ressource demandée, _une méthode_ pour définir l'action désirée (comme GET pour obtenir, DELETE pour supprimer ou POST pour publier) et peut également inclure des informations supplémentaires encodées dans _les paramètres_ de l'URL (des paires clés/valeurs envoyées via une chaîne de recherche — [query string](https://en.wikipedia.org/wiki/Query_string) en anglais), les données POST (données envoyées par la [méthode HTTP POST](/fr/docs/Web/HTTP/M%C3%A9thode/POST)), ou les {{glossary("Cookie", "cookies associés")}}. Les serveurs web attendent des requêtes du client, les traitent quand elles arrivent, et répondent au navigateur web avec une **réponse HTTP**. La réponse contient _un statut_ qui indique si la requête a pu être traitée avec succès ou non (exemple "HTTP/1.1 200 OK" pour indiquer le succès). @@ -64,13 +64,13 @@ Le serveur récupère le document demandé du système de fichiers et retourne u Un site web dynamique, quant à lui, est un site dont une partie des réponses sont générées _dynamiquement_, à la demande. Sur les sites dynamiques, les pages HTML sont normalement crées en insérant des données d'une base de données dans des espaces réservés à l'intérieur de templates HTML (c'est une manière beaucoup plus efficace que des fichiers statiques pour stocker de grandes quantités de contenu). -Un site dynamique peut retourner des données différentes pour une URL, en se basant sur les informations fournies par l'utilisateur ou les préférences stockées et peut effectuer des opérations avant de retourner la réponse. +Un site dynamique peut retourner des données différentes pour une URL, en se basant sur les informations fournies par l'utilisateur ou les préférences stockées et peut effectuer des opérations avant de retourner la réponse. La plupart du code pour maintenir un site web dynamique doit s'exécuter sur le serveur. La création de ce code est ce qu'on appelle la "**programmation côté serveur**" (ou parfois "**codage back-end**"). Le diagramme ci-dessous montre une architecture simple pour un _site web dynamique_. Comme dans le diagramme précédent, les navigateurs envoient des requêtes HTTP au serveur, qui les traite et retourne les réponses HTTP appropriées. -Les requêtes pour les ressources _statiques_ sont toujours gérées de la même manière que pour les sites statiques (les ressources statiques sont tous les fichiers qui ne changent pas —typiquement : CSS, JavaScript, Images, fichiers PDF etc). +Les requêtes pour les ressources _statiques_ sont toujours gérées de la même manière que pour les sites statiques (les ressources statiques sont tous les fichiers qui ne changent pas —typiquement : CSS, JavaScript, Images, fichiers PDF etc).  @@ -82,19 +82,19 @@ Voyons maintenant le code impliqué dans la programmation côté serveur et côt - Ils ont des objectifs et des préoccupations différents. - Ils n'utilisent généralement pas les mêmes langages de programmation (exception faite de JavaScript, qui peut être utilisé côté serveur et côté client). -- Ils s'exécutent dans des environnements différents du système d'exploitation. +- Ils s'exécutent dans des environnements différents du système d'exploitation. -Le code exécuté par le navigateur est connu sous le nom de **code côté client** (ou **front-end**) et se préoccupe principalement de l'apparence et du comportement des pages web affichées. Cela inclut sélectionner et styliser les composants de l'interface utilisateur, créer la mise en page, la navigation, valider les formulaires, etc. D'un autre côté, la programmation côté serveur implique de définir le contenu retourné au navigateur en réponse aux requêtes. Le code côté serveur gère des tâches telles que la validation des données envoyées, l'utilisation des bases de données pour stocker et récupérer des données et l'envoi de données générées au client tel qu'attendu. +Le code exécuté par le navigateur est connu sous le nom de **code côté client** (ou **front-end**) et se préoccupe principalement de l'apparence et du comportement des pages web affichées. Cela inclut sélectionner et styliser les composants de l'interface utilisateur, créer la mise en page, la navigation, valider les formulaires, etc. D'un autre côté, la programmation côté serveur implique de définir le contenu retourné au navigateur en réponse aux requêtes. Le code côté serveur gère des tâches telles que la validation des données envoyées, l'utilisation des bases de données pour stocker et récupérer des données et l'envoi de données générées au client tel qu'attendu. -Le code client est écrit en [HTML](/fr/Apprendre/HTML), [CSS](/fr/Apprendre/CSS), et [JavaScript](/fr/Apprendre/JavaScript) — il est exécuté dans un navigateur web et a peu ou pas accès au système d'exploitation de l'utilisateur (inclut un accès limité au système de fichiers). +Le code client est écrit en [HTML](/fr/Apprendre/HTML), [CSS](/fr/Apprendre/CSS), et [JavaScript](/fr/Apprendre/JavaScript) — il est exécuté dans un navigateur web et a peu ou pas accès au système d'exploitation de l'utilisateur (inclut un accès limité au système de fichiers). -Les développeurs web ne peuvent pas contrôler quel navigateur est utilisé par l'utilisateur pour voir le site web — or les navigateurs fournissent des niveaux de compatibilité inconsistants quant aux fonctionnalités du code côté client, et une partie du challenge de la programmation côté client consiste à gérer les différences de support des navigateurs. +Les développeurs web ne peuvent pas contrôler quel navigateur est utilisé par l'utilisateur pour voir le site web — or les navigateurs fournissent des niveaux de compatibilité inconsistants quant aux fonctionnalités du code côté client, et une partie du challenge de la programmation côté client consiste à gérer les différences de support des navigateurs. -Le code côté serveur peut être écrit dans nombre de langages de programmation — les langages les plus populaires pour la programmation web côté serveur sont en autres PHP, Python, Ruby, C#, et NodeJS(JavaScript). Le code côté serveur a plein accès au système d'exploitation du serveur et le développeur est libre de choisir le langage (et la version) qu'il veut utiliser. +Le code côté serveur peut être écrit dans nombre de langages de programmation — les langages les plus populaires pour la programmation web côté serveur sont en autres PHP, Python, Ruby, C#, et NodeJS(JavaScript). Le code côté serveur a plein accès au système d'exploitation du serveur et le développeur est libre de choisir le langage (et la version) qu'il veut utiliser. Typiquement, les développeurs écrivent leur code en utilisant des **frameworks web**. Les frameworks web sont des ensembles de fonctions, objets, règles et autres constructions de code conçus pour résoudre des problèmes courants, accélérer le développement et simplifier les différents types de tâches rencontrées dans un domaine particulier. -Encore une fois, bien que le code côté client et côté serveur utilisent des frameworks, les domaines d'application sont très différents et par conséquent les frameworks aussi. Les frameworks web côté client simplifient les tâches de mise en page et de présentation tandis que les frameworks web côté serveur fournissent des fonctionnalités "courantes" que vous auriez probablement à implémenter vous-même autrement (comme le support des sessions, des utilisateurs et de l'authentification, l'accès à la base de données, les bibliothèques de templates, etc.). +Encore une fois, bien que le code côté client et côté serveur utilisent des frameworks, les domaines d'application sont très différents et par conséquent les frameworks aussi. Les frameworks web côté client simplifient les tâches de mise en page et de présentation tandis que les frameworks web côté serveur fournissent des fonctionnalités "courantes" que vous auriez probablement à implémenter vous-même autrement (comme le support des sessions, des utilisateurs et de l'authentification, l'accès à la base de données, les bibliothèques de templates, etc.). > **Note :** Les frameworks côté client sont souvent utilisés pour accélérer le développement du code côté client, mais vous pouvez également choisir d'écrire tout le code à la main ; en vérité, écrire votre code à la main peut être plus rapide et plus efficace si vous n'avez besoin que d'une petite interface web très simple. > @@ -106,15 +106,15 @@ La programmation côté serveur est très utile parce qu'elle nous permet de dé Des compagnies comme Amazon utilisent la programmation côté serveur pour construire la recherche de produits, faire des suggestions de produit ciblées sur les préférences du client et ses habitudes d'achat, simplifier les achats, etc. -Les banques l'utilisent pour stocker les informations du compte ainsi que faire des transactions et n'autoriser à les consulter que les utilisateurs reconnus. D'autres services comme Facebook, Twitter, Instagram, et Wikipedia utilisent la programmation côté serveur pour mettre en avant, partager et contrôler l'accès au contenu. +Les banques l'utilisent pour stocker les informations du compte ainsi que faire des transactions et n'autoriser à les consulter que les utilisateurs reconnus. D'autres services comme Facebook, Twitter, Instagram, et Wikipedia utilisent la programmation côté serveur pour mettre en avant, partager et contrôler l'accès au contenu. Les utilisations les plus courantes et les plus bénéfiques de la programmation côté serveur sont listées ci-dessous. Vous verrez qu'il y a quelques recoupements : ### Stockage et distribution de l'information plus efficaces -Imaginez combien de produits sont disponibles sur Amazon et combien de posts ont été écrits sur Facebook. Créer une page statique distincte pour chaque produit ou article serait totalement impossible. +Imaginez combien de produits sont disponibles sur Amazon et combien de posts ont été écrits sur Facebook. Créer une page statique distincte pour chaque produit ou article serait totalement impossible. -La programmation côté serveur nous permet plutôt de stocker l'information dans une base de données et de construire et retourner dynamiquement le HTML ainsi que d'autres types de fichiers (comme les PDF, images, etc.). Il est également possible de simplement retourner des données ({{glossary("JSON")}}, {{glossary("XML")}}, etc.) pour les afficher avec des frameworks côté client (cela réduit la charge de travail du serveur et la quantité de données qui doit être retournée). +La programmation côté serveur nous permet plutôt de stocker l'information dans une base de données et de construire et retourner dynamiquement le HTML ainsi que d'autres types de fichiers (comme les PDF, images, etc.). Il est également possible de simplement retourner des données ({{glossary("JSON")}}, {{glossary("XML")}}, etc.) pour les afficher avec des frameworks côté client (cela réduit la charge de travail du serveur et la quantité de données qui doit être retournée). Le serveur ne se limite pas à l'envoi d'informations à partir de bases de données, il peut retourner le résultat d'autres outils logiciels, ou les données de services de communication. Le contenu peut même être ciblé pour le type d'appareil client qui le reçoit. @@ -126,11 +126,11 @@ Comme les informations se trouvent dans une base de données, elles peuvent éga > 2. Cherchez un certain nombre de mot-clés et remarquez que la structure de la page de change pas, même si les résultats oui. > 3. Ouvrez deux ou trois produits. Remarquez que la structure et la disposition de la page sont identiques, mais que le contenu pour les différents produits a été extrait de la base de données. > -> Pour un terme de recherche courant ("poisson", disons) vous pouvez voir littéralement des millions de valeurs retournées. Utiliser une base de données permet à ces données d'être stockées et partagées efficacement, et permet de contrôler la présentation de l'information à partir d'un seul endroit. +> Pour un terme de recherche courant ("poisson", disons) vous pouvez voir littéralement des millions de valeurs retournées. Utiliser une base de données permet à ces données d'être stockées et partagées efficacement, et permet de contrôler la présentation de l'information à partir d'un seul endroit. ### Expérience utilisateur personnalisée -Les serveurs peuvent stocker et utiliser des informations sur les clients pour fournir une expérience utilisateur personnalisée. Par exemple, beaucoup de sites proposent d'enregistrer une carte de crédit pour que les détails n'aient pas à être saisis de nouveau. Des sites comme Google Maps peuvent utiliser les emplacement enregistrés ou l'emplacement en cours pour fournir des informations d'itinéraire et chercher ou utiliser l'historique des voyages précédents pour trouver des boutiques locales dans les résultats de recherche. +Les serveurs peuvent stocker et utiliser des informations sur les clients pour fournir une expérience utilisateur personnalisée. Par exemple, beaucoup de sites proposent d'enregistrer une carte de crédit pour que les détails n'aient pas à être saisis de nouveau. Des sites comme Google Maps peuvent utiliser les emplacement enregistrés ou l'emplacement en cours pour fournir des informations d'itinéraire et chercher ou utiliser l'historique des voyages précédents pour trouver des boutiques locales dans les résultats de recherche. Une analyse plus approfondie des habitudes des utilisateurs peut être utilisée pour anticiper leurs intérêts et personnaliser les réponses ou les notifications du serveur, par exemple pour fournir une liste des lieux précédemment visités ou les plus populaires que vous pourriez vouloir chercher sur la carte. @@ -150,14 +150,14 @@ La programmation côté serveur permet aux sites de restreindre l'accès aux uti Quelques exemples du monde réel incluent : -- Les réseaux sociaux comme Facebook permettent aux utilisateurs de contrôler entièrement leurs propres données, mais permettent seulement à leurs amis de les voir ou des les commenter. L'utilisateur détermine qui peut voir ses données, et par extension, dans le flux de qui elles apparaissent — l'autorisation est une partie centrale de l'expérience utilisateur ! +- Les réseaux sociaux comme Facebook permettent aux utilisateurs de contrôler entièrement leurs propres données, mais permettent seulement à leurs amis de les voir ou des les commenter. L'utilisateur détermine qui peut voir ses données, et par extension, dans le flux de qui elles apparaissent — l'autorisation est une partie centrale de l'expérience utilisateur ! - Le site sur lequel vous êtes en ce moment même contrôle l'accès au contenu : les articles sont visibles à tout le monde, mais seuls les utilisateurs identifiés peuvent éditer le contenu. Essayez de cliquer sur le bouton **Modifier** en haut de cette page — si vous êtes identifié, vous verrez la vue d'édition ; sinon, vous serez redirigé vers la page d'inscription. > **Note :** Il existe de nombreux autres exemples où l'accès au contenu est contrôlé. Par exemple, que voyez-vous si vous allez sur le site en ligne de votre banque ? Connectez-vous à votre compte — quelles autres informations pouvez-vous voir et modifier ? Quelles informations pouvez-vous voir que seule la banque peut changer ? ### Stocker les informations de session/d'état -La programmation côté serveur permet aux développeurs d'utiliser des **sessions** — en gros, un mécanisme qui permet au serveur de stocker des informations sur l'utilisation en cours d'un site et d'envoyer des réponses différentes selon cette information. +La programmation côté serveur permet aux développeurs d'utiliser des **sessions** — en gros, un mécanisme qui permet au serveur de stocker des informations sur l'utilisation en cours d'un site et d'envoyer des réponses différentes selon cette information. Cela permet, par exemple, à un site de savoir qu'un utilisateur s'est déjà identifié et afficher des messages qui lui sont destinés, d'afficher son historique de commande, ou peut-être encore, dans le cas d'un jeu, lui permettre de reprendre là où il en est resté. @@ -165,7 +165,7 @@ Cela permet, par exemple, à un site de savoir qu'un utilisateur s'est déjà id ### Notifications et communication -Les serveurs peuvent envoyer des notifications générales ou personnalisées à l'utilisateur via le site web lui-même ou par email, SMS, messagerie instantannée, conversations vidéo ou autres services de communication. +Les serveurs peuvent envoyer des notifications générales ou personnalisées à l'utilisateur via le site web lui-même ou par email, SMS, messagerie instantannée, conversations vidéo ou autres services de communication. Quelques exemples incluent : @@ -179,11 +179,11 @@ Quelques exemples incluent : Un site web peut collecter beaucoup de données sur les utilisateurs : ce qu'ils cherchent, ce qu'ils achètent, ce qu'ils recommandent, combien de temps ils restent sur chaque page. La programmation côté serveur peut être utilisée pour affiner les réponses en fonction de l'analyse de ces données. -Par exemple, Amazon et Google font tous deux de la publicité pour des produits en se basant sur les recherches précédentes, sur les achats que vous avez faits. +Par exemple, Amazon et Google font tous deux de la publicité pour des produits en se basant sur les recherches précédentes, sur les achats que vous avez faits. > **Note :** Si vous êtes un utilisateur de Facebook, allez sur votre flux principal et regardez les posts. Notez que certains posts ne sont pas classés par ordre numérique — en particulier, les posts qui ont le plus de "likes" sont souvent placés plus haut dans la liste que les posts plus récents. > -> Observez également quels types de publicités vous voyez — vous pourrez voir des publicités pour des choses que vous avez regardé sur d'autres sites. L'algorithme de Facebook pour mettre en avant du contenu et faire de la publicité est un peu un mystérieux, mais il est clair qu'il prend en compte vos likes et ce que vous avez l'habitude de regarder ! +> Observez également quels types de publicités vous voyez — vous pourrez voir des publicités pour des choses que vous avez regardé sur d'autres sites. L'algorithme de Facebook pour mettre en avant du contenu et faire de la publicité est un peu un mystérieux, mais il est clair qu'il prend en compte vos likes et ce que vous avez l'habitude de regarder ! ## Sommaire @@ -191,11 +191,11 @@ Félicitations, vous avez atteint la fin du premier article sur la programmation Vous avez maintenant appris que le code côté serveur est exécuté sur un serveur web et que son rôle principal est de contrôler _quelle_ information est envoyée à l'utilisateur (tandis que le code côté client gère principalement la structure et la présentation des données pour l'utilisateur). -Vous devez également comprendre que c'est utile pour créer des sites web qui délivrent de l'information _efficacement_, adaptée à chaque utilisateur et avoir une bonne idée de quelques choses que vous seriez capable de faire quand vous serez programmeur back-end. +Vous devez également comprendre que c'est utile pour créer des sites web qui délivrent de l'information _efficacement_, adaptée à chaque utilisateur et avoir une bonne idée de quelques choses que vous seriez capable de faire quand vous serez programmeur back-end. -Finalement, vous devez comprendre que le code côté serveur peut être écrit dans de nombreux langages de programmation et que l'on peut utiliser des frameworks web pour rendre ce processus plus facile. +Finalement, vous devez comprendre que le code côté serveur peut être écrit dans de nombreux langages de programmation et que l'on peut utiliser des frameworks web pour rendre ce processus plus facile. -Dans un futur article, nous vous aiderons à choisir le framework le plus adapté pour la création d'un premier site. Ensuite, nous vous présenterons les principales interactions client-serveur plus en détails. +Dans un futur article, nous vous aiderons à choisir le framework le plus adapté pour la création d'un premier site. Ensuite, nous vous présenterons les principales interactions client-serveur plus en détails. {{NextMenu("Learn/Server-side/First_steps/Client-Server_overview", "Learn/Server-side/First_steps")}} diff --git a/files/fr/learn/server-side/first_steps/web_frameworks/index.md b/files/fr/learn/server-side/first_steps/web_frameworks/index.md index c46e7f7ebf..afb18ef9d9 100644 --- a/files/fr/learn/server-side/first_steps/web_frameworks/index.md +++ b/files/fr/learn/server-side/first_steps/web_frameworks/index.md @@ -11,7 +11,7 @@ original_slug: Learn/Server-side/Premiers_pas/Web_frameworks --- {{LearnSidebar}}{{PreviousMenuNext("Learn/Server-side/First_steps/Client-Server_overview", "Learn/Server-side/First_steps/Website_security", "Learn/Server-side/First_steps")}} -L'article précédent nous a montré à quoi ressemble la communication entre les clients et les serveurs web, la nature des demandes et des réponses HTTP et ce qu’une application web côté serveur doit faire pour répondre aux demandes d’un navigateur web. Avec ces connaissances en main, il est temps d'explorer comment les frameworks peuvent nous simplifier la tâche.  En chemin, vous comprendrez comment choisir le framework le mieux adapté pour votre première application web côté serveur. +L'article précédent nous a montré à quoi ressemble la communication entre les clients et les serveurs web, la nature des demandes et des réponses HTTP et ce qu’une application web côté serveur doit faire pour répondre aux demandes d’un navigateur web. Avec ces connaissances en main, il est temps d'explorer comment les frameworks peuvent nous simplifier la tâche. En chemin, vous comprendrez comment choisir le framework le mieux adapté pour votre première application web côté serveur. <table class="standard-table"> <tbody> @@ -30,21 +30,21 @@ L'article précédent nous a montré à quoi ressemble la communication entre </tbody> </table> -Les sections suivantes proposent des illustrations avec des fragments de codes utilisés par des frameworks réels. Ne soyez pas inquiété si vous ne comprenez pas tout directement. Les explications viendront au fur et à mesure. +Les sections suivantes proposent des illustrations avec des fragments de codes utilisés par des frameworks réels. Ne soyez pas inquiété si vous ne comprenez pas tout directement. Les explications viendront au fur et à mesure. ## Vue d'ensemble -Les frameworks web côté serveur (c-à -d "web application frameworks") facilitent l'écriture, la maintenance et la mise à l'échelle d'applications web. Ils fournissent des outils et des bibliothèques qui simplifient les tâches courantes de développement web, notamment l'acheminement des URL vers les gestionnaires appropriés, l'interaction avec les bases de données, les sessions et l'autorisation des utilisateurs, le formatage de la sortie (HTML, JSON, XML, par exemple) et l'amélioration de la sécurité contre les attaques web. +Les frameworks web côté serveur (c-à -d "web application frameworks") facilitent l'écriture, la maintenance et la mise à l'échelle d'applications web. Ils fournissent des outils et des bibliothèques qui simplifient les tâches courantes de développement web, notamment l'acheminement des URL vers les gestionnaires appropriés, l'interaction avec les bases de données, les sessions et l'autorisation des utilisateurs, le formatage de la sortie (HTML, JSON, XML, par exemple) et l'amélioration de la sécurité contre les attaques web. -Dans la section suivante, nous allons voir un peu plus de détails comment les frameworks facilite le développement d'applications web. Nous verrons alors les critères utilisés pour choisir un framework adapté. +Dans la section suivante, nous allons voir un peu plus de détails comment les frameworks facilite le développement d'applications web. Nous verrons alors les critères utilisés pour choisir un framework adapté. ## Que peut faire un framework web pour vous ? -Les frameworks web fournissent des outils et des bibliothèques pour simplifier les opérations de développement web courantes. Utiliser un framework web côté serveur n'est pas obligatoire, mais fortement conseillé... Cela vous facilitera grandement la vie. Cette section présente certaines des fonctionnalités parmi les plus souvent fournies par les frameworks web. +Les frameworks web fournissent des outils et des bibliothèques pour simplifier les opérations de développement web courantes. Utiliser un framework web côté serveur n'est pas obligatoire, mais fortement conseillé... Cela vous facilitera grandement la vie. Cette section présente certaines des fonctionnalités parmi les plus souvent fournies par les frameworks web. ### Travailler directement avec les requêtes et les réponses HTTP -Comme nous l'avons vu dans le dernier article, les serveurs web et les navigateurs communiquent via le protocole HTTP — les serveurs attendent les requêtes HTTP du navigateur, puis renvoient des informations dans les réponses HTTP. Les infrastructures web vous permettent d'écrire une syntaxe simplifiée qui générera un code côté serveur pour travailler avec ces demandes et réponses. Cela signifie que vous aurez un travail plus facile, en interagissant avec un code plus facile, de niveau supérieur, plutôt que des primitives de réseau de niveau inférieur. +Comme nous l'avons vu dans le dernier article, les serveurs web et les navigateurs communiquent via le protocole HTTP — les serveurs attendent les requêtes HTTP du navigateur, puis renvoient des informations dans les réponses HTTP. Les infrastructures web vous permettent d'écrire une syntaxe simplifiée qui générera un code côté serveur pour travailler avec ces demandes et réponses. Cela signifie que vous aurez un travail plus facile, en interagissant avec un code plus facile, de niveau supérieur, plutôt que des primitives de réseau de niveau inférieur. L'exemple ci-dessous montre comment cela fonctionne dans le framework web Django (Python). Chaque fonction "view" (un gestionnaire de demandes) reçoit un objet HttpRequest contenant les informations de la demande et doit renvoyer un objet HttpResponse avec la sortie formatée (dans ce cas une chaîne). @@ -137,7 +137,7 @@ Les frameworks web fournissent souvent des systèmes de templates. Ceux-ci vous Les frameworks web fournissent souvent un mécanisme facilitant la génération d'autres formats à partir de données stockées, notamment {{glossary ("JSON")}} et {{glossary ("XML")}}. -Par exemple, le système de templates Django vous permet de spécifier des variables en utilisant une syntaxe "double-handlebars" (par exemple,` {`` { variable_name ``}``} ` ), qui sera remplacée par les valeurs transmises à partir de la fonction d'affichage lors du rendu d'une page. Le système de templates prend également en charge les expressions (avec la syntaxe:`{% expression %}` ), qui permettent aux templates d'effectuer des opérations simples, telles que l'itération des valeurs de liste transmises au modèle. +Par exemple, le système de templates Django vous permet de spécifier des variables en utilisant une syntaxe "double-handlebars" (par exemple,` {`` { variable_name ``}``} ` ), qui sera remplacée par les valeurs transmises à partir de la fonction d'affichage lors du rendu d'une page. Le système de templates prend également en charge les expressions (avec la syntaxe:`{% expression %}` ), qui permettent aux templates d'effectuer des opérations simples, telles que l'itération des valeurs de liste transmises au modèle. > **Note :** Many other templating systems use a similar syntax, e.g.: Jinja2 (Python), handlebars (JavaScript), moustache (JavaScript), etc. @@ -170,22 +170,22 @@ Il existe de nombreux frameworks web pour presque tous les langages de programma Certains des facteurs qui peuvent affecter votre décision sont les suivants: -- **Effort d'apprentissage** : L'effort d'apprentissage d'un framework web dépend de votre familiarité avec le langage de programmation sous-jacent, de la cohérence de son API, de la qualité de sa documentation ainsi que de la taille et de l'activité de sa communauté. Si vous avez peu d'eqperience en programmation, pensez à Django (l’un des plus faciles à apprendre en fonction des critères ci-dessus). Si vous faites partie d'une équipe de développement qui possède déjà une expérience significative avec un framework web ou un langage de programmation particulier, il est logique de s'en tenir à cela. -- **Productivité** : la productivité mesure la rapidité avec laquelle vous pouvez créer de nouvelles fonctionnalités une fois que vous êtes familiarisé avec le framework. Elle inclut à la fois les efforts d'écriture et de maintenance du code (car vous ne pouvez pas écrire de nouvelles fonctionnalités alors que les anciennes sont endommagées). Un grand nombre des facteurs qui affectent la productivité sont similaires à ceux de "Effort d'apprentissage" - par ex. documentation, communauté, expérience en programmation, etc. - les autres facteurs incluent: +- **Effort d'apprentissage** : L'effort d'apprentissage d'un framework web dépend de votre familiarité avec le langage de programmation sous-jacent, de la cohérence de son API, de la qualité de sa documentation ainsi que de la taille et de l'activité de sa communauté. Si vous avez peu d'eqperience en programmation, pensez à Django (l’un des plus faciles à apprendre en fonction des critères ci-dessus). Si vous faites partie d'une équipe de développement qui possède déjà une expérience significative avec un framework web ou un langage de programmation particulier, il est logique de s'en tenir à cela. +- **Productivité** : la productivité mesure la rapidité avec laquelle vous pouvez créer de nouvelles fonctionnalités une fois que vous êtes familiarisé avec le framework. Elle inclut à la fois les efforts d'écriture et de maintenance du code (car vous ne pouvez pas écrire de nouvelles fonctionnalités alors que les anciennes sont endommagées). Un grand nombre des facteurs qui affectent la productivité sont similaires à ceux de "Effort d'apprentissage" - par ex. documentation, communauté, expérience en programmation, etc. - les autres facteurs incluent: - - *Objectif / origine du framework* : certains frameworks web ont été créés à l'origine pour résoudre certains types de problèmes, et restent meilleurs pour créer des applications web avec des contraintes similaires. Par exemple, Django a été créé pour soutenir le développement d'un site web de journal. Il est donc bon pour les blogs et les autres sites impliquant la publication d'éléments. En revanche, Flask est un cadre beaucoup plus léger et est idéal pour créer des applications web s'exécutant sur des périphériques intégrés. + - *Objectif / origine du framework* : certains frameworks web ont été créés à l'origine pour résoudre certains types de problèmes, et restent meilleurs pour créer des applications web avec des contraintes similaires. Par exemple, Django a été créé pour soutenir le développement d'un site web de journal. Il est donc bon pour les blogs et les autres sites impliquant la publication d'éléments. En revanche, Flask est un cadre beaucoup plus léger et est idéal pour créer des applications web s'exécutant sur des périphériques intégrés. - _Populaire vs Impopulaire_: Un framework populaire est un frameqork dans lequel il est recommandé de "meilleures" manières de résoudre un problème particulier. Les frameworks populaire ont tendance à être plus productifs lorsque vous essayez de résoudre des problèmes courants, car ils vous orientent dans la bonne direction, mais ils sont parfois moins flexibles. - - *Ressource incluses vs. l'obtenir vous-même* : certains frameworks web incluent des outils / bibliothèques qui traitent tous les problèmes que leurs développeurs peuvent penser "par défaut", tandis que des frameworks plus légers attendent des développeurs web qu'ils choisissent la solution aux problèmes de bibliothèques séparées (Django est un exemple inclue " tout " tandis que Flask est un exemple de framework très léger). Les frameworks qui incluent tout sont souvent plus faciles à démarrer car vous avez déjà tout ce dont vous avez besoin, et il est probable qu’il soit bien intégré et bien documenté. Cependant, si une structure plus petite contient tout ce dont vous avez besoin (le cas échéant), elle peut alors s'exécuter dans des environnements plus restreints et disposer d'un sous-ensemble de choses plus petites et plus faciles à apprendre. - - *Si le framework encourage ou non les bonnes pratiques de développement* : par exemple, un cadre qui encourage une architecture Model-View-Controller où on sépare le code en fonctions logiques engendrera un code plus facile à maintenir qu'un code qui n'a pas d'attente de la part des développeurs. De même, la conception de la structure peut avoir un impact important sur la facilité de test et de réutilisation du code. + - *Ressource incluses vs. l'obtenir vous-même* : certains frameworks web incluent des outils / bibliothèques qui traitent tous les problèmes que leurs développeurs peuvent penser "par défaut", tandis que des frameworks plus légers attendent des développeurs web qu'ils choisissent la solution aux problèmes de bibliothèques séparées (Django est un exemple inclue " tout " tandis que Flask est un exemple de framework très léger). Les frameworks qui incluent tout sont souvent plus faciles à démarrer car vous avez déjà tout ce dont vous avez besoin, et il est probable qu’il soit bien intégré et bien documenté. Cependant, si une structure plus petite contient tout ce dont vous avez besoin (le cas échéant), elle peut alors s'exécuter dans des environnements plus restreints et disposer d'un sous-ensemble de choses plus petites et plus faciles à apprendre. + - *Si le framework encourage ou non les bonnes pratiques de développement* : par exemple, un cadre qui encourage une architecture Model-View-Controller où on sépare le code en fonctions logiques engendrera un code plus facile à maintenir qu'un code qui n'a pas d'attente de la part des développeurs. De même, la conception de la structure peut avoir un impact important sur la facilité de test et de réutilisation du code. -- **Performances du framework/langage de programmation** : généralement, la *vitesse* n’est pas le facteur le plus important dans la sélection car même des exécutions relativement lentes, comme Python, sont plus que *suffisantes* pour les sites de taille moyenne fonctionnant avec un matériel raisonnablement performant. Les avantages perçus en termes de vitesse par rapport à  un autre langage comme C++ ou JavaScript peuvent être compensés par les coûts d’apprentissage et de maintenance. -- **Mise en cache** : la popularité de votre site web grandit, vous constatez peut-être que le serveur ne peut plus gérer toutes les requêtes. À ce stade, vous pouvez envisager d’ajouter un support pour la mise en cache : une optimisation dans laquelle vous stockez tout ou partie de la réponse à  une requête web afin qu'il ne soit pas nécessaire de la recalculer la prochaine fois. Retourner la réponse en cache à une requête est beaucoup plus rapide que d'en calculer une. La mise en cache peut être implémentée dans votre code ou sur le serveur (voir proxy inverse). Les infrastructures web auront différents niveaux de prise en charge pour définir le contenu pouvant être mis en cache. -- **Adpatation** : votre site web connaît un succès fantastique, vous avez épuisé les avantages de la mise en cache, vous atteignez même les limites de la mise à l'échelle verticale (exécuter votre application Web sur un matériel plus puissant). À ce stade, il est temps de penser à une mise à l’échelle horizontale (partager la charge en répartissant votre site sur un certain nombre de serveurs web et de bases de données) ou «géographiquement», car certains de vos clients sont très éloignés de votre serveur. L'infrastructure web que vous choisissez peut faire toute la différence en termes de facilité d'adaptation de votre site. -- **Sécurité web** : certains environnements web offrent une meilleure prise en charge de la gestion des attaques web courantes. Django, par exemple, supprime toutes les entrées utilisateur des modèles HTML afin que le code JavaScript saisi par l'utilisateur ne puisse pas être exécuté. D'autres frameworks offrent une protection similaire, mais celle-ci n'est pas toujours activée par défaut. +- **Performances du framework/langage de programmation** : généralement, la *vitesse* n’est pas le facteur le plus important dans la sélection car même des exécutions relativement lentes, comme Python, sont plus que *suffisantes* pour les sites de taille moyenne fonctionnant avec un matériel raisonnablement performant. Les avantages perçus en termes de vitesse par rapport à un autre langage comme C++ ou JavaScript peuvent être compensés par les coûts d’apprentissage et de maintenance. +- **Mise en cache** : la popularité de votre site web grandit, vous constatez peut-être que le serveur ne peut plus gérer toutes les requêtes. À ce stade, vous pouvez envisager d’ajouter un support pour la mise en cache : une optimisation dans laquelle vous stockez tout ou partie de la réponse à une requête web afin qu'il ne soit pas nécessaire de la recalculer la prochaine fois. Retourner la réponse en cache à une requête est beaucoup plus rapide que d'en calculer une. La mise en cache peut être implémentée dans votre code ou sur le serveur (voir proxy inverse). Les infrastructures web auront différents niveaux de prise en charge pour définir le contenu pouvant être mis en cache. +- **Adpatation** : votre site web connaît un succès fantastique, vous avez épuisé les avantages de la mise en cache, vous atteignez même les limites de la mise à l'échelle verticale (exécuter votre application Web sur un matériel plus puissant). À ce stade, il est temps de penser à une mise à l’échelle horizontale (partager la charge en répartissant votre site sur un certain nombre de serveurs web et de bases de données) ou «géographiquement», car certains de vos clients sont très éloignés de votre serveur. L'infrastructure web que vous choisissez peut faire toute la différence en termes de facilité d'adaptation de votre site. +- **Sécurité web** : certains environnements web offrent une meilleure prise en charge de la gestion des attaques web courantes. Django, par exemple, supprime toutes les entrées utilisateur des modèles HTML afin que le code JavaScript saisi par l'utilisateur ne puisse pas être exécuté. D'autres frameworks offrent une protection similaire, mais celle-ci n'est pas toujours activée par défaut. Il existe de nombreux autres facteurs possibles, y compris les licences, que le cadre soit ou non en cours de développement actif, etc. -Si vous débutez en programmation, vous choisirez probablement un framework facile à prendre en main. Une documentation riche et une communauté active sont également des critères pertinents pour votre choix. Pour la suite de ce cours, nous avons choisi Django (Python) et Express (Node/JavaScript) principalement parce que ces frameworks sont faciles à apprendre et bénéficient d'un bon soutien. +Si vous débutez en programmation, vous choisirez probablement un framework facile à prendre en main. Une documentation riche et une communauté active sont également des critères pertinents pour votre choix. Pour la suite de ce cours, nous avons choisi Django (Python) et Express (Node/JavaScript) principalement parce que ces frameworks sont faciles à apprendre et bénéficient d'un bon soutien. > **Note :** Let's go to the main websites for [Django](https://www.djangoproject.com/) (Python) and [Express](http://expressjs.com/) (Node/JavaScript) and check out their documentation and community. > diff --git a/files/fr/learn/server-side/first_steps/website_security/index.md b/files/fr/learn/server-side/first_steps/website_security/index.md index 5ea6284730..abdb71fd01 100644 --- a/files/fr/learn/server-side/first_steps/website_security/index.md +++ b/files/fr/learn/server-side/first_steps/website_security/index.md @@ -11,7 +11,7 @@ original_slug: Learn/Server-side/Premiers_pas/Website_security --- {{LearnSidebar}}{{PreviousMenu("Learn/Server-side/First_steps/Web_frameworks", "Learn/Server-side/Premiers_pas")}} -La sécurité d'un site web exige de la vigilance dans tous les aspects de sa conception et de son utilisation. Cet article d'introduction ne fera pas de vous un gourou de la sécurité des sites web, mais il vous aidera à comprendre d'où viennent les menaces et ce que vous pouvez faire pour renforcer votre application web contre les attaques les plus courantes. +La sécurité d'un site web exige de la vigilance dans tous les aspects de sa conception et de son utilisation. Cet article d'introduction ne fera pas de vous un gourou de la sécurité des sites web, mais il vous aidera à comprendre d'où viennent les menaces et ce que vous pouvez faire pour renforcer votre application web contre les attaques les plus courantes. <table class="standard-table"> <tbody> @@ -34,13 +34,13 @@ La sécurité d'un site web exige de la vigilance dans tous les aspects de sa c Internet est un endroit dangereux ! Fréquemment, nous entendons parler de sites web devenus indisponibles en raison d'attaques par déni de service, ou affichant des informations modifiées (et souvent préjudiciables) sur leurs pages d'accueil. Dans d'autres cas, très médiatisés, des millions de mots de passe, d'adresses électroniques et de détails sur des cartes de crédit sont divulgués au public, exposant les utilisateurs du site web à la fois à l'embarras personnel et au risque de pertes financières. -L'objectif de la sécurité des sites web est de prévenir ces types d'attaques. Plus formellement, la sécurité des sites web est l'acte de protéger les sites web contre l'accès, l'utilisation, la modification, la destruction ou la perturbation non autorisées. +L'objectif de la sécurité des sites web est de prévenir ces types d'attaques. Plus formellement, la sécurité des sites web est l'acte de protéger les sites web contre l'accès, l'utilisation, la modification, la destruction ou la perturbation non autorisées. -La sécurité efficace d'un site web nécessite un effort de conception sur l'ensemble du site : dans votre application web, dans la configuration du serveur web, dans vos politiques de création et de renouvellement des mots de passe et dans le code côté-client. Bien que tout cela semble très inquiétant, la bonne nouvelle est que si vous utilisez un framework web côté serveur, il incluera certainement par défaut des mécanismes de défense solides et bien pensés contre un certain nombre des attaques les plus courantes. D'autres attaques peuvent être atténuées grâce à la configuration de votre serveur web, par exemple en activant HTTPS. Enfin, les outils d'analyse de vulnérabilité accessibles au public peuvent vous aider à découvrir d'éventuelles erreurs dans votre conception. +La sécurité efficace d'un site web nécessite un effort de conception sur l'ensemble du site : dans votre application web, dans la configuration du serveur web, dans vos politiques de création et de renouvellement des mots de passe et dans le code côté-client. Bien que tout cela semble très inquiétant, la bonne nouvelle est que si vous utilisez un framework web côté serveur, il incluera certainement par défaut des mécanismes de défense solides et bien pensés contre un certain nombre des attaques les plus courantes. D'autres attaques peuvent être atténuées grâce à la configuration de votre serveur web, par exemple en activant HTTPS. Enfin, les outils d'analyse de vulnérabilité accessibles au public peuvent vous aider à découvrir d'éventuelles erreurs dans votre conception. Le reste de cet article détaille les menaces les plus courantes qui pèsent sur les sites web et quelques étapes simples pour protèger votre site. -> **Note :** ceci est un article d'introduction, conçu pour vous aider à réflechir à la sécurité de votre site web. Il n'est en rien exhaustif. +> **Note :** ceci est un article d'introduction, conçu pour vous aider à réflechir à la sécurité de votre site web. Il n'est en rien exhaustif. ## Menaces visant la sécurité des sites web @@ -54,7 +54,7 @@ XSS est un terme utilisé pour décrire une classe d'attaque qui permet à l'att Il y a deux manières principales pour demander au site de retourner un script injecté vers un navigateur web — elles sont désignées en tant que vulnérabilités XSS _réfléchie_ et _persistante_. -- Une vulnérabilité XSS réfléchie se produit quand le contenu de l'utilisateur transmis au serveur est immédiatement retourné, sans avoir été modifié, pour être affiché dans le navigateur — tout les scripts présents dans le contenu d'origine seront exécutés quand la nouvelle page sera chargée! +- Une vulnérabilité XSS réfléchie se produit quand le contenu de l'utilisateur transmis au serveur est immédiatement retourné, sans avoir été modifié, pour être affiché dans le navigateur — tout les scripts présents dans le contenu d'origine seront exécutés quand la nouvelle page sera chargée! On prendra par exemple une fonction de recherche dans un site où les termes recherchés sont encodés en tant que paramètres dans l'URL, et que ces termes sont affichés en permanence avec les résultats. Un attaquant peut construire un lien de recherche contenant un script malicieux en tant que paramètre (ex: `http://mysite.com?q=beer<script%20src="http://sitedangereux.com/malicieux.js"></script>`) et le transmettre par e-mail à un autre utilisateur. Si l'utilisateur ciblé clique sur ce "lien intéressant", le script sera exécuté quand les résultats de la recherche seront affichés. Comme vu auparavant, cela donne à l'attaquant toute les informations dont il a besoin pour se connecter sur le site avec le compte de la victime — potentiellement faire des achats en tant que cet utilisateur ou accèder à la liste de contacts.. - Une vulnérabilité XSS _persistante_ sera celle où le script malicieux est stocké sur le site web puis affiché, sans modification, un peu plus tard par les autres utilisateurs et exécuté à leur insu. Par exemple, un écran de discussion qui accepte les commentaires contenant du code HTML pur peuvent stocker le script malicieux de l'attaquant. Quand les commentaires sont affichés le script est exécuté et peut ensuite transmettre à l'attaquant les informations nécessaires pour accèder au compte de l'utilisateur. Cette méthode d'attaque est extrêmement courante et efficace, parce que l'attaquant n'a pas besoin d'avoir une relation directe avec les victimes. @@ -63,7 +63,7 @@ Il y a deux manières principales pour demander au site de retourner un script i La meilleur défense contre les vulnérabilités XSS est de supprimer ou désactiver toutes les balises qui peuvent potentiellement contenir des instructions pour exécuter du code. Pour HTML cela inclut les tags comme `<script>`, `<object>`, `<embed>`, et `<link>`. -Il est nécessaire de traiter les données saisies par l'utilisateur pour être sûr qu'il ne puisse ni exécuter de scripts ni pertuber le fonctionnement normal du site (ce procédé est appelé *input sanitization* en anglais). De nombreux frameworks proposent par défaut cette vérification sur les entrées des formulaires. +Il est nécessaire de traiter les données saisies par l'utilisateur pour être sûr qu'il ne puisse ni exécuter de scripts ni pertuber le fonctionnement normal du site (ce procédé est appelé *input sanitization* en anglais). De nombreux frameworks proposent par défaut cette vérification sur les entrées des formulaires. ### Injection SQL @@ -75,7 +75,7 @@ Cette vulnérabilité est présente quand la saisie de l'utilisateur est transmi statement = "SELECT * FROM users WHERE name = '" + userName + "';" ``` -Si l'utilisateur entre un nom correct cela marchera comme voulu. Cependant un utilisateur malveillant peut changer complètement le sens de cette requête SQL pour obtenir la requête qui suit, simplement en ajoutant le texte, **en gras** ci dessous, en tant que nom, cette requête, modifiée, va créer une requête SQL valide qui va supprimer la table `users` et sélectionner toute les données de la table `userinfo` (révèlant les informations de chaque utilisateur). Tout cela est rendu possible à cause du début du texte injecté (`'a';`) qui va complèter la requête d'origine (`'` est le symbole pour délimiter une chaine de texte en SQL). +Si l'utilisateur entre un nom correct cela marchera comme voulu. Cependant un utilisateur malveillant peut changer complètement le sens de cette requête SQL pour obtenir la requête qui suit, simplement en ajoutant le texte, **en gras** ci dessous, en tant que nom, cette requête, modifiée, va créer une requête SQL valide qui va supprimer la table `users` et sélectionner toute les données de la table `userinfo` (révèlant les informations de chaque utilisateur). Tout cela est rendu possible à cause du début du texte injecté (`'a';`) qui va complèter la requête d'origine (`'` est le symbole pour délimiter une chaine de texte en SQL). ```sql SELECT * FROM users WHERE name = 'a';DROP TABLE users; SELECT * FROM userinfo WHERE 't' = 't'; @@ -132,9 +132,9 @@ La majorité des attaques citées précédement réusissent lorsque l'applicatio Quelques autres points que vous pouvez mettre en place : - Utilisez une politique de gestion des mots de passe efficace. Encouragez les mots de passe solides avec leur renouvellement fréquent. Prenez en compte une authentification à deux facteurs sur votre site (l'utilisateur, en plus du mot de passe, devra fournir un autre code d'authentification généralement fourni par un matériel physique, que seul l'utilisateur possède, comme un code envoyé sur le téléphone par SMS). -- Configurez vos serveurs web pour utiliser [HTTPS](/fr/docs/Glossary/https) et [HTTP Strict Transport Security](/fr/docs/Web/Security/HTTP_strict_transport_security) (HSTS). HTTPS chiffre les données transmises entre le client et le serveur. Cela garantit que les données d'authentification, les cookies, les données transistant avec `POST` et les informations d'en-têtes deviennent moins disponibles pour l'attaquant. +- Configurez vos serveurs web pour utiliser [HTTPS](/fr/docs/Glossary/https) et [HTTP Strict Transport Security](/fr/docs/Web/Security/HTTP_strict_transport_security) (HSTS). HTTPS chiffre les données transmises entre le client et le serveur. Cela garantit que les données d'authentification, les cookies, les données transistant avec `POST` et les informations d'en-têtes deviennent moins disponibles pour l'attaquant. - Tenez vous informé des dernières menaces ([la liste actuelle d'OWASP est ici](/fr/docs/)) et préoccupez vous toujours des vulnerabilités les plus courantes en premier. -- Utilisez [des outils de recherche de vulnérabilités](https://www.owasp.org/index.php/Category:Vulnerability_Scanning_Tools) pour faire automatiquement des recherches de bug sur votre site (votre site pourra également proposer un programme de _buf bounty_ pour déceler des bugs, [comme le fait Mozilla ici](https://www.mozilla.org/en-US/security/bug-bounty/faq-webapp/)). +- Utilisez [des outils de recherche de vulnérabilités](https://www.owasp.org/index.php/Category:Vulnerability_Scanning_Tools) pour faire automatiquement des recherches de bug sur votre site (votre site pourra également proposer un programme de _buf bounty_ pour déceler des bugs, [comme le fait Mozilla ici](https://www.mozilla.org/en-US/security/bug-bounty/faq-webapp/)). - Ne stockez et n'affichez que les données nécessaires. Par exemple, si votre utilisateur doit stocker des données sensibles comme des informations bancaires, affichez seulement ce qui sera suffisant pour être identifié par l'utilisateur, mais pas suffisament pour être copié par un attaquant et être utilisé sur un autre site. La méthode la plus courante en ce moment est de n'afficher que les quatre derniers chiffres du numéro de carte bancaire. Les frameworks web peuvent aider à atténuer beaucoup des vulnérabilités les plus courantes. |