前回の記事では、タスクの編集フォームを作成しました。
前回の記事では、タスクを編集する方法について学びました。 [sitecard subtitle=関連記事 url=https://freemas.stepupkaraoke.com/python/django/making-todol[…]
今回は、これまでの内容から一歩踏み込んで、Ajaxを使用してタスクの完了を実現する方法を解説します。
この記事を読んで取り組むことで、以下を習得することができます。
- Ajaxを使った非同期処理の実装方法
- DjangoとAjaxの連携方法
- データ更新後の画面描画方法
本記事ではDjangoの内容のみならず、Ajaxを中心としたJavaScriptの内容も扱います。
DjangoでWebアプリケーションを作成する際、JavaScriptのようなフロント周りの知識は重要です。
今回はそれらの連携方法を見つつ、簡単な完了フラグの更新を行っていきましょう。
DjangoでTODOリストを作ろう!その5:タスクを完了しよう
今回の内容を実装した後のTODOリストの動き
タスクの完了を実装することで、ユーザーは完了したタスクを一覧から見分けることができます。
以下のように「完了」ボタンを押すことで、背景を暗くして視覚的に分かるのが特徴です。
またボタンを押すことで、ボタンそのものの表示も「未完了にする」に変更しています。
削除のボタンをすでに実装していますが、あえて完了したタスクを残しておきたい場合に便利です。
それでは今回はModel→View→Template→urlsの順に実装し、最後にAjax処理を実装しましょう。
タスク完了のためにModelsに追記する
タスクが完了したかどうかを表すBooleanFieldをTaskモデルに追加します。
models.pyに以下のように追記します。
1 2 3 4 5 |
class Task(models.Model): title = models.CharField(max_length=200) due_date = models.DateTimeField() created_date = models.DateTimeField(auto_now_add=True) is_completed = models.BooleanField(default=False) # 追記 |
is_completedはTrueかFalseの値を持つBooleamFieldとなっています。
完了ならTrue、未完了ならFalseとしています。
またdefaultをFalseに設定することで、マイグレーションしたときに既存のデータのis_completedにFalseを設定しています。
Model変更後はコンソールから「python manage.py makemigrations」と「python manage.py migrate」を忘れずに実行してくださいね。
タスク完了フラグを更新する処理をViewに追記する
続いてviews.pyに、タスクの完了を処理するためのviewを追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
from django.http import JsonResponse #1.完了処理を関数ベースで作成 def complete_task(request, pk): #2.DBからタスクを取得 task = Task.objects.get(pk=pk) #3.完了、未完了を入れ替え if task.is_completed: task.is_completed = False else: task.is_completed = True #4.DB保存 task.save() #5.Ajax送信かどうかで分岐 if request.headers.get('X-Requested-With') == 'XMLHttpRequest': data = { 'is_completed': task.is_completed } #6.JSON形式でデータ送信する return JsonResponse(data) |
以下、詳細です。
このタスク完了処理はこれまでとは違ってclassではなくdefから書き始めています。
これは関数ベースと言われるもので、短い処理に向いています。
今回はこれまでと違ってクラスベースビューを使うわけでもないのでこちらを採用しています。
ちなみにこれまでと同じようにclassから初めても同様の処理を実現することは可能です。
その場合は以下のようになります。
1 2 3 |
class CompleteTaskView(View): def post(self, request, pk): -- 省略 -- |
省略部分は一個前のコードと変わりません。
関数ベースのビューとクラスベースのビューは利点、欠点がありどちらが良いかは状況に応じて異なります。
もし気になる場合は、これらのより詳細な違いを調べてみるのも良いでしょう。
受け取ったpkの値を元に、modelからタスクのデータを取得しています。
pkはユニークになるので、一つのデータが取得されます。
ここの処理は簡単で、取得したis_completedの値に応じて、TrueとFalseを入れ替えています。
このように実装することで未完了→完了はもちろん、完了→未完了に戻すことも可能になります。
is_completedのTrueとFalseを入れ替えることができたので、データベースに保存しています。
データベースの更新が終ったので、ここからは画面で背景を暗くしたり、ボタンの文言を変更する処理に移ります。
上記までの実装では画面を再ロードしないと変更が画面に反映されないため、ここで押した「瞬間に」変更するようにしましょう。
この関数がAjaxで呼び出された場合に、以下のコードでdataという変数に値を渡しています。
最後に上記の変数dataをJson形式でテンプレートに返しています。
これまでは画面遷移をするためにURLを渡したりしていました。
しかし今回のAjaxでのデータのやり取りは画面の遷移が起こらないために、Jsonのデータを渡すのみとなっています。
それでは次にテンプレート側の実装を見てみましょう。
タスク完了を表示するためにテンプレートを編集する
まずはtask_list.htmlの完了ボタンを少し修正します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
{% for task in tasks %} <!-- 1.class名に分岐を追加 --> <tr data-id="{{ task.id }}" {% if task.is_completed %} class="completed"{% endif %}> <td>{{ task.title }}</td> <td>{{ task.due_date|date:"Y/m/d H:i" }}</td> <td class="flex"> <button class="btn btn-primary btn-sm toggle-completed" data-id="{{ task.id }}"> <!-- 2.ボタンの文言も変更する --> {% if task.is_completed %}未完了にする{% else %}完了にする{% endif %} </button> <a href="{% url 'task_edit' task.id %}" class="btn btn-secondary">編集</a> </td> </tr> {% endfor %} |
今回のアプリケーションでは、タスクが完了したかどうかを表すために、タスクモデルにis_completedというBooleanFieldを追加しました。
ここではデータベースの完了フラグか、Json形式で返ってきたデータの完了フラグがTrueの場合に背景を暗くするCSSクラス”completed”を設定しています。
完了ボタンの文言も同様に完了フラグによって表記を変えています。
続いてbase.htmlも追記します。
大きく2つ追記することになります。
①Ajaxを使用するために、jQueryを導入する
JavaScriptを使用してAjaxリクエストを処理するため、base.htmlのhead末尾に以下のようにjQueryのCDNを追加します。
1 |
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> |
②CSSファイルとJSファイルへのリンクを掲載する
次に専用のCSSファイルとJSファイルのリンクを追記します。
base.htmlに直接記載しても良いのですが、記述量が多すぎてコードの見通しが悪くなるために別ファイルにするのがおすすめです。
python-docker-devenvフォルダに「static/css」「static/js」の二つのファイルを作成しましょう。
この中にそれぞれ「todo.css」と「script.js」のファイルを格納します。
この2つのファイルへのリンクをbase.htmlに記載します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<!-- 1.load構文 --> {% load static %} <!-- 追記 --> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>{% block title %} {% endblock %}</title> <!-- Bootstrap CDN --> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous"> <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> <!-- 2.css,jsファイルを取得 --> <link rel="stylesheet" href="{% static 'css/todo.css' %}"> <!-- 追記 --> <script src="{% static 'js/script.js' %}"></script> <!-- 追記 --> </head> |
外部のcssファイルやjsファイルを指定するときにstaticというタグが必要になるのでテンプレートの中でロードしています。
指定しないとエラーになるので注意してください。
cssファイルとjsファイルをstaticタグで取得しています。
これらの設定はsettings.pyにあるのですが、もし自動で作成されていない場合は以下の内容をコピペしてください。
1 2 3 4 5 6 7 8 9 10 |
# Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/3.2/howto/static-files/ STATIC_URL = '/static/' STATICFILES_DIRS = [ BASE_DIR / "static", ] STATIC_ROOT = BASE_DIR / "staticfiles" |
staticファイルはcssやjsファイルの他にも画像を格納することもできます。
これでcssファイルとjsファイルの導入が出来ました。urls.pyに追記後に、それぞれのファイルの中身を追記していきましょう。
タスク完了のためのURLを設定する
次に、タスク完了のためのURLを設定します。urls.pyに以下のようなコードを追加します。
1 2 3 4 5 6 7 8 9 |
from .views import TaskList, TaskCreate,TaskUpdate,TaskDelete,complete_task #追記 urlpatterns = [ path('', TaskList.as_view(), name='task_list'), path('create/', TaskCreate.as_view(), name='task_create'), path('<int:pk>/edit/', TaskUpdate.as_view(), name='task_edit'), path('<int:pk>/delete/', TaskDelete.as_view(), name='task_delete'), path('<int:pk>/complete/', complete_task, name='task_complete'), #追記 ] |
これまでの編集や削除処理と同じURLの形となっています。
IDを渡して更新をする形ですね。
タスク完了用のCSSファイルに記載する
今回のTODOアプリではCSSデザインはbootstrapにほぼ任せているために、記載量は少なめです。
1 2 3 4 5 6 7 |
.completed{ background-color: darkgrey; } .flex{ display:flex; } |
背景を暗くするクラスと、要素を横並びにするクラスを定義しているだけです。
bootstrapで対応できない場合にはこのように個別のCSSを追加することも可能です。
Ajaxによるタスク完了をjsファイルに記載する
最後に、script.jsに非同期通信を行うための処理を実装しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
//完了、未完了ボタンを押したときの動き $(document).ready(function() { $('.toggle-completed').click(function() { //1.タスクID取得 var taskId = $(this).data('id'); //2,Ajax送信処理 $.ajax({ url: '/djangotodo/' + taskId + '/complete/', type: 'POST', dataType: 'json', //3.CSRF処理 beforeSend: function(xhr, settings) { xhr.setRequestHeader('X-CSRFToken', $('input[name="csrfmiddlewaretoken"]').val()); }, //4.Ajax受信処理 success: function(data, textStatus, xhr) { if (data.is_completed) { $('tr[data-id="' + taskId + '"]').addClass('completed'); $('.toggle-completed[data-id="' + taskId + '"]').text('未完了にする'); } else { $('tr[data-id="' + taskId + '"]').removeClass('completed'); $('.toggle-completed[data-id="' + taskId + '"]').text('完了にする'); } }, }); }); }); |
記載量が多いのですが、一つ一つ見ていきましょう。
この一つ上のコードにあるように、「タスク完了」ボタンを押したときに起動します。
そしてこのコードでは「タスク完了」ボタンの「data-id」属性の値を取得しています。
task_list.htmlを見ると分かるのですが、data-idの中にはtask.id、つまりタスクのIDが入っています。
この処理で完了にするタスクのIDをjsファイルが取得できたことになります。
ここからはAjaxの「送信」処理になります。
urlやtype、dataTypeなど分かりやすいですね。HTMLにおけるPOST送信をjavascriptで行っていると思ってください。
Djangoのフォームでデータを送信する場合には{% crsf_token %}をセキュリティの観点から記載しましょうという話でした。
それはAjaxの場合も変わりません。この処理ではそれを行っています。
ここからはAjaxの「受信」処理になります。Viewからデータが返ってきた後の話ですね。
返ってきた後に完了なら未完了に、未完了ならば完了に表示を変えています。
ここでの処理は画面の遷移を行わずにjQueryを用いて文言や背景を変えています。
こうすることでボタンを押した瞬間にデータベースの処理と画面の処理がノータイムで行われたように見え、瞬時の完了、未完了ができたように見えます。
タスク完了が出来るかを確認する
お疲れ様でした。それでは実際に完了、未完了を切り替えられるかをテストしてみましょう。
開発環境に接続して「完了にする」ボタンを押してみてください。
背景が暗くなり、ボタンが「未完了にする」になっていればOKです。
念のためにもう一度押してみて、元に戻るかも確認してみてください!
おわりに
以上で、Djangoを使ってTODOリストアプリのタスク完了機能を実装する方法を解説しました。
今回は、Ajaxを使って非同期通信を行い、ページのリロードを必要とせずにタスクを完了することができるようになりました。
さて前回と同じくここまでのソースはGithubに掲載しておくので、もしも見返したい場合は確認してみてください。
次回は仕上げとして、これまでやってきたことを組み合わせてより高度なタスク更新を実装していきましょう!
前回の記事では、タスク完了ボタンを実装しました。 [sitecard subtitle=関連記事 url=https://freemas.stepupkaraoke.com/python/django/making-todolist-[…]