【初心者】Djangoで作ったToDoアプリを改良してみた
はじめに
こんにちは。クラスメソッドオペレーションズの藤瀨です。
以前、FlaskとDjangoを比較しながらシンプルなToDoアプリを作ってみた記事を書きました。
あのときのToDoアプリは「タスクを追加して一覧表示する」だけの非常にシンプルなものでしたが、記事の最後にこんなことを書いていました。
今回のToDoアプリにも機能を追加した改良版をいずれ作ってみたいですね。
では、ToDoアプリ 改良編のブログでお会いしましょう。
今回はその約束を果たすべく、Djangoで作ったToDoアプリに以下の4つの機能を追加してみました!
後ほど実装の手順で詳しく説明しますが、データベース(以降、DB)と連携させ、CRUD操作を中心に実用性を高める機能を選択しています。
- DB保存:リロードしてもタスクが消えないようにする(今回はSQLiteを使用)
- 削除・完了機能:タスクを削除したり、完了済みにチェックできるようにする
- 期限・優先度:タスクに締め切り日と優先度(高/中/低)を設定できるようにする
- ログイン認証:ユーザーごとにタスクを管理できるようにする
このブログは、私と同じくDjangoを学習したての方向けに、Djangoの基本的な知識とアプリ改良の手順をまとめています。
実際に手を動かして作ってみたいという方は、上記のブログも参考にしてみてください。
Djangoの基本構造
実装に入る前に、Djangoの設計思想を2つのポイントで整理します。
① プロジェクトとアプリ
Djangoでは、全体をプロジェクト、その中の機能単位をアプリと呼んで分けて管理します。
| 概念 | 役割 | 今回の例 |
|---|---|---|
| プロジェクト | アプリ全体の設定・管理 | config/(settings.py ・ urls.py) |
| アプリ | 特定の機能のまとまり | accounts/(認証)、todo/(タスク管理) |
アプリを機能ごとに分けることで、それぞれの役割が明確になり、コードの見通しがよくなります。
② MVTアーキテクチャ
さらに各アプリの内部は、MVT(Model・View・Template)という3層構造で設計されています。
| 層 | 役割 | 今回のファイル |
|---|---|---|
| Model | データ構造の定義とDBとのやり取りを担当 | todo/models.py(TaskManagerクラス:今回はSQLを直接記述する独自クラスとして実装) |
| View | リクエストを受け取り、処理してテンプレートに渡す | todo/views.py |
| Template | HTMLを生成してブラウザに返す | todo/templates/index.html など |
なお、通常のDjangoのModelは models.Model を継承したクラスとして定義しますが、今回の TaskManager は Django標準のModelではなく、SQLを直接実行するための独自クラスです。
そのため、一般的なDjangoのModelとは書き方や管理方法が異なる点にご注意ください。
ブラウザからリクエストが来たとき、Djangoの内部では以下の流れで処理されます。
ブラウザ
↓ リクエスト(URLにアクセス)
config/urls.py → どのアプリのurls.pyに渡すか振り分ける
↓
アプリのurls.py → そのアプリの中でどのViewを呼ぶか振り分ける
↓
views.py → 処理ロジック(必要に応じてModelを呼ぶ)
↓
models.py → DBからデータを取得・更新
↓
views.py → 取得したデータをTemplateに渡す
↓
templates/ → HTMLを生成
↓ レスポンス
ブラウザ
このMVTの構造と処理の流れを意識することで、各ファイルがなぜ存在するのか、自分が編集しているファイルが何のために使われるのかがイメージできるようになります。
実装手順の該当するSTEPに、MVTのどの部分にあたるのかを明示していますので、もし混乱したときはこのセクションと最終的なフォルダ構成を見比べてみるといいかもしれません。
最終的なフォルダ構成
フォルダ構成は以下のようになります。
django_todo/
├── config/ ← プロジェクト設定
│ ├── settings.py
│ ├── urls.py
│ └── templates/
│ └── base.html ← 共通レイアウト
├── accounts/ ← ユーザー認証アプリ
│ ├── views.py
│ ├── urls.py
│ └── templates/
│ ├── registration/
│ │ └── login.html
│ └── register.html
├── todo/ ← タスク管理アプリ
│ ├── models.py
│ ├── views.py
│ ├── urls.py
│ └── templates/
│ └── index.html
├── create_tasks_table.py ← タスクテーブル作成スクリプト
├── db.sqlite3 ← 自動生成される
└── manage.py
同じ名前のファイルが各アプリにあるので、編集するファイルを間違えないよう注意です。
このブログでは前回の記事の続きではなく、一から作成手順を説明しますが、PythonやDjangoのインストール手順などは前回のブログを参照してみてください。
次のセクションから実装の流れをSTEPごとに説明していきます。
本記事で使用している実行環境は以下の通りです。
動作確認済みの環境と異なる場合、一部手順が異なる可能性があります。
・OS : Windows
・Python : 3.14.4
・Django : 5.2.8
【参考】
Python公式ドキュメント
Django公式ドキュメント
実装手順
STEP1 プロジェクトとアプリの雛型を作成する
ターミナルで以下を実行します。
django-admin startproject config .
python manage.py startapp accounts
python manage.py startapp todo
config/・accounts/・todo/ の3フォルダが生成されます。
startproject と startapp はそれぞれ、プロジェクトとアプリを作成するDjangoのコマンドです。
STEP2 config/settings.py の編集
INSTALLED_APPS に作成した2つのアプリを追加します。
ここに登録することで、Djangoがそのアプリの存在を認識します。
INSTALLED_APPS = [
...
'accounts.apps.AccountsConfig', # 追加
'todo.apps.TodoConfig', # 追加
]
次にTEMPLATES の DIRS にプロジェクト共通テンプレートの場所を追加します。
これはHTMLの各ページに継承させるテンプレートになります。
'DIRS': [BASE_DIR / 'config' / 'templates'],
最後に、ファイルの末尾にログイン関連の設定を追加します。
今回はDjangoの標準機能であるログイン認証機能を用いるため、ログイン・ログアウト後にどのページに遷移するかを示しています。
LOGIN_REDIRECT_URL = '/' # ログイン後、タスク一覧ページに遷移
LOGOUT_REDIRECT_URL = '/accounts/login/' # ログアウト後、ログイン画面に遷移
LOGIN_URL = '/accounts/login/' # 未ログインで認証が必要な画面にアクセスした際、ログイン画面に遷移
STEP3 【M:Model】テーブルを作成する
①Django標準のテーブルを作成する
まず migrate を実行して、Django標準のテーブル(認証用の auth_user テーブルを含む)を作成します。
python manage.py migrate
今回は Django の標準認証機能を使うため、ユーザー情報のテーブル(auth_user)はここで自動生成されます。migrate コマンドは、Django が内部で管理しているマイグレーションファイルをもとに DB へテーブルを作成・更新します。
②タスクテーブルを作成する
今回は、タスクのテーブルはSQLを直接書いて作ります。manage.py と同じ階層に create_tasks_table.py を新規作成してください。
import sqlite3
db = sqlite3.connect("db.sqlite3", isolation_level=None)
create_sql = """
CREATE TABLE IF NOT EXISTS tasks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
title VARCHAR(200) NOT NULL,
due_date DATE,
priority VARCHAR(6) DEFAULT 'medium',
completed INTEGER DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES auth_user(id)
);
"""
db.execute("PRAGMA foreign_keys = ON")
db.execute(create_sql)
db.close()
isolation_level=None を指定すると「自動コミットモード」になります。
通常はSQLを実行しても commit() を呼び出すまでDBに反映されませんが、
自動コミットモードではSQLの実行と同時に即座にDBへ反映されます。
また、FOREIGN KEY (user_id) REFERENCES auth_user(id) は、「このタスクの user_id は、Djangoが管理する auth_user テーブルのidと紐づける」という意味です。
作成したらターミナルで以下を実行します。エラーが出なければ成功です(成功時は何も表示されません)。
python create_tasks_table.py
STEP4 【M:Model】TaskManagerの作成(todo/models.py)
タスクのDB操作をまとめた TaskManager クラスを、todo/models.py に書きます。
少し長いですが、各関数の説明はコードの下にまとめています。
from django.db import connection
def dictfetchall(cursor):
columns = [col[0] for col in cursor.description]
return [dict(zip(columns, row)) for row in cursor.fetchall()]
class TaskManager:
@classmethod
def get_tasks_by_user(cls, user_id):
with connection.cursor() as cursor:
cursor.execute("""
SELECT id, title, due_date, priority, completed, created_at
FROM tasks
WHERE user_id = %s
ORDER BY
CASE priority WHEN 'high' THEN 1 WHEN 'medium' THEN 2 ELSE 3 END,
created_at DESC
""", [user_id])
return dictfetchall(cursor)
@classmethod
def create_task(cls, user_id, title, due_date, priority):
with connection.cursor() as cursor:
cursor.execute("""
INSERT INTO tasks (user_id, title, due_date, priority)
VALUES (%s, %s, %s, %s)
""", [user_id, title, due_date or None, priority])
@classmethod
def toggle_complete(cls, task_id, user_id):
with connection.cursor() as cursor:
cursor.execute("""
UPDATE tasks
SET completed = CASE WHEN completed = 0 THEN 1 ELSE 0 END
WHERE id = %s AND user_id = %s
""", [task_id, user_id])
@classmethod
def get_task_by_id(cls, task_id, user_id):
with connection.cursor() as cursor:
cursor.execute("""
SELECT id, title, user_id FROM tasks
WHERE id = %s AND user_id = %s
""", [task_id, user_id])
results = dictfetchall(cursor)
return results[0] if results else None
@classmethod
def delete_task(cls, task_id, user_id):
with connection.cursor() as cursor:
cursor.execute("""
DELETE FROM tasks WHERE id = %s AND user_id = %s
""", [task_id, user_id])
〇PythonからSQLiteを操作する仕組み
connection.cursor() はDjangoを通じてSQLiteに接続し、SQLを実行するための窓口です。
cursor.execute(SQL文, [値]) でSQLを実行します。
値を直接SQL文に埋め込まず %s というプレースホルダーを使うのは、SQLインジェクション(悪意あるSQLを混入させる攻撃)を防ぐためです。
〇 dictfetchall
これはSQLの取得結果を辞書形式に変換する関数です。
変換しないままだとデータは (1, 'タスク名', '2026-06-15', 'high', 0) のようなタプル(値の並び)で返ってくるため、テンプレートで値を使うには task.0・task.1 のように順番で指定しなければなりません。
辞書に変換することで {{ task.title }}・{{ task.priority }} のように名前でアクセスできるようになります。
〇CRUD操作
DBに対する操作はCRUD(クラッド)という4種類に分類されます。
| CRUD | 意味 | SQL |
|---|---|---|
| Create | 登録 | INSERT |
| Read | 取得 | SELECT |
| Update | 更新 | UPDATE |
| Delete | 削除 | DELETE |
TaskManager はタスクに関するCRUD操作をすべてまとめたクラスです。各メソッドの対応は以下の通りです。
| メソッド | CRUD | 処理内容 |
|---|---|---|
get_tasks_by_user |
R | ログインユーザーのタスクを優先度順・作成日順で取得 |
create_task |
C | 新しいタスクをDBに追加 |
toggle_complete |
U | 完了フラグを0→1、1→0に反転 |
get_task_by_id |
R | 特定のタスクを1件取得 |
delete_task |
D | タスクをDBから削除 |
また、すべてのメソッドに WHERE user_id = %s という条件が入っています。DBにはすべてのユーザーのタスクが混在しているため、この条件がないとAさんがログインしているのにBさんのタスクまで表示・操作できてしまいます。
ログイン中のユーザーのIDで絞り込むことで、自分のタスクだけを扱えるようにしています。
※get_task_by_id は今回の画面では使用していませんが、今後タスク編集機能を追加する場合などに、対象タスクを取得する処理として利用できます。
STEP5 【V:View】accountsアプリ
ログイン・ログアウトはDjangoの標準機能に任せるので、accounts アプリにはユーザー登録の処理だけを書きます。
① accounts/views.py
from django.shortcuts import render, redirect
from django.contrib.auth.forms import UserCreationForm
def register(request):
if request.method == 'POST':
form = UserCreationForm(request.POST)
if form.is_valid():
form.save()
return redirect('login')
else:
form = UserCreationForm()
return render(request, 'register.html', {'form': form})
UserCreationForm はDjangoが用意しているユーザー作成フォームです。ユーザー名・パスワード・パスワード確認の入力欄とバリデーションを自動で処理してくれます。
また、register 関数はGETとPOSTで処理を分けています。
- GET(ページを開いたとき) —
elseブロックで空のフォームを作り、テンプレートに渡して表示します。 - POST(登録ボタンを押したとき) —
UserCreationForm(request.POST)でフォームに送信データを流し込み、is_valid()でバリデーション(パスワードの一致・強度チェックなど)を実行します。問題なければform.save()でDBにユーザーを保存し、ログイン画面にリダイレクトします。バリデーションが通らなかった場合はelseに入らずreturn render(...)でエラー付きのフォームを再表示します。
② accounts/urls.py
from django.urls import path
from .views import register
urlpatterns = [
path('register/', register, name='register'),
]
/accounts/register/ というURLにアクセスしたとき、views.py の register 関数を呼ぶよう紐づけています。
name='register' をつけておくことで、テンプレートから {% url 'register' %} という名前でこのURLを参照できるようになります。
STEP6 【V:View】todoアプリ
① todo/views.py
タスクの追加・完了切り替え・削除の処理を書きます。
from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required
from .models import TaskManager
@login_required
def todo_list(request):
user_id = request.user.id # Django標準認証ではrequest.userでユーザー情報を取得できる
if request.method == 'POST':
title = request.POST.get('title', '').strip()
due_date = request.POST.get('due_date') or None
priority = request.POST.get('priority') or 'medium'
if priority not in ['high', 'medium', 'low']:
priority = 'medium'
if not title:
tasks = TaskManager.get_tasks_by_user(user_id)
return render(request, 'index.html', {'tasks': tasks, 'error': 'タイトルを入力してください'})
TaskManager.create_task(user_id, title, due_date, priority)
return redirect('todo_list')
tasks = TaskManager.get_tasks_by_user(user_id)
return render(request, 'index.html', {'tasks': tasks})
@login_required
def toggle_complete(request, task_id):
if request.method == 'POST':
TaskManager.toggle_complete(task_id, request.user.id) # ← toggle_complete を呼ぶ
return redirect('todo_list')
@login_required
def todo_delete(request, task_id):
if request.method == 'POST':
TaskManager.delete_task(task_id, request.user.id)
return redirect('todo_list')
〇@login_required
各関数の直前につけているデコレータです。未ログイン状態でそのURLにアクセスしようとすると、自動的にログイン画面へ飛ばされます。STEP2で行った通り、settings.py の LOGIN_URL で指定したページに遷移する仕組みです。
〇todo_list 関数(タスク一覧・追加)
ページを開いたとき(GET)はタスク一覧を取得して表示し、追加ボタンを押したとき(POST)はフォームの入力値を受け取ってDBに保存します。request.POST.get('title') でフォームの name="title" の値を取り出しています。
〇toggle_complete・todo_delete 関数
どちらもボタンを押したとき(POST)にだけ呼ばれる処理です。URLに含まれる task_id(例:/complete/3/)を受け取って、対象のタスクを更新・削除し、redirect でタスク一覧に戻ります。
② todo/urls.py
accountsアプリと同様に、URLに応じてどの関数を呼ぶかを定義します。
from django.urls import path
from .views import todo_list, toggle_complete, todo_delete
urlpatterns = [
path('', todo_list, name='todo_list'),
path('complete/<int:task_id>/', toggle_complete, name='toggle_complete'),
path('delete/<int:task_id>/', todo_delete, name='todo_delete'),
]
STEP7 【V:View】 config/urls.py の編集
config/urls.py はプロジェクト全体のURL受付窓口です。ブラウザからリクエストが来ると、まずここで「どのアプリに渡すか」を振り分けます。
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('accounts/', include('django.contrib.auth.urls')), # ログイン・ログアウト
path('accounts/', include('accounts.urls')), # ユーザー登録
path('', include('todo.urls')),
]
include() でURLの処理を別のファイルに委ねています。
たとえば include('todo.urls') と書くと、「この先のURL判定は todo/urls.py に任せる」という意味になります。
また、path('accounts/', include('django.contrib.auth.urls')) で、Django標準のログイン・ログアウト用URLをまとめて登録しています。
この1行だけで /accounts/login/ や /accounts/logout/ が使えるようになるので、自分でビューを書く必要はありません。
STEP8 【T:Template】テンプレートの作成
最後に、それぞれのページのテンプレートを作成していきます。
① config/templates/base.html(共通レイアウト)の作成
テンプレート継承の「親」にあたるファイルです。{% block title %} と {% block content %} が子テンプレートの差し込み口で、それ以外のナビバーや Bootstrap の読み込みはすべてのページで共通になります。
ナビの {% if user.is_authenticated %} は、Django標準認証を使うとテンプレートで自動的に使える変数で、ログイン中はログアウト用のPOSTフォームを表示し、未ログイン時はログインリンクとユーザー登録リンクを表示しています。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<title>{% block title %}{% endblock %} | ToDoリスト</title>
</head>
<body>
<nav class="navbar navbar-expand-lg bg-body-tertiary">
<div class="container-fluid">
<a class="navbar-brand" href="{% url 'todo_list' %}">📝 ToDoリスト</a>
<ul class="navbar-nav ms-auto">
{% if user.is_authenticated %}
<li class="nav-item"><span class="nav-link">{{ user.username }}</span></li>
<li class="nav-item">
<form method="POST" action="{% url 'logout' %}">
{% csrf_token %}
<button type="submit" class="nav-link btn btn-link border-0 bg-transparent">
ログアウト
</button>
</form>
</li>
{% else %}
<li class="nav-item"><a class="nav-link" href="{% url 'login' %}">ログイン</a></li>
<li class="nav-item"><a class="nav-link" href="{% url 'register' %}">ユーザー登録</a></li>
{% endif %}
</ul>
</div>
</nav>
<div class="container mt-4">
{% block content %}{% endblock %}
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
② accounts/templates/registration/login.html
ログイン画面のファイルです。
registration/ フォルダを作ってその中に置きます(Django標準認証がこのパスを探します)。
{% extends 'base.html' %}
{% block title %}ログイン{% endblock %}
{% block content %}
<h2>ログイン</h2>
{% if form.errors %}<p class="text-danger">ユーザー名またはパスワードが正しくありません</p>{% endif %}
<form method="POST">
{% csrf_token %}
<div class="mb-3">
<label>ユーザー名</label>
<input type="text" name="username" class="form-control" required>
</div>
<div class="mb-3">
<label>パスワード</label>
<input type="password" name="password" class="form-control" required>
</div>
<button type="submit" class="btn btn-primary">ログイン</button>
<a href="{% url 'register' %}" class="btn btn-outline-secondary ms-2">ユーザー登録はこちら</a>
</form>
{% endblock %}
最初に {% extends 'base.html' %} と書くことで、親である base.html を継承することを示しています。
また、フォームには {% csrf_token %} が必須です。
これはDjangoが自動的にトークンをチェックして、外部サイトからの不正なリクエストを防ぐ仕組みです(CSRF対策)。form.errors があればエラーメッセージを表示し、なければ何も出ません。
③ accounts/templates/register.html
ユーザー登録画面のファイルです。
{% extends 'base.html' %}
{% block title %}ユーザー登録{% endblock %}
{% block content %}
<h2>ユーザー登録</h2>
<form method="POST">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="btn btn-primary">登録</button>
<a href="{% url 'login' %}" class="btn btn-outline-secondary ms-2">ログインはこちら</a>
</form>
{% endblock %}
{{ form.as_p }} の1行だけで、UserCreationForm のすべてのフィールド(ユーザー名・パスワード・確認用パスワード)が <p> タグ付きで自動レンダリングされます。
④ todo/templates/index.html
タスク一覧画面のファイルです。
長いですが、コードの下にポイントをまとめています。
{% extends 'base.html' %}
{% block title %}タスク一覧{% endblock %}
{% block content %}
<h2>タスク一覧</h2>
{% if error %}<p class="text-danger">{{ error }}</p>{% endif %}
<form method="POST" class="mb-4">
{% csrf_token %}
<div class="row g-2 align-items-end">
<div class="col-md-4">
<label>タイトル</label>
<input type="text" name="title" class="form-control" placeholder="タスクを入力" required>
</div>
<div class="col-md-3">
<label>期限</label>
<input type="date" name="due_date" class="form-control">
</div>
<div class="col-md-3">
<label>優先度</label>
<select name="priority" class="form-select">
<option value="high">高</option>
<option value="medium" selected>中</option>
<option value="low">低</option>
</select>
</div>
<div class="col-md-2">
<button type="submit" class="btn btn-primary w-100">追加</button>
</div>
</div>
</form>
<table class="table table-hover">
<thead class="table-dark">
<tr><th>完了</th><th>タイトル</th><th>期限</th><th>優先度</th><th></th></tr>
</thead>
<tbody>
{% for task in tasks %}
<tr>
<td>
<form method="POST" action="{% url 'toggle_complete' task.id %}">
{% csrf_token %}
<button type="submit" class="btn btn-sm btn-outline-secondary">
{% if task.completed %}✅{% else %}⬜{% endif %}
</button>
</form>
</td>
<td {% if task.completed %}style="text-decoration: line-through; color: gray;"{% endif %}>
{{ task.title }}
</td>
<td>{{ task.due_date|default:"-" }}</td>
<td>
{% if task.priority == 'high' %}<span class="badge bg-danger">高</span>
{% elif task.priority == 'medium' %}<span class="badge bg-warning text-dark">中</span>
{% else %}<span class="badge bg-success">低</span>{% endif %}
</td>
<td>
<form method="POST" action="{% url 'todo_delete' task.id %}">
{% csrf_token %}
<button type="submit" class="btn btn-sm btn-outline-danger">削除</button>
</form>
</td>
</tr>
{% empty %}
<tr><td colspan="5" class="text-center">タスクはありません</td></tr>
{% endfor %}
</tbody>
</table>
{% endblock %}
-
タスク追加フォーム —
method="POST"でビューのtodo_listに送信します。name="title"など各フィールドのname属性が、ビュー側のrequest.POST.get('title')と対応しています。 -
{% for task in tasks %}— ビューから渡されたtasksリストをループして1行ずつ表示します。タスクが0件のときは{% empty %}以下が表示されます。 -
完了ボタン —
{% url 'toggle_complete' task.id %}でURLにtask_idを埋め込んでいます。/complete/3/のように動的なURLを生成でき、ビューでtask_idとして受け取れます。 -
task.due_date|default:"-"— 期限が未設定(None)のときに-を表示するDjangoのテンプレートフィルタです。 -
削除ボタン — 完了ボタンと同じく
<form method="POST">で送信しています。リンク(<a href>)ではなくフォームにしているのは、GETリクエストで削除が走らないようにするためです。
STEP9 動かしてみる・よくあるエラーと対処法
python manage.py runserver
http://127.0.0.1:8000/accounts/register/ からユーザー登録をして、ログイン後にタスクを追加・完了・削除してみてください。
以下の画面のように表示されていれば成功です。

| よくあるエラー | 原因と対処 |
|---|---|
| TemplateDoesNotExist | テンプレートファイルのパスや配置場所を確認 |
| no such table: tasks | create_tasks_table.py の実行忘れ、または migrate の未実行 |
| ログイン後に 404 | LOGIN_REDIRECT_URL の設定と todo/urls.py のパスが一致しているか確認 |
実際に作ってみて
今回の実装を通して、「Djangoを一段階深く理解できた」という実感を得られました。
前回の記事でアプリを初めて作ったときは、手順通りにファイルを作れば動くものの、なぜそのファイルが必要なのかがよくわかっていませんでした。views.py に書くのか models.py に書くのか、判断の根拠がなく、何となく進めていた部分がかなりありました。
今回は研修でMVTの構造を学んでから実装に臨んだこともあり、コードを書く前に「これはModelの処理か、Viewの処理か」と考える習慣がつきました。例えば「タスクを取得してくる処理はModelに書く」「取得したデータをどのテンプレートに渡すかはViewが決める」という判断が、自然にできるようになっていました。
また、urls.py の役割についても理解が変わりました。前回はただ「パスとビューを結びつけるファイル」という認識でしたが、今回はMVTフロー全体の中で「リクエストをどのViewに渡すか振り分けるゲートウェイ」として捉えられるようになり、ファイルの存在意義が腑に落ちました。
以前はファイルが多いことを複雑さの原因と感じていましたが、今は逆に、役割ごとにファイルが分かれているからこそ「どこを見ればいいか」「どこに書けばいいか」を迷わないのだと理解できています。
今回「改良してみた」というタイトルでブログを書きましたが、まだまだこのアプリには改善点があります。
例えば、完了したタスクが画面上に残り続けてしまう問題や、タスクの編集・検索・並び替えといった機能がまだ未実装であるなど、この ToDo アプリをより実用的にできる余地はいくつもあります。
UIの面も含めて、ぜひ自分なりのアレンジを加えてみてください。
最後までお読みいただきありがとうございました!
クラスメソッドオペレーションズ株式会社について
クラスメソッドグループのオペレーション企業です。
運用・保守開発・サポート・情シス・バックオフィスの専門チームが、IT・AIをフル活用した「しくみ」を通じて、お客様の業務代行から課題解決や高付加価値サービスまでを提供するエキスパート集団です。
当社は様々な職種でメンバーを募集しています。
「オペレーション・エクセレンス」と「らしく働く、らしく生きる」を共に実現するカルチャー・しくみ・働き方にご興味がある方は、クラスメソッドオペレーションズ株式会社 コーポレートサイトをぜひご覧ください。
※2026年1月 アノテーション㈱から社名変更しました。






