[PYTHON] Django Tutorial (Blog App Creation) ⑥ --Article Details / Editing / Deleting Functions

Last time, in Django Tutorial (Blog App Creation) ⑤ --Article Creation Function Added the ability to create articles within the app.

This time, C (Create) in CRUD is possible, so we will add the remaining functions (details, edit, delete).

We will also implement the detailed view, edit, and delete functions of articles in the class-based general-purpose view.

Fix urls.py

In the functions to be included in the future, we will access and edit based on the unique key of each article. The key value is the ** primary key ** for each article.

Django's class-based generic view feature allows you to configure routing in urls.py. You can specify the URL pattern with the string ** <int: pk> **, the primary key for each article.

For example, in the detailed display function, just set the URL pattern'blog / post_detail / <int: pk>' The created article can be identified by the primary key and routed to View, which controls details, editing, and deletion.

Set the name of each view class with the following name.

--Details: PostDetailView --Edit: PostUpdateView --Delete: PostDeleteView

At this time, urls.py will be as follows.

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'), #add to(Example) /blog/detail/1 * Since processing is performed on a specific record, it is identified by pk.
    path('post_update/<int:pk>/', views.PostUpdateView.as_view(), name='post_update'), #add to(Example) /blog/update/1 * Same as above
    path('post_delete/<int:pk>/', views.PostDeleteView.as_view(), name='post_delete'), #add to(Example) /blog/delete/1 * Same as above
]

You can see that detail, update, and delete in each article are specified by slash-separated primary keys. Now, for example, the details of the very first article can be accessed at / blog / post_detail / 1 /. (If you are on localhost, you can access it from your browser with the URL ** 127.0.0.1:8000/blog/post_detail/1**)

Modify views.py

Next, we will create each view. I will show you the completed form first.

views.py


...
class PostDetailView(generic.DetailView): #add to
    model = Post  # pk(primary key)Is urls.Since it is specified by py, you only need to call model here
    
class PostUpdateView(generic.UpdateView): #add to
    model = Post
    form_class = PostCreateForm #PostCreateForm can be used almost as it is
    success_url = reverse_lazy('blog:post_detail')
class PostDeleteView(generic.DeleteView): #add to
    model = Post
    success_url = reverse_lazy('blog:post_list')

Explanation of PostDetailView

You might be surprised at the small amount of code. You don't have to write anything about the primary key in particular, and thanks to the power of generic views, you can create a detail page with just this. Also, if you give the template a file name of ** post_detail.html **, it will be automatically identified.

PostUpdateView In fact, UpdateView is very similar to CreateView because it allows you to use the same form as when you created a new one.

views.Explanation of py


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

The only difference is that the success_url at the time of successful editing is used as the article details screen in UpdateView. If this is also the same as the article list (post_list), the contents can be exactly the same coat.

Also, in fact, ** post_form.html ** used in PostCreate can be used as it is, so There is no need to prepare a new template.

Explanation of PostDetailView

The delete does not use the input form, so there is no need to call the form. However, there are two things to note that are different from Update.

One is that the template name is ** post_confirm_delete.html **. From the flow so far, it seems that it will be post_delete.html, but The definition of the name is slightly different because it is not deleted suddenly even if you access it.

The other is the redirect destination when the deletion is successful. In UpdateView, either the list screen or the detail screen was fine. Be careful not to redirect to the detail screen (post_detail), as deleting it will eliminate the articles you want to access.

Template preparation

Then, as I mentioned a little in views.py, create a template (html) for details and deletion under template / blog /.

└── templates
    └── blog
        ├── index.html
        ├── post_confirm_delete.html #add to
        ├── post_detail.html #add to
        ├── post_form.html
        └── post_list.html

Now, let's edit ** post_detail.html ** first.

post_detail.html


<table>
    <tr>
      <th>title</th>
      <td>{{ post.title }}</td>
    </tr>
    <tr>
      <th>Text</th>
      <!--If you insert linebreaksbk, it will be displayed properly with a line break tag.-->
      <td>{{ post.text | linebreaksbr}}</td>
    </tr>
    <tr>
      <th>date</th>
      <td>{{ post.date }}</td>
    </tr>
</table>

The variable ** post ** is received on the template side and displayed as {{post.title}} & {{post.text}} & {{post.date}}. If you insert "| linebreaksbr" after post.text, even long articles such as the body of the article will be automatically broken.

Then edit ** post_confirm_delete.html **.

post_confirm_delete.html


<form action="" method="POST">
  <table>
      <tr>
        <th>title</th>
        <td>{{ post.title }}</td>
      </tr>
      <tr>
        <th>Text</th>
        <td>{{ post.text }}</td>
      </tr>
      <tr>
        <th>date</th>
        <td>{{ post.date }}</td>
      </tr>
  </table>
  <p>Delete this data.</p>
  <button type="submit">Send</button>
  {% csrf_token %}
</form>

The first line uses the POST method of deleting articles, so the form specifies the POST method. Since the processing such as the POST destination is adjusted on the view side, it is not necessary to describe it on the template side. Finally, be careful to write {% csrf_token%} in the CSRF measures.

This completes the template editing.

Creating unit tests

Add first

test_views.py


...
class PostDetailTests(TestCase): #add to
    """PostDetailView test class"""

    def test_not_fount_pk_get(self):
        """Confirm that 404 is returned when accessing with the primary key of an article that does not exist in the empty state without registering the article"""
        response = self.client.get(
            reverse('blog:post_detail', kwargs={'pk': 1}),
        )
        self.assertEqual(response.status_code, 404)

    def test_get(self):
        """Confirm that it is accessed by the GET method and status code 200 is returned."""
        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): #add to
    """PostUpdateView test class"""

    def test_not_fount_pk_get(self):
        """Confirm that 404 is returned when accessing with the primary key of an article that does not exist in the empty state without registering the article"""
        response = self.client.get(
            reverse('blog:post_update', kwargs={'pk': 1}),
        )
        self.assertEqual(response.status_code, 404)

    def test_get(self):
        """Confirm that it is accessed by the GET method and status code 200 is returned."""
        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): #add to
    """PostDeleteView test class"""

    def test_not_fount_pk_get(self):
        """Confirm that 404 is returned when accessing with the primary key of an article that does not exist in the empty state without registering the article"""
        response = self.client.get(
            reverse('blog:post_delete', kwargs={'pk': 1}),
        )
        self.assertEqual(response.status_code, 404)

    def test_get(self):
        """Confirm that it is accessed by the GET method and status code 200 is returned."""
        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)

Confirming that the GET method returns 200 is not much different from previous unit tests, but The process of getting a reponse for an article that should exist is very different.

...
def test_not_fount_pk_get(self):
    """Confirm that 404 is returned when accessing with the primary key of an article that does not exist in the empty state without registering the article"""
    response = self.client.get(
        reverse('blog:post_delete', kwargs={'pk': 1}),
    )
    self.assertEqual(response.status_code, 404)
...

I've already mentioned that you can register data in the database (create an article here) in a unit test. However, when trying to access the details / edit / delete screen of the article with primary key 1 = the first article when the article does not exist Of course, the page doesn't exist, so it returns an HTTP status code of 404, which indicates that the page doesn't exist.

In the above test method, even if you dare to access the individual page of the article that does not exist and operate it I'm checking that an error is returned.

Now when you run the unit test, it should pass without error.

(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'...

Now that you've successfully added the CRUD functionality, it's a big feature.

Next, let's modify the template and display it on all pages by adding a common navigation bar to all screens.

→ Next time Django Tutorial (Blog App Creation) ⑦ --Front End Completed

Recommended Posts

Django Tutorial (Blog App Creation) ⑥ --Article Details / Editing / Deleting Functions
Django Tutorial (Blog App Creation) ⑤ --Article Creation Function
Django Tutorial (Blog App Creation) ③ --Article List Display
Django Tutorial (Blog App Creation) ④ --Unit Test
Django Tutorial (Blog App Creation) ① --Preparation, Top Page Creation
Django Tutorial (Blog App Creation) ⑦ --Front End Complete
Django tutorial (blog application creation) ② --model creation, management site preparation
Create a Todo app with Django ④ Implement folder and task creation functions