【Django入門】templateとHTMLを使ったWebページでclassを追加/削除する

Djangoでtodoタスク管理アプリを作成する中で、タスクが完了済かどうか視覚的に分かるような実装をしたくなりました。

ここではcheckboxをクリックした時、あるいはタイトルをクリックした時に、完了フラグを反転させ、同時に特定のclass名を追加/削除して、完了したなら取り消し線が付くようにしたいと思います。

基本的なCRUD処理は実装済として説明は省略します。ここで大事なのは、一覧表示させている"templates/show.html"に実装したとトグル操作などになります。

エンドポイント

urls.py

from django.contrib import admin
from django.urls import path
from . import views 


urlpatterns = [
    path('',views.index, name='index'),
    path('show/', views.show, name='show'),
    path('create/', views.create, name='create'),
    path('<uuid:id>/update/', views.update, name='update'),
    path('<uuid:id>/delete/', views.delete, name='delete'),
    path('<uuid:id>/toggle/', views.toggle, name="toggle"),

]

注目箇所

"show"は一覧表示させているだけですので、ここでは"toggle"の箇所のみの説明になります。

モデルの定義

カラムにはタスク内容を記載する"title"と、完了したかどうかのフラグである"is_completed"を設けます。

models.py

from django.db import models
from uuid import uuid4 as uid

# Create your models here.
class Todo(models.Model):
    id = models.UUIDField(primary_key=True, default=uid, editable=False, verbose_name="ID")
    title = models.CharField(max_length=255, verbose_name="タイトル")
    is_completed = models.BooleanField(default=False, verbose_name="完了")
    created_at = models.DateTimeField(auto_now_add=True, verbose_name="作成日時")
    updated_at = models.DateTimeField(auto_now=True, verbose_name="更新日時")

    def __str__(self):
        return self.title

トグルの反転処理

"toggle"処理では"is_completed"をnotで反転させます。
javascript言語で良く使う反転演算子"!"は使えません。

views.py

from django.shortcuts import render, redirect, get_object_or_404
from django.views import View
from .models import Todo
from .forms import TodoForm

#(中略)

# toggle
class ToggleView(View):
    def get(self, request, id):
        todo = get_object_or_404(Todo, id=id)
        todo.is_completed = not todo.is_completed
        todo.save()
        return redirect('show')
    
toggle = ToggleView.as_view()

djangoのyesnoフイルター

  • checkboxでは、<input type="checkbox" {% if todo.is_completed %} checked {% endif %} >でtodo.is_completedがtrueであればchecked属性が付くように設定します。
  • クラス名の変更は、3項演算子が使えば便利です。しかし、djangoのテンプレートはpython言語と関係ありませんので、djangoのyesnoフイルターを使います。
  • yesnoフィルターは次のように使います。
    {{ value|yesno:'trueの場合の値, falseの場合の値'}}
  • 従って、class="mytitle {{ todo.is_completed | yesno:'isdone,' }}"
    すれば、is_completedがTrueであればclass名にisdoneが付き、Falseであれば何も付かないことになります。
  • タイトルをクリックするとトグルするように実装していますが、checkboxをクリックしても同じ様にトグルするようにJavascriptのDOM操作で行っています。もちろんどちらか片方だけでもOKかと思います。

base.htmlとshow.html

base.html


<!DOCTYPE html>
<html lang="ja">
    {% load static %}
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Task MA</title>
    <link rel="stylesheet" href="{% static 'css/style.css' %}">
</head>
<body>
    <div class="container">
        <h2 >タスク管理 </h2>
        <hr>
        {% block content %}
        {% endblock  %}
    </div>
    <footer><small>2024/11 Todo管理アプリ ...Django ver 4.2.16</small></footer>
    {% block myscript %}
    {% endblock myscript %}
</body>
</html>


※ここではJavascriptを別ファイルとせずに、htmlファイルの中
で実装しています。

show.html (base.htmlを継承)

{% extends "base.html" %}

{% block content %}
    <h3>Todo 一覧画面  <small style="color:orange; text-decoration:underline ">計{{ total}}件</small>
        <span class="back-link">
            <a href="{% url 'create' %}">新規追加</a>
            <a href="{% url 'index' %}">トップ画面へ戻る</a>
        </span>
    </h3>
    <hr>
    <ul class="list-ul"> 
        {% for todo  in todos %}
        <li> 
            <a href="{% url 'toggle' todo.id %}" style="text-decoration:none;" >
                <input type="checkbox" 
                    {% if todo.is_completed %}
                        checked
                    {% endif %} 
                > 
                <span  class="mytitle {{ todo.is_completed | yesno:'isdone,' }}"  >
                    {{ todo.title }}
                </span>
            </a> 
            <form action="{% url "delete" todo.id %}" method="post" class="delete-form">
                {% csrf_token %}
                <button type="submit">削除</button>
            </form>

            <a class="update-link " href="{% url 'update' todo.id %}">更新</a> 
            <span class="newbox "></span>
            <span class="back-date">
                {{ todo.created_at|date:"Y/m/d"  }}
            </span>
        </li>
        {% empty %}
        <li>タスクがありません</li>
        {% endfor %}
    </ul>
{% endblock %}

JavaScript

{% block myscript %}
<script>
    'use strict';
{
    const ck_boxes = document.querySelectorAll('input[type=checkbox]');
        
    ck_boxes.forEach((item)=>{
        item.addEventListener('change', ()=>{
            item.parentElement.click();
        });
    });
}
</script>
{% endblock  %}

スタイルの適用

ご参考までにstyle.cssの一部になります。

css/style.css

.list-ul{   
    /* margin: 0; */
    padding: 0;
    padding-left: 8px;
}

.list-ul li{
  margin-bottom: 4px;
  list-style: none;
}

.mytitle{
   color:#81288B;
}

.mytitle.isdone{
    color: white;
    background-color: rgb(226, 192, 148);
    border-radius: 8px;
    text-decoration: line-through;
    margin-right: 4px;
}

実行結果

最終的にはこんな感じに仕上がりました。

実行結果

以上です。

Follow me!