python

Django入門その5(ToDoアプリ)

前回はCRUD操作のREADとBootstrapの導入を行いました。
今回は残りのCRUD操作を実装してToDoアプリを完成させます。

Create(新規作成)

Createを行う際は、CreateViewテンプレートを使用していきます。ListViewなどと使い方は一緒です。特徴はデータを新しく作成する時に適したテンプレートです。

ここからコードを実装しながら中身をみていきます。
まず、表示する画面を作成したいので、create.htmlファイルを作っていきましょう。
作成後は、todoディレクトリurls.pyを変更して繋ぎ込みを行っていきます。

./
├── db.sqlite3
├── manage.py
├── templates
│ ├── base.html
│ ├── create.html
│ ├── detail.html
│ └── list.html
├── todo
│ ├── init.py
│ ├── pycache
│ │ ├── init.cpython-37.pyc
│ │ ├── admin.cpython-37.pyc
│ │ ├── models.cpython-37.pyc
│ │ ├── urls.cpython-37.pyc
│ │ └── views.cpython-37.pyc
│ ├── admin.py
│ ├── apps.py
│ ├── migrations
│ │ ├── 0001_initial.py
│ │ ├── 0002_auto_20200418_1317.py
│ │ ├── init.py
│ │ └── pycache
│ │ ├── 0001_initial.cpython-37.pyc
│ │ ├── 0002_auto_20200418_1317.cpython-37.pyc
│ │ └── init.cpython-37.pyc
│ ├── models.py
│ ├── tests.py
│ ├── urls.py
│ └── views.py
└── todoproject
├── init.py
├── pycache
│ ├── init.cpython-37.pyc
│ ├── settings.cpython-37.pyc
│ ├── urls.cpython-37.pyc
│ └── wsgi.cpython-37.pyc
├── asgi.py
├── settings.py
├── urls.py
└── wsgi.py

from django.urls import path
from .views import TodoList, TodoDetail, TodoCreate

urlpatterns = [
    path('list/', TodoList.as_view()),
    path('detail/<int:pk>', TodoDetail.as_view()),
    path('create/', TodoCreate.as_view()),
]

続いて、urls.pyに記載したTodoCreateクラスをviews.pyに実装していきます。

from django.shortcuts import render
from django.views.generic import ListView, DetailView, CreateView
from .models import TodoModel
from django.urls import reverse_lazy

class TodoList(ListView):
  template_name = 'list.html'
  model = TodoModel

class TodoDetail(DetailView):
  template_name = 'detail.html'
  model = TodoModel

class TodoCreate(CreateView):
  template_name = 'create.html'
  model = TodoModel
  # 新規に書き込める項目を指定
  fields = ('title', 'memo', 'priority', 'duedate')
  # データ作成後のリダイレクト先を指定
  success_url = reverse_lazy('list')

データ作成後にリダイレクト先に飛ばす方法として、reverse_lazyを使用しています。
ただ、今回はクラスで定義しているからreverse_lazyを使用して問題ないですが、関数で定義している場合は、reverseメソッドを使用してあげる必要があります。
これはdjangoの仕様なので、覚えるしかありません。
リダイレクト先の指定の仕方が少し独特で、urls.pyの内容を変更してreverse_lazyからurlを呼び出せるようにします。その為の変更が下記になります。

from django.urls import path
from .views import TodoList, TodoDetail, TodoCreate

urlpatterns = [
    path('list/', TodoList.as_view(), name='list'),
    path('detail/<int:pk>', TodoDetail.as_view(), name='detail'),
    path('create/', TodoCreate.as_view(), name='create'),
]

これで、views.pyのreverse_lazy(‘list’)とリダイレクト先のurlが紐づきました。

そして、はじめに作成したcreate.htmlを作り込んでいきます。

{% extends 'base.html' %}

{% block content %}
<form action="" method="POST">{% csrf_token %}
  {{ form.as_p }}
  <input type="submit" class="btn btn-primary" value="作成する">
</form>
{% endblock content %}

{% csrf_token %}はdjangoで用意してくれているCSRF対策になります。
基本的にformを扱う際は必須なるので、つけ忘れないように注意してください。
{{ form.as_p }}はviews.pyで指定したDBのテーブルに書き込む項目を取得してpタグで自動的にhtml表示してくれます。
その他はHTMLの内容と一緒です。

ここまでできたら、サーバーを立ち上げて、データを入力してみましょう

データが入力されて、http://localhost:8000/list/ にリダイレクトされることが確認できるはずです。

Delete(削除)

CreateやReadの時と同様に、djangoがデータを削除するときに便利なテンプレートを用意しておいてくれています。
それが、DeleteViewです。
早速コードを実装していきます。
最初は、Createの時と同じで、delete.htmlを作成して、urls.pyと繋ぎ込みをします。

from django.urls import path
from .views import TodoList, TodoDetail, TodoCreate, TodoDelete

urlpatterns = [
    path('list/', TodoList.as_view(), name='list'),
    path('detail/<int:pk>', TodoDetail.as_view(), name='detail'),
    path('create/', TodoCreate.as_view(), name='create'),
    path('delete/<int:pk>', TodoDelete.as_view(), name='delete'),
]

次に、view.pyにdeleteのクラスを書いていきます。

from django.shortcuts import render
from django.views.generic import ListView, DetailView, CreateView, DeleteView
from .models import TodoModel
from django.urls import reverse_lazy

class TodoList(ListView):
  template_name = 'list.html'
  model = TodoModel

class TodoDetail(DetailView):
  template_name = 'detail.html'
  model = TodoModel

class TodoCreate(CreateView):
  template_name = 'create.html'
  model = TodoModel
  # 新規に書き込める項目を指定
  fields = ('title', 'memo', 'priority', 'duedate')
  # データ作成後のリダイレクト先を指定
  success_url = reverse_lazy('list')

class TodoDelete(DeleteView):
  template_name = 'delete.html'
  model = TodoModel
  success_url = reverse_lazy('list')

ここまでは似たような作業が続いてるので、慣れてきたと思います。
データを削除後も元の一覧画面に戻したいので、リダイレクト処理を書きます。
次にやることも予測がついてるかもしれませんが、delete.htmlを書き込んでいきます。

{% extends 'base.html' %}

{% block content %}
<form action="" method="POST">{% csrf_token %}
  <input type="submit" value="削除します">
</form>
{% endblock content %}

ここまでできたら、サーバを立ち上げて、http://localhost:8000/delete/1 などにアクセスして、削除ボタンが表示されるか確認して、実行後削除されているか確認してください。

これで削除機能の実装が完了しました。

Update(更新)

Updateもほとんど同じなので、自分で実装できる方はやってみてください。
テンプレートはUpdateViewを使用します。
それでは、update.htmlの作成とurls.pyの変更からはじめます。

from django.urls import path
from .views import TodoList, TodoDetail, TodoCreate, TodoDelete, TodoUpdate

urlpatterns = [
    path('list/', TodoList.as_view(), name='list'),
    path('detail/<int:pk>', TodoDetail.as_view(), name='detail'),
    path('create/', TodoCreate.as_view(), name='create'),
    path('delete/<int:pk>', TodoDelete.as_view(), name='delete'),
    path('update/<int:pk>', TodoUpdate.as_view(), name='update'),
]

続いて、views.pyの変更

from django.shortcuts import render
from django.views.generic import ListView, DetailView, CreateView, DeleteView, UpdateView
from .models import TodoModel
from django.urls import reverse_lazy

class TodoList(ListView):
  template_name = 'list.html'
  model = TodoModel

class TodoDetail(DetailView):
  template_name = 'detail.html'
  model = TodoModel

class TodoCreate(CreateView):
  template_name = 'create.html'
  model = TodoModel
  # 新規に書き込める項目を指定
  fields = ('title', 'memo', 'priority', 'duedate')
  # データ作成後のリダイレクト先を指定
  success_url = reverse_lazy('list')

class TodoDelete(DeleteView):
  template_name = 'delete.html'
  model = TodoModel
  success_url = reverse_lazy('list')

class TodoUpdate(UpdateView):
  template_name = 'update.html'
  model = TodoModel
  fields = ('title', 'memo', 'priority')
  success_url = reverse_lazy('list')

update.htmlも基本的に一緒なのですが、唯一違うのフォームの中身にデータの内容を反映させてあげる程度です。

{% extends 'base.html' %}

{% block content %}
<form action="" method="POST">{% csrf_token %}
  <div>{{ form.title }}</div>
  <div>{{ form.memo }}</div>
  <div>{{ form.priority }}</div>
  <input type="submit" class="btn btn-primary" value="更新する">
</form>
{% endblock content %}

ここまでできたら、サーバーを立ち上げて確認してください。先程idが1のデータを削除してしまったので、http://localhost:8000/update/2 にアクセスしてフォームに出たの内容が反映されている点と、更新ができるかを確認します。

ここまででCRUDの全操作が完了しました。

最後に、今のままだとURLを直叩きしないと行けないので、編集のボタンなどにリンクを貼る作業をしていきます。

リンクの繋ぎ込み

list.htmlのボタンリンクを修正します。

{% extends 'base.html' %}

{% block header %}
<div class="jumbotron jumbotron-fluid">
  <div class="container">
    <h1 class="display-4">ToDoリスト</h1>
    <p class="lead">ToDoリストを管理することで生産性をアップしていきます。</p>
  </div>
</div>
<h1></h1>
{% endblock header %}

{% block content %}
<div class='container'>
{% for item in object_list %}
<div class="alert alert-{{ item.priority }}" role="alert">
  <p>{{ item.title }}<br> ー {{ item.memo }}</p>
  <a class="btn btn-primary" href="{% url 'detail' item.pk %}" role="button">詳細</a>
  <a class="btn btn-success" href="{% url 'update' item.pk %}" role="button">編集</a>
  <a class="btn btn-danger" href="{% url 'delete' item.pk %}" role="button">削除</a>
</div>
{% endfor %}
</div>
{% endblock content %}

変更箇所は、href=”{% url ‘detail’ item.pk %}”の部分です。使い方はreverse_lazyと似ています。どのURLに飛ばすのかは、urls.pyで設定したnameの値を使います。更に、どのデータかを特定するために、DBに入ってるidを利用しています。

以上で、リンクの繋ぎ込みが完了しました。

おまけ

新規作成する為のボタンが抜けていたので、追記しました。

{% extends 'base.html' %}

{% block header %}
<div class="jumbotron jumbotron-fluid">
  <div class="container">
    <h1 class="display-4">ToDoリスト</h1>
    <p class="lead">ToDoリストを管理することで生産性をアップしていきます。</p>
  </div>
</div>
<h1></h1>
{% endblock header %}

{% block content %}
<div class='container'>
{% for item in object_list %}
<div class="alert alert-{{ item.priority }}" role="alert">
  <p>{{ item.title }}<br> ー {{ item.memo }}</p>
  <a class="btn btn-primary" href="{% url 'detail' item.pk %}" role="button">詳細</a>
  <a class="btn btn-success" href="{% url 'update' item.pk %}" role="button">編集</a>
  <a class="btn btn-danger" href="{% url 'delete' item.pk %}" role="button">削除</a>
</div>
{% endfor %}
<a class="btn btn-info" href="{% url 'create' %}" role="button">新規作成</a>
</div>
{% endblock content %}