はじめに
Djangoで簡単なTODOアプリを作ったので、セットアップから開発までの流れを記事にしました。
セットアップ
仮想環境の作成にuvを使うので、まずuvをインストールします。
pip install uv
uv init django_todo
cd django_todo
Djangoをインストールします。
uv add django
プロジェクトを作成します。
uv run django-admin startproject django_todo .
アプリを追加します。
uv run manage.py startapp todo
main.pyは不要なので削除します。
rm ./main.py
マイグレーション後に開発用サーバーを起動してhttp://localhost:8000/にアクセスします。
ページが表示されたら成功です。
uv run manage.py migrate
uv run manage.py runserver
Conventional Commitsを強制させる
コミット時にConvetional Commits形式のコメントを強制させます。
pre-commitをインストールします。
uv add pre-commit
.pre-commit-config.yamlを追加して以下を追記します。
default_install_hook_types:
- pre-commit
- commit-msg
repos:
- repo: https://github.com/compilerla/conventional-pre-commit
rev: v4.2.0
hooks:
- id: conventional-pre-commit
stages: [commit-msg]
以下のコマンドを実行します。
uv run pre-commit install
管理者ユーザを追加
以下のコマンドを実行してadminアカウントを作成します。
uv run .\manage.py createsuperuser
ログイン、ログアウト機能を実装
最初にDjangoの標準機能を使ってログイン、ログアウトを実装します。
django_todo/urls.pyに以下を追加します。
from django.contrib import admin
from django.urls import path
+ from django.contrib.auth.views import LoginView, LogoutView
urlpatterns = [
path("admin/", admin.site.urls),
+ path("login/", LoginView.as_view(), name="login"),
+ path("logout/", LogoutView.as_view(), name="logout"),
]
django_todo/settings.pyに設定を追加します。
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
+ "todo",
]
...
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
+ "DIRS": [BASE_DIR / "templates"],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
],
},
},
]
...
+ LANGUAGE_CODE = "ja"
+ TIME_ZONE = "Asia/Tokyo"
...
+ LOGIN_URL = "login"
+ LOGIN_REDIRECT_URL = "index"
+ LOGOUT_REDIRECT_URL = "logout"
リポジトリ直下にtemplates/registrationフォルダを作成して以下のhtmlを追加します。
<!DOCTYPE html>
<html lang="jp">
<head>
<meta charset="UTF-8">
<title>ログイン</title>
</head>
<body>
<h2>ログイン</h2>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">ログイン</button>
</form>
</body>
</html>
<!DOCTYPE html>
<html lang="jp">
<head>
<meta charset="UTF-8">
<title>ログアウト</title>
</head>
<body>
<h2>ログアウトしました</h2>
<a href="{% url 'login' %}">再ログイン</a>
</body>
</html>
http://localhost:8000/login/を開いてadminアカウントを入力します。
認証が成功すると以下のエラーが表示されます。
これはログイン後に遷移するページが用意されていないためです。
ログイン後の画面を追加
templatesフォルダ内にtodoフォルダを作成して以下のhtmlファイルを追加します。
<!DOCTYPE html>
<html lang="jp">
<head>
<meta charset="UTF-8">
<title>TODOアプリ</title>
</head>
<body>
<h2>Hello, {{ user.username }}</h2>
<form method="post" action="{% url 'logout' %}">
{% csrf_token %}
<button type="submit">ログアウト</button>
</form>
</body>
</html>
todo/views.pyにビューを追加します。
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import TemplateView
class TaskListView(LoginRequiredMixin, TemplateView):
template_name = "todo/index.html"
todo/urls.pyを作成して以下を追加します。
from django.urls import path
from todo.views import TaskListView
urlpatterns = [
path("", TaskListView.as_view(), name="index"),
]
django_todo/urls.pyに以下を追加します。
urlpatterns = [
path("admin/", admin.site.urls),
path("login/", LoginView.as_view(), name="login"),
path("logout/", LogoutView.as_view(), name="logout"),
+ path("", include("todo.urls")),
]
ログイン画面でadminアカウントを入力すると、以下のページにログインします。
ログアウトボタンを押すとログアウトされます。
テンプレートの分割
各htmlファイルでページ全体のhtmlタグを記述するのは冗長なので、テンプレート機能を使います。
<!DOCTYPE html>
<html lang="jp">
<head>
<meta charset="UTF-8">
<title>{% block title %}{% endblock %}</title>
</head>
<body>
{% block content %}
{% endblock %}
</body>
</html>
各ページはタイトルとボディの部分だけ記述するようにします。
{% extends "base.html" %}
{% block title %}ログイン{% endblock %}
{% block content %}
<h2>ログイン</h2>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">ログイン</button>
</form>
{% endblock %}
{% extends "base.html" %}
{% block title %}ログアウト{% endblock %}
{% block content %}
<h2>ログアウトしました</h2>
<a href="{% url 'login' %}">再ログイン</a>
{% endblock %}
{% extends "base.html" %}
{% block title %}TODOアプリ{% endblock %}
{% block content %}
<h2>Hello, {{ user.username }}</h2>
<form method="post" action="{% url 'logout' %}">
{% csrf_token %}
<button type="submit">ログアウト</button>
</form>
{% endblock %}
サインアップ機能の実装
次はブラウザ上からアカウントを追加できるようにします。
todo/views.pyにサインアップ用のビューを追加します。
from django.views.generic import TemplateView, CreateView
from django.contrib.auth.forms import UserCreationForm
from django.urls import reverse_lazy
from django.contrib.auth import login
class SignupView(CreateView):
template_name = "registration/signup.html"
form_class = UserCreationForm
success_url = reverse_lazy("index")
def form_valid(self, form):
response = super().form_valid(form)
user = form.save()
login(self.request, user)
return response
todo/urls.pyにURLを追加します。
urlpatterns = [
path("", TaskListView.as_view(), name="index"),
+ path("signup/", SignupView.as_view(), name="signup"),
]
templates/registrationにHTMLを追加します。
{% extends "base.html" %}
{% block title %}新規登録{% endblock %}
{% block content %}
<h2>新規登録</h2>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">登録</button>
</form>
<a href="{% url 'login' %}">ログイン</a>
{% endblock %}
ログインページにリンクを追加します。
{% extends "base.html" %}
{% block title %}ログイン{% endblock %}
{% block content %}
<h2>ログイン</h2>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">ログイン</button>
</form>
+ <a href="{% url 'signup' %}">新規登録</a>
{% endblock %}
サインアップページでIDとパスワードを入力すると、アカウントが作成されてそのままログインできます。
タスクを追加する
タスクを管理するモデルを追加します。
ユーザー毎にタスクを管理するために、Userモデルを外部キーとして紐づけます。
from django.db import models
from django.contrib.auth.models import User
class Task(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=200)
completed = models.BooleanField(default=False)
def __str__(self):
return self.title
モデルを追加したのでマイグレーションを実行します。
uv run manage.py makemigrations
uv run manage.py migrate
Taskを管理サイトに登録します。
from django.contrib import admin
from .models import Task
admin.site.register(Task)
TaskListViewにタスクを取得する処理と、タスクを追加する処理を追加します。
class TaskListView(LoginRequiredMixin, TemplateView):
template_name = "todo/index.html"
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ context["tasks"] = Task.objects.filter(user=self.request.user)
+ return context
+ def post(self, request, *args, **kwargs):
+ title = request.POST.get("title")
+ if title:
+ Task.objects.create(user=request.user, title=title)
+ return redirect("index")
完了フラグを更新するビューとタスクを削除するビューも追加します。
class TaskToggleView(View):
def post(self, request, *args, **kwargs):
task = Task.objects.get(id=kwargs["id"])
task.completed = not task.completed
task.save()
return redirect("index")
class TaskDeleteView(View):
def post(self, request, *args, **kwargs):
task = Task.objects.get(id=kwargs["id"])
task.delete()
return redirect("index")
todo/urls.pyにリンクを追加します。
urlpatterns = [
path("", TaskListView.as_view(), name="index"),
path("signup/", SignupView.as_view(), name="signup"),
+ path("toggle/<int:id>", TaskToggleView.as_view(), name="toggle"),
+ path("delete/<int:id>", TaskDeleteView.as_view(), name="delete"),
]
index.htmlを修正します。
{% block content %}
- <h2>Hello, {{ user.username }}</h2>
+ <h2>{{ user.username }} のTODOリスト</h2>
+ <form method="post">
+ {% csrf_token %}
+ <input type="text" name="title" placeholder="新しいタスク" required>
+ <button type="submit">追加</button>
+ </form>
+ <ul>
+ {% for task in tasks %}
+ <li>
+ <span class="{% if task.completed %}completed{% endif %}">{{ task.title }}</span>
+ <form action="{% url 'toggle' task.id %}" style="display: inline;" method="post">
+ {% csrf_token %}
+ <button type="submit">✔</button>
+ </form>
+ <form action="{% url 'delete' task.id %}" style="display: inline;" method="post">
+ {% csrf_token %}
+ <button type="submit">削除</button>
+ </form>
+ </li>
+ {% endfor %}
+ </ul>
+ <hr />
<form method="post" action="{% url 'logout' %}">
{% csrf_token %}
<button type="submit">ログアウト</button>
</form>
{% endblock %}
タスクの追加、更新、削除ができるようになりました。
CSSを適用させる
TODOリストのページにCSSを適用させます。
django_todo/settings.pyに設定項目を追加します。
...
STATIC_URL = "static/"
+ STATICFILES_DIRS = [BASE_DIR / "static"]
...
リポジトリ直下にstatic/css作成して、以下のCSSを作成します。
span {
display: inline-block;
min-width: 300px;
}
.completed {
text-decoration: line-through;
color: gray;
}
templates/base.htmlでCSSを読み込むようにします。
+ {% load static %}
<!DOCTYPE html>
<html lang="jp">
<head>
<meta charset="UTF-8">
<title>{% block title %}{% endblock %}</title>
+ <link rel="stylesheet" type="text/css" href="{% static 'css/style.css' %}">
</head>
<body>
{% block content %}
{% endblock %}
</body>
</html>
「✔」ボタンを押すと打消し線が表示されるようになりました。
参考文献






