[PYTHON] Tutoriel Django (Créer une application de blog) ⑥ - Détails de l'article / Fonctions d'édition / suppression

La dernière fois, dans Tutoriel Django (création d'une application de blog) ⑤ - fonction de création d'article Ajout de la possibilité de créer des articles dans l'application.

Maintenant que nous pouvons faire C (Créer) dans CRUD, nous allons ajouter les fonctions restantes (détails, éditer, supprimer).

Nous implémenterons également les fonctions d'affichage, d'édition et de suppression des détails des articles dans la vue générale basée sur les classes.

Réparer urls.py

Dans les fonctions à inclure désormais, vous accéderez et éditerez en fonction de la clé unique de chaque article. La valeur de la clé est la ** clé primaire ** de chaque article.

La fonction d'affichage générique basée sur les classes de Django vous permet de configurer le routage dans urls.py. Vous pouvez spécifier le modèle d'URL avec la chaîne de clé primaire ** <int: pk> ** pour chaque article.

Par exemple, dans la fonction d'affichage détaillée, définissez simplement le modèle d'URL "blog / post_detail / <int: pk>" L'article créé peut être identifié par la clé primaire et acheminé vers View, qui contrôle les détails, l'édition et la suppression.

Définissez le nom de la classe de vue avec les noms suivants.

--Détails: PostDetailView --Modifier: PostUpdateView --Supprimer: PostDeleteView

À ce stade, urls.py ressemble à ceci:

urls.py


from django.urls import path
from . import views

app_name = 'blog'

urlpatterns = [
    path('', views.IndexView.as_view(), name='index'),
    path('post_list', views.PostListView.as_view(), name='post_list'),
    path('post_create', views.PostCreateView.as_view(), name='post_create'),
    path('post_detail/<int:pk>/', views.PostDetailView.as_view(), name='post_detail'), #ajouter à(Exemple) /blog/detail/1 * Le traitement étant effectué sur un enregistrement spécifique, il peut être identifié par pk.
    path('post_update/<int:pk>/', views.PostUpdateView.as_view(), name='post_update'), #ajouter à(Exemple) /blog/update/1 * Identique à ci-dessus
    path('post_delete/<int:pk>/', views.PostDeleteView.as_view(), name='post_delete'), #ajouter à(Exemple) /blog/delete/1 * Identique à ci-dessus
]

Vous pouvez voir que les détails, la mise à jour et la suppression dans chaque article sont spécifiés par des clés primaires séparées par des barres obliques. Désormais, par exemple, les détails du tout premier article sont accessibles à l'adresse / blog / post_detail / 1 /. (Si vous êtes un hébergeur local, vous pouvez y accéder depuis votre navigateur avec l'URL ** 127.0.0.1:8000/blog/post_detail/1**)

Modifier views.py

Ensuite, nous créerons chaque vue. Je vais d'abord vous montrer le formulaire rempli.

views.py


...
class PostDetailView(generic.DetailView): #ajouter à
    model = Post  # pk(primary key)Est des URL.Puisqu'il est spécifié par py, il vous suffit d'appeler model ici
    
class PostUpdateView(generic.UpdateView): #ajouter à
    model = Post
    form_class = PostCreateForm #PostCreateForm peut être utilisé presque tel quel
    success_url = reverse_lazy('blog:post_detail')
class PostDeleteView(generic.DeleteView): #ajouter à
    model = Post
    success_url = reverse_lazy('blog:post_list')

Explication de PostDetailView

Vous pourriez être surpris par la petite quantité de code. Vous n'avez rien à écrire sur la clé primaire en particulier, et grâce à la puissance de la vue générique, vous pouvez créer une page de détail avec juste cela. De plus, si vous attribuez au modèle le nom de fichier ** post_detail.html **, il sera automatiquement identifié.

PostUpdateView En fait, UpdateView est très similaire à CreateView car vous pouvez utiliser le même formulaire que lorsque vous en avez créé un nouveau.

views.Explication de py


...
class PostCreateView(generic.CreateView):
    model = Post
    form_class = PostCreateForm
    success_url = reverse_lazy('blog:post_list')
...

La seule différence est que success_url au moment de la modification réussie est utilisé comme écran des détails de l'article dans UpdateView. Si c'est aussi le même que la liste d'articles (post_list), le contenu peut être exactement le même manteau.

De plus, en fait, ** post_form.html ** utilisé dans PostCreate peut être utilisé tel quel, donc Il n'est pas nécessaire de préparer un nouveau modèle.

Explication de PostDetailView

La suppression n'utilise pas de formulaire d'entrée, il n'est donc pas nécessaire d'appeler le formulaire. Cependant, il y a deux choses à noter qui sont différentes de Update.

La première est que le nom du modèle est ** post_confirm_delete.html **. D'après le flux jusqu'à présent, il semble que ce sera post_delete.html, La définition du nom est légèrement différente car il n'est pas supprimé soudainement même si vous y accédez.

L'autre est la destination de la redirection lorsque la suppression est réussie. Dans UpdateView, l'écran de liste ou l'écran de détail fonctionnait bien. Attention à ne pas rediriger vers l'écran des détails (post_detail), car sa suppression éliminera les articles auxquels vous souhaitez accéder.

Préparation du modèle

Ensuite, comme je l'ai mentionné un peu dans views.py, créez un modèle (html) pour les détails et la suppression sous template / blog /.

└── templates
    └── blog
        ├── index.html
        ├── post_confirm_delete.html #ajouter à
        ├── post_detail.html #ajouter à
        ├── post_form.html
        └── post_list.html

Maintenant, éditons d'abord ** post_detail.html **.

post_detail.html


<table>
    <tr>
      <th>Titre</th>
      <td>{{ post.title }}</td>
    </tr>
    <tr>
      <th>Texte</th>
      <!--Si vous insérez linebreaksbk, il s'affichera correctement avec une balise de saut de ligne.-->
      <td>{{ post.text | linebreaksbr}}</td>
    </tr>
    <tr>
      <th>Date</th>
      <td>{{ post.date }}</td>
    </tr>
</table>

La variable ** post ** est reçue du côté du modèle et affichée sous la forme {{post.title}} & {{post.text}} & {{post.date}}. Si vous insérez "| linebreaksbr" après post.text, même les articles longs tels que le corps de l'article seront automatiquement interrompus.

Puis éditez ** post_confirm_delete.html **.

post_confirm_delete.html


<form action="" method="POST">
  <table>
      <tr>
        <th>Titre</th>
        <td>{{ post.title }}</td>
      </tr>
      <tr>
        <th>Texte</th>
        <td>{{ post.text }}</td>
      </tr>
      <tr>
        <th>Date</th>
        <td>{{ post.date }}</td>
      </tr>
  </table>
  <p>Supprimez ces données.</p>
  <button type="submit">Envoyer</button>
  {% csrf_token %}
</form>

La première ligne utilise la méthode POST de suppression d'articles, le formulaire spécifie donc la méthode POST. Étant donné que le traitement tel que la destination POST est ajusté du côté de la vue, il n'est pas nécessaire de le décrire du côté du modèle. Enfin, veillez à écrire {% csrf_token%} dans les mesures CSRF.

Ceci termine l'édition du modèle.

Créer un test unitaire

Ajouter d'abord

test_views.py


...
class PostDetailTests(TestCase): #ajouter à
    """Classe de test PostDetailView"""

    def test_not_fount_pk_get(self):
        """Confirmez que 404 est renvoyé lors de l'accès avec la clé primaire d'un article qui n'existe pas à l'état vide sans enregistrer l'article"""
        response = self.client.get(
            reverse('blog:post_detail', kwargs={'pk': 1}),
        )
        self.assertEqual(response.status_code, 404)

    def test_get(self):
        """Confirmez que le code d'état 200 est renvoyé en accédant avec la méthode GET."""
        post = Post.objects.create(title='test_title', text='test_text')
        response = self.client.get(
            reverse('blog:post_detail', kwargs={'pk': post.pk}),
        )
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, post.title)
        self.assertContains(response, post.text)

class PostUpdateTests(TestCase): #ajouter à
    """Classe de test PostUpdateView"""

    def test_not_fount_pk_get(self):
        """Confirmez que 404 est renvoyé lors de l'accès avec la clé primaire d'un article qui n'existe pas à l'état vide sans enregistrer l'article"""
        response = self.client.get(
            reverse('blog:post_update', kwargs={'pk': 1}),
        )
        self.assertEqual(response.status_code, 404)

    def test_get(self):
        """Confirmez que le code d'état 200 est renvoyé en accédant avec la méthode GET."""
        post = Post.objects.create(title='test_title', text='test_text')
        response = self.client.get(
            reverse('blog:post_update', kwargs={'pk': post.pk}),
        )
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, post.title)
        self.assertContains(response, post.text)

class PostDeleteTests(TestCase): #ajouter à
    """Classe de test PostDeleteView"""

    def test_not_fount_pk_get(self):
        """Confirmez que 404 est renvoyé lors de l'accès avec la clé primaire d'un article qui n'existe pas à l'état vide sans enregistrer l'article"""
        response = self.client.get(
            reverse('blog:post_delete', kwargs={'pk': 1}),
        )
        self.assertEqual(response.status_code, 404)

    def test_get(self):
        """Confirmez que le code d'état 200 est renvoyé en accédant avec la méthode GET."""
        post = Post.objects.create(title='test_title', text='test_text')
        response = self.client.get(
            reverse('blog:post_delete', kwargs={'pk': post.pk}),
        )
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, post.title)
        self.assertContains(response, post.text)

Confirmer que 200 est renvoyé par la méthode GET n'est pas très différent du test unitaire précédent, Le processus d'obtention d'une réponse pour un article qui devrait exister est très différent.

...
def test_not_fount_pk_get(self):
    """Confirmez que 404 est renvoyé lors de l'accès avec la clé primaire d'un article qui n'existe pas à l'état vide sans enregistrer l'article"""
    response = self.client.get(
        reverse('blog:post_delete', kwargs={'pk': 1}),
    )
    self.assertEqual(response.status_code, 404)
...

J'ai déjà mentionné que vous pouvez enregistrer des données dans la base de données (créer un article ici) dans le test unitaire. Cependant, si vous essayez d'accéder à l'écran détails / éditer / supprimer de l'article avec la clé primaire 1 = le premier article lorsque l'article n'existe pas Bien sûr, la page n'existe pas, elle renvoie donc un code d'état HTTP de 404, ce qui indique que la page n'existe pas.

Dans la méthode de test ci-dessus, même si vous osez accéder à la page individuelle de l'article qui n'existe pas et l'exploiter Je confirme qu'une erreur est renvoyée.

Maintenant, lorsque vous exécutez le test unitaire, il devrait réussir sans erreur.

(blog) bash-3.2$ python3 manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
.................
----------------------------------------------------------------------
Ran 17 tests in 0.460s

OK
Destroying test database for alias 'default'...

Maintenant que vous avez ajouté avec succès la fonctionnalité CRUD, c'est une fonctionnalité importante.

Ensuite, modifions le modèle et affichons-le sur toutes les pages en ajoutant une barre de navigation commune à tous les écrans.

→ La prochaine fois Tutoriel Django (Créer une application de blog) ⑦ --Front End terminé

Recommended Posts

Tutoriel Django (Créer une application de blog) ⑥ - Détails de l'article / Fonctions d'édition / suppression
Tutoriel Django (Créer une application de blog) ⑤ --Fonction de création d'article
Tutoriel Django (Créer une application de blog) ③ --Affichage de la liste d'articles
Tutoriel Django (Créer une application de blog) ④ --Test d'unité
Tutoriel Django (Créer une application de blog) ① --Préparation, Création de la première page
Tutoriel Django (Créer une application de blog) ⑦ --Front End Complete
Tutoriel Django (création d'applications de blog) ② - création de modèles, préparation de site de gestion
Créer une application Todo avec Django ④ Implémenter la fonction de création de dossier et de tâche