概要
Pythonのflaskフレームワークを使用して、TODOリストを管理するWEBアプリを作成したのでまとめた。
flaskのGET/POSTの方法やセッション等の扱い、Jinjaテンプレートの使い方について、当アプリを作成しながら振り返ることが目的。
当記事ではTODOタスクの詳細表示、編集、削除について扱う。
概要 Pythonのflaskフレームワークを使用して、TODOリストを管理するWEBアプリを作成したのでまとめた。 flaskのGET/POSTの方法やセッション等の扱いについて、当アプリを作成しながら振り返ることが目的。 当記[…]
概要 Pythonのflaskフレームワークを使用して、TODOリストを管理するWEBアプリを作成したのでまとめた。 flaskのGET/POSTの方法やセッション等の扱いについて、当アプリを作成しながら振り返ることが目的。 当記[…]
概要 Pythonのflaskフレームワークを使用して、TODOリストを管理するWEBアプリを作成したのでまとめた。 flaskのGET/POSTの方法やセッション等の扱い、Jinjaテンプレートの使い方について、当アプリを作成しながら[…]
TODO詳細、編集、削除機能
機能説明
以下の機能をもつTODO詳細、編集画面を作成した。
・TODOタスクの詳細表示機能
・TODOタスクの削除機能
TODO編集画面:
・画面初期値設定機能
・入力チェック機能
・TODOタスク内容の更新機能
アプリ概要
画面遷移
◆TODOトップ画面
◆TODO詳細画面
◆TODO編集画面
◆更新成功時
◆削除失敗時
ファイル構成
ファイル構成は以下とする。
※赤文字箇所は前回からの追加ファイルとなる
TODOタスクの詳細表示機能
TODOトップ画面にてタスク名リンクを押下すると
TODO詳細画面にタスク内容を表示する。
解説
サーバー側
data_access.py
def get_todo(t_id):
""" タスク情報取得 """
session = get_session()
return session.get(Todo, t_id)
DBアクセスレイヤでは、タスクIDに紐づくタスククラスをDBから取得して返却する。
business_logic.py
def get_todo(t_id):
""" タスク情報取得 """
todo = data_access.get_todo(t_id)
todo_item = {
"id": todo.id,
"title": todo.title,
"category_id": str(todo.category.id),
"category": todo.category.category_name,
"content": todo.content,
"memo": todo.memo,
"due_date": f"{todo.due_date:%Y-%m-%d}",
"status": "未完了" if todo.status == consts.STATUS_INCOMPLETE else "完了"
}
return todo_item
ビジネスロジックレイヤでは、DBアクセスレイヤからタスククラスを取得し、画面表示用に辞書データに変換する。
例えば、期日をdatetimeから文字列に変換したり、ステータスを「1/0」から「完了/未完了」に変換している。
また、category_idについては画面側でセレクトボックスの初期値を設定するために文字列型に変換している。
views.py
@app.route("/todo_apps/detail/")
def show_detail(t_id):
""" タスク詳細画面を表示する """
todo = business_logic.get_todo(t_id)
return render_template("detail.html", todo=todo)
ルーティングレイヤでは、画面からタスクIDを受け取りビジネスロジックレイヤへ引数として渡す。
ビジネスロジックからタスク情報を取得し、画面に返却する。
画面側
top.html
<td><a href="{{ url_for('show_detail', t_id=todo_item['id']) }}">{{ todo_item["content"] }}</a></td>
TODOトップ画面では、タスク名にアンカータグを設定する。
ルーティングレイヤのTODO詳細画面表示処理へリクエストする。
detail.html
<div class="row border p-2 mb-2">
<div class="col-sm-3"><h5>カテゴリ:</h5></div>
<div class="col-sm-9"><p>{{ todo["category"] }}</p></div>
</div>
(略)
<div class="row border p-2 mb-2">
<div class="col-sm-3"><h5>メモ:</h5></div>
<div class="col-sm-9">
{% if todo["memo"] %}
<p>{{ todo["memo"].replace("\r\n", "<br>") | safe }}</p>
{% else %}
<p></p>
{% endif %}
</div>
</div>
TODO詳細画面では、サーバー側から取得したtodo辞書データを表示する。
メモの内容についてはテキストエリアのため、改行コード(\r\n)を含む内容となる。
jinjaテンプレートでは、HTMLを安全に生成するために改行コード(\r\n)などは自動的にエスケープ処理が行われていた。
そのため、改行された状態でメモの内容を表示するために、str.replaceメソッドを使用して改行コードを<br>タグに変換した。
尚、safeフィルタをつけないと以下のようにHTMLがそのまま表示されてしまうため、safeフィルタが必須。
削除機能
TODO詳細画面の【削除する】ボタンを押下すると、タスクをDBから物理削除できる。
・タスク削除成功時
対象のタスクが削除されてTODOトップ画面に遷移する。
・タスク削除失敗時
タスクの削除に失敗すると、エラー画面に遷移する。
解説
サーバー側
data_access.py
def delete_task(t_id):
""" タスクの削除 """
is_valid = True
session = get_session()
try:
session.query(Todo).filter_by(id=t_id).delete()
session.commit()
except Exception as e:
print(e)
session.rollback()
is_valid = False
finally:
session.close()
return is_valid
DBアクセスレイヤでは、タスクIDに紐づくタスク情報をDBから削除する。
削除に失敗した場合、is_valid変数にFalseを格納して返却する。
削除に成功した場合、is_valid変数にTrueを格納して返却する。
business_logic.py
def delete_task(t_id):
""" タスクの削除 """
return data_access.delete_task(t_id)
ビジネスロジックレイヤでは、DBアクセスレイヤの削除結果をそのまま返却する。
views.py
@app.route("/todo_apps/delete/")
def delete_todo(t_id):
""" タスクを削除する """
is_valid = business_logic.delete_task(t_id)
if not is_valid:
# 削除エラーの場合
return redirect(url_for("show_error", error_id=consts.DB_ERROR))
else:
return redirect(url_for('top'))
ルーティングレイヤでは、画面からタスクIDを受け取りビジネスロジックレイヤへ引数として渡す。
削除結果を取得し、その結果を判定して画面遷移する。
タスク削除に失敗した場合、エラーIDを設定してエラー画面表示メソッドにリダイレクトする。
タスク削除に成功した場合、TODOトップ画面表示メソッドにリダイレクトする。
views.py
@app.route("/todo_apps/error")
def show_error():
session.clear()
error_id = request.args.get("error_id", "")
if error_id == consts.DB_ERROR:
# DBエラーの場合
error_msg = "DBを更新中にエラーが発生しました。時間を空けて、再度ログインからやりなおしてください。"
else:
error_msg = "エラーが発生しました。時間を空けて、再度ログインからやりなおしてください。"
return render_template("error.html", error_msg=error_msg)
エラー画面表示メソッド。
セッションクリアを行った後、エラーIDをもとにエラーメッセージを作成してエラー画面を返却する。
エラー画面に遷移する理由は、再度TODO詳細画面に遷移しても詳細情報をDBから取得できず、画面状態を再現できない可能性があるため。
画面側
error.html
<div class="container mt-5 p-3 card border">
<h2 class="text-center">処理続行エラー</h2>
<div class="mt-4">
<div class="card-body">
<div class="p-2 mb-3 text-center">
<p>{{ error_msg }}</p>
</div>
<div class="d-flex justify-content-center mt-3">
<a href="{{ url_for('show_login') }}" class="btn btn-primary mb-3">ログイン</a>
</div>
</div>
</div>
</div>
エラー画面では、サーバー側から取得したエラーメッセージを表示している。
また、ログインボタンを表示している。
画面初期値設定機能
TODO編集画面を表示すると、各フォーム部品にDBから取得したタスク情報を初期値として設定する。
解説
サーバー側
◆data_access.py
TODOタスクの詳細表示機能と同様。
get_todoメソッドを呼び出す。
◆business_logic.py
TODOタスクの詳細表示機能と同様。
get_todoメソッドを呼び出す。
views.py
@app.route("/todo_apps/edit/")
def show_edit(t_id):
""" 編集画面を表示する """
categories = business_logic.get_category_all()
todo = business_logic.get_todo(t_id)
return render_template("edit.html", categories=categories, todo=todo)
ルーティングレイヤでは、画面からタスクIDを受け取りビジネスロジックレイヤへ引数として渡してタスク情報を取得する。
あわせてカテゴリ一覧もDBから取得する。
カテゴリ一覧リスト、タスク情報を画面に返却する。
画面側
edit.html
<div class="form-group">
<label for="category">カテゴリ<span class="text-primary ml-3">※必須</span></label>
<select class="form-control" id="category" name="category">
<option value="">選択してください</option>
{% for category in categories %}
<option value="{{category.id}}"
{% if category.id|string()==todo.get('category_id') %} selected {% endif %}>{{ category.category_name }}</option>
{% endfor %}
</select>
</div>
(中略)
<div class="form-group mt-3">
<label for="title">タイトル <span class="text-primary ml-3">※必須</span></label>
<input type="text" class="form-control" id="title" name="title" value="{{ todo.get('title', '') }}">
</div>
TODO編集画面では、サーバー側から取得したtodo辞書データを表示する。
入力チェック機能
TODO編集画面にて入力エラーがある場合、form部品直下にエラーメッセージを表示する。
※TODO登録画面と同様の機能
解説
サーバー側
views.py
@app.route("/todo_apps/edit/done", methods=["POST"])
def edit_todo():
""" タスクを編集する """
# 入力値検証
error_dict = {}
is_valid = validate_views.validate_input_todo(request.form, error_dict)
if not is_valid:
# 入力エラーの場合
categories = business_logic.get_category_all()
todo = get_todo(request.form)
return render_template("edit.html", categories=categories, error_dict=error_dict, todo=todo)
ルーティングレイヤでは、TODOタスクの更新リクエストがくると画面の入力値検証を行う。
入力エラーの場合、各フォームのエラーメッセージ内容が格納されたerror_dict辞書データを画面側に返却する。
入力チェックを行うメソッドについては、TODO登録画面と同じ入力チェックメソッドを使用するため以下のようにメソッド名を修正した。
is_valid = validate_views.validate_create_todo(request.form, error_dict)
is_valid = validate_views.validate_input_todo(request.form, error_dict)
validate_create_todoという名前の場合、意味的に登録画面の入力チェックとなってしまうため、より汎用的な名前のvalidate_input_todoに変更した。
エラーが発生した場合、TODO編集画面の画面状態を再現するため以下のようにリクエスト内容をtodo辞書データに変換している。
views.py
def get_todo(form):
""" タスク情報を返却する """
todo = {
"id": form.get("id"),
"title": form.get("title", ""),
"category_id": form.get("category", ""),
"content": form.get("content", ""),
"memo": form.get("memo", ""),
"due_date": form.get("due_date", "")
}
return todo
TODO登録画面では、このような辞書データに変換する処理は行わず、画面入力内容はrequest.formから再現している。
しかし、編集ルートの場合はTODO編集画面初期表示時にtodo辞書データを画面側に渡しているため、入力エラー時の画面状態再現のためには初期表示時と同じ形式で画面側にタスク内容を返却する必要がある。
そのため、request.formから入力値を取得してtodo辞書データに変換している。
画面側
edit.html
{% if error_dict and error_dict["content"] %}
<div id="error-message" class="text-danger mb-3">{{ error_dict["content"] }}</div>
{% endif %}
TODO編集画面では、TODO登録画面と同様のエラーメッセージ表示領域を作成した。
error_dictを最初に判定する理由は、サーバー側からerror_dictが画面に返却されていない状態で、オブジェクト内を参照しようとするとエラーになってしまうため。
まず、error_dict内に値があるか判定を行う必要がある。
error_dict内に格納されたエラーメッセージは、各フォーム部品の名前をキーに表示する。
TODOタスク内容の更新機能
TODO編集画面にて、タスク内容を更新できる。
解説
サーバー側
data_access.py
def update_todo(todo_item):
""" タスクの更新 """
is_valid = True
session = get_session()
todo = session.get(Todo, todo_item["id"])
if not todo:
is_valid = False
else:
try:
todo.title = todo_item["title"]
todo.content = todo_item["content"]
todo.memo = todo_item["memo"]
todo.due_date = todo_item["due_date"]
todo.category_id = todo_item["category_id"]
session.add(todo)
session.commit()
except Exception as e:
print(e)
session.rollback()
is_valid = False
finally:
session.close()
return is_valid
DBアクセスレイヤでは、タスク辞書データを取得して以下の順序で更新する。
①タスクIDをキーにDBからTodoクラスを取得
②取得したTodoクラスに画面入力値を設定してコミット
DB更新に失敗した場合、is_valid変数にFalseを格納して返却する。
DB更新に成功した場合、is_valid変数にTrueを格納して返却する。
business_logic.py
def update_todo(form):
""" タスクの更新 """
todo_item = {
"id": form.get("id"),
"title": form.get("title"),
"content": form.get("content"),
"memo": form.get("memo", ""),
"due_date": datetime.strptime(form.get("due_date"), consts.DATE_FORMAT),
"category_id": int(form.get("category"))
}
return data_access.update_todo(todo_item)
ビジネスロジックレイヤでは、画面入力値をDB更新できる値に変換してDBアクセスレイヤに渡す。
DBアクセスレイヤの結果をそのまま返却する。
views.py
# タスク更新
is_valid = business_logic.update_todo(request.form)
if not is_valid:
# 入力エラーの場合
categories = business_logic.get_category_all()
error_msg = "タスクの更新に失敗しました。時間を空けて再度実行してください。"
todo = get_todo(request.form)
return render_template("edit.html", categories=categories, error_msg=error_msg, todo=todo)
else:
return redirect(url_for("show_detail", t_id=request.form["id"]))
ルーティングレイヤでは、ビジネスロジックレイヤから取得したDB更新結果を判定する。
DB更新に失敗した場合、再度カテゴリ一覧を取得,タスク情報を画面表示用に変換して取得,エラーメッセージを設定して画面に返却する。
DB更新に成功した場合、TODO詳細画面表示メソッドにリダイレクトする。
画面側
edit.html
<div class="card-body">
<div id="error-message" class="text-danger mb-3">{{ error_msg }}</div>
DB更新エラーの場合、画面上にエラーメッセージを表示する。
ファイル詳細
data_access.py
import os from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker from models import User, TodoCategory, Todo import consts def get_session(): """ セッション情報を返却 """ db_path = os.path.join('sqlite:///', 'db', 'todo.db') engine = create_engine(db_path) Session = sessionmaker(bind=engine) session = Session() return session def find_user(input_name, input_password): """ 利用者検索 """ session = get_session() user = session.query(User).filter_by(name=input_name, password=input_password).first() return user def find_todo_all(u_id): """ 利用者IDをもとにTODOタスク一覧を取得する """ session = get_session() todo_list = session.query(Todo).filter(Todo.user_id == u_id, Todo.status == consts.STATUS_INCOMPLETE).order_by(Todo.due_date.asc()).all() return todo_list def update_todo_status(t_id): """ タスクのステータスを1(完了)に更新する """ is_valid = True session = get_session() try: todo = session.get(Todo, t_id) todo.status = consts.STATUS_COMPLETE session.add(todo) session.commit() except: session.rollback() is_valid = False finally: session.close() return is_valid def get_category_all(): """ カテゴリ一覧取得 """ session = get_session() return session.query(TodoCategory).all() def insert_todo(todo): """ タスクの新規登録 """ is_valid = True session = get_session() try: todo = Todo( title = todo["title"], content = todo["content"], memo = todo["memo"], due_date = todo["due_date"], category_id = todo["category_id"], user_id = todo["user_id"] ) session.add(todo) session.commit() except Exception as e: print(e) session.rollback() is_valid = False finally: session.close() return is_valid def get_todo(t_id): """ タスク情報取得 """ session = get_session() return session.get(Todo, t_id) def update_todo(todo_item): """ タスクの更新 """ is_valid = True session = get_session() todo = session.get(Todo, todo_item["id"]) if not todo: is_valid = False else: try: todo.title = todo_item["title"] todo.content = todo_item["content"] todo.memo = todo_item["memo"] todo.due_date = todo_item["due_date"] todo.category_id = todo_item["category_id"] session.add(todo) session.commit() except Exception as e: print(e) session.rollback() is_valid = False finally: session.close() return is_valid def delete_task(t_id): """ タスクの削除 """ is_valid = True session = get_session() try: session.query(Todo).filter_by(id=t_id).delete() session.commit() except Exception as e: print(e) session.rollback() is_valid = False finally: session.close() return is_valid
business_logic.py
import data_access import consts from datetime import datetime def find_user(name, password): """ userを取得する """ # 名前とパスワードからuserオブジェクトを取得 return data_access.find_user(name, password) def find_todo_all(u_id): """ タスク一覧を取得する """ # userに紐づくタスク一覧の取得 todo_list = data_access.find_todo_all(u_id) todos = [] for todo in todo_list: # 画面表示用データに整形 todo_item = { "id": todo.id, "title": todo.title, "category": todo.category.category_name, "content": todo.content, "due_date": f"{todo.due_date:%Y-%m-%d}", "status": "未完了" if todo.status == consts.STATUS_INCOMPLETE else "完了" } todos.append(todo_item) return todos def update_todo_status(t_id): """ タスクを完了にする """ return data_access.update_todo_status(t_id) def get_category_all(): """ カテゴリ一覧取得 """ return data_access.get_category_all() def insert_todo(form, u_id): """ タスクの新規登録 """ todo = { "title": form.get("title"), "content": form.get("content"), "memo": form.get("memo", ""), "due_date": datetime.strptime(form.get("due_date"), consts.DATE_FORMAT), "user_id": u_id, "category_id": int(form.get("category")) } return data_access.insert_todo(todo) def get_todo(t_id): """ タスク情報取得 """ todo = data_access.get_todo(t_id) todo_item = { "id": todo.id, "title": todo.title, "category_id": str(todo.category.id), "category": todo.category.category_name, "content": todo.content, "memo": todo.memo, "due_date": f"{todo.due_date:%Y-%m-%d}", "status": "未完了" if todo.status == consts.STATUS_INCOMPLETE else "完了" } return todo_item def update_todo(form): """ タスクの更新 """ todo_item = { "id": form.get("id"), "title": form.get("title"), "content": form.get("content"), "memo": form.get("memo", ""), "due_date": datetime.strptime(form.get("due_date"), consts.DATE_FORMAT), "category_id": int(form.get("category")) } return data_access.update_todo(todo_item) def delete_task(t_id): """ タスクの削除 """ return data_access.delete_task(t_id)
validate_views.py
import business_logic from datetime import datetime import consts def validate_login(form, errors): """ ログイン情報の入力チェックを行う """ is_valid = True name = form.get("name") password = form.get("password") if not name or not password: errors.append("名前またはパスワードを入力してください。") is_valid = False return is_valid def validate_input_todo(form, error_dict): """ タスク登録または編集の入力チェックを行う """ is_valid = True # カテゴリ 必須 c_id = form.get("category") if not c_id: error_dict["category"] = "カテゴリを選択してください。" is_valid = False else: # カテゴリ 範囲 categories = business_logic.get_category_all() id_list = list(map(str, [category.id for category in categories])) if not c_id in id_list: error_dict["category"] = "不正なカテゴリが選択されています。" is_valid = False # タイトル 必須 title = form.get("title") if not title: error_dict["title"] = "タイトルを入力してください。" is_valid = False # タスク内容 必須 content = form.get("content") if not content: error_dict["content"] = "タスク内容を入力してください。" is_valid = False # タスク期日 必須 due_date = form.get("due_date") if not due_date: error_dict["due_date"] = "タスク期日を入力してください。" is_valid = False else: # タスク期日 存在する日付 try: datetime.strptime(due_date, consts.DATE_FORMAT) except ValueError: error_dict["due_date"] = "存在する日付を入力してください。" is_valid = False return is_valid
views.py
from flask import Flask, render_template, request, redirect, url_for, session import business_logic import validate_views import consts app = Flask(__name__) app.secret_key = "yiYKQmFC6MTVKs5THpKkD" @app.route("/") def index(): return redirect(url_for("show_login")) @app.route("/todo_apps") def show_login(): """ ログイン画面表示 """ return render_template("login.html") @app.route("/todo_apps/login", methods=["POST"]) def login(): """ ログイン処理 """ # 入力値検証 errors = [] if not validate_views.validate_login(request.form, errors): return render_template("login.html", errors=errors) # ログイン確認 name = request.form["name"] password = request.form["password"] user = business_logic.find_user(name, password) if user: # ログイン成功 session["name"] = user.name session["u_id"] = user.id return redirect(url_for("top")) else: # ログイン失敗 errors.append("名前またはパスワードが正しくありません。") return render_template("login.html", errors=errors) @app.route("/todo_apps/top") def top(): """ TODOタスク一覧画面表示 """ # タスク一覧を取得 todos = business_logic.find_todo_all(session["u_id"]) return render_template("top.html", todos=todos) @app.route("/logout") def logout(): """ ログアウト """ session.clear() return redirect(url_for("show_login")) @app.route("/todo_apps/top/task/<int:t_id>") def update_todo_done(t_id): """ タスクを完了にする """ is_valid = business_logic.update_todo_status(t_id) if not is_valid: errors = ["更新に失敗しました。時間を空けて再度実行してください。"] # タスク一覧を取得 todos = business_logic.find_todo_all(session["u_id"]) return render_template("top.html", todos=todos, errors=errors) else: return redirect(url_for("top")) @app.route("/todo_apps/create_input") def show_create(): """ タスク新規登録画面を表示する """ categories = business_logic.get_category_all() return render_template("create.html", categories=categories) @app.route("/todo_apps/create_todo", methods=["POST"]) def create_todo(): """ タスクを新規登録する """ # 入力値検証 error_dict = {} is_valid = validate_views.validate_input_todo(request.form, error_dict) if not is_valid: # 入力エラーの場合 categories = business_logic.get_category_all() return render_template("create.html", categories=categories, error_dict=error_dict) else: # タスク登録 is_valid = business_logic.insert_todo(request.form, session["u_id"]) if not is_valid: # DBエラーの場合 categories = business_logic.get_category_all() error_msg = "タスクの登録に失敗しました。時間を空けて再度実行してください。" return render_template("create.html", categories=categories, error_msg=error_msg) else: return redirect(url_for("top")) @app.route("/todo_apps/detail/<int:t_id>") def show_detail(t_id): """ タスク詳細画面を表示する """ todo = business_logic.get_todo(t_id) return render_template("detail.html", todo=todo) @app.route("/todo_apps/edit/<int:t_id>") def show_edit(t_id): """ 編集画面を表示する """ categories = business_logic.get_category_all() todo = business_logic.get_todo(t_id) return render_template("edit.html", categories=categories, todo=todo) @app.route("/todo_apps/edit/done", methods=["POST"]) def edit_todo(): """ タスクを編集する """ # 入力値検証 error_dict = {} is_valid = validate_views.validate_input_todo(request.form, error_dict) if not is_valid: # 入力エラーの場合 categories = business_logic.get_category_all() todo = get_todo(request.form) return render_template("edit.html", categories=categories, error_dict=error_dict, todo=todo) else: # タスク更新 is_valid = business_logic.update_todo(request.form) if not is_valid: # DBエラーの場合 categories = business_logic.get_category_all() error_msg = "タスクの更新に失敗しました。時間を空けて再度実行してください。" todo = get_todo(request.form) return render_template("edit.html", categories=categories, error_msg=error_msg, todo=todo) else: return redirect(url_for("show_detail", t_id=request.form["id"])) def get_todo(form): """ タスク情報を返却する """ todo = { "id": form.get("id"), "title": form.get("title", ""), "category_id": form.get("category", ""), "content": form.get("content", ""), "memo": form.get("memo", ""), "due_date": form.get("due_date", "") } return todo @app.route("/todo_apps/delete/<int:t_id>") def delete_todo(t_id): """ タスクを削除する """ is_valid = business_logic.delete_task(t_id) if not is_valid: # 削除エラーの場合 return redirect(url_for("show_error", error_id=consts.DB_ERROR)) else: return redirect(url_for('top')) @app.route("/todo_apps/error") def show_error(): session.clear() error_id = request.args.get("error_id", "") if error_id == consts.DB_ERROR: # DBエラーの場合 error_msg = "DBを更新中にエラーが発生しました。時間を空けて、再度ログインからやりなおしてください。" else: error_msg = "エラーが発生しました。時間を空けて、再度ログインからやりなおしてください。" return render_template("error.html", error_msg=error_msg) if __name__ == '__main__': # 8080ポートで起動 app.run(port=8080, debug=True)
consts.py
# ステータス:未完了 STATUS_INCOMPLETE = 0 # ステータス:完了 STATUS_COMPLETE = 1 # 日付変換フォーマット DATE_FORMAT = "%Y-%m-%d" # エラー画面フラグ DB_ERROR = "1"
top.html
<!DOCTYPE html> <html> <head> <title>TODOトップ</title> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/5.0.0-alpha1/css/bootstrap.min.css"> </head> <body> <div class="container mt-5"> <h2 class="text-center">{{ session["name"] }} さんのタスク一覧</h2> <a href="{{ url_for('show_create') }}" class="btn btn-primary mr-3">タスク登録</a> <a href="{{ url_for('logout') }}" class="btn btn-primary">ログアウト</a> <div class="card mt-4"> <div class="card-body"> {% for msg in errors %} <div id="error-message" class="text-danger mb-3">{{ msg }}</div> {% endfor %} <table class="table table-striped"> <thead> <tr> <th>カテゴリ</th> <th>タイトル</th> <th>タスク名</th> <th>期日</th> <th>ステータス</th> <th>アクション</th> </tr> </thead> <tbody> {% for todo_item in todos %} <tr> <td>{{ todo_item["category"] }}</td> <td>{{ todo_item["title"] }}</td> <td><a href="{{ url_for('show_detail', t_id=todo_item['id']) }}">{{ todo_item["content"] }}</a></td> <td>{{ todo_item["due_date"] }}</td> <td>{{ todo_item["status"] }}</td> <td><a href="{{ url_for('update_todo_done', t_id=todo_item['id']) }}" class="btn btn-success">完了</a></td> </tr> {% endfor %} </tbody> </table> </div> </div> </div> </body> </html>
detail.html
<!DOCTYPE html> <html> <head> <title>TODO詳細</title> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/5.0.0-alpha1/css/bootstrap.min.css"> </head> <body> <div class="container mt-2"> <a href="{{ url_for('top') }}" class="btn btn-primary mb-3">戻る</a> <h2 class="text-center">TODO詳細</h2> <div class="card mt-4 border"> <div class="card-body"> <div class="row border p-2 mb-2"> <div class="col-sm-3"><h5>カテゴリ:</h5></div> <div class="col-sm-9"><p>{{ todo["category"] }}</p></div> </div> <div class="row border p-2 mb-2"> <div class="col-sm-3"><h5>タイトル:</h5></div> <div class="col-sm-9"><p>{{ todo["title"] }}</p></div> </div> <div class="row border p-2 mb-2"> <div class="col-sm-3"><h5>タスク内容:</h5></div> <div class="col-sm-9"><p>{{ todo["content"] }}</p></div> </div> <div class="row border p-2 mb-2"> <div class="col-sm-3"><h5>ステータス:</h5></div> <div class="col-sm-9"><p>{{ todo["status"] }}</p></div> </div> <div class="row border p-2 mb-2"> <div class="col-sm-3"><h5>メモ:</h5></div> <div class="col-sm-9"> {% if todo["memo"] %} <p>{{ todo["memo"].replace("\r\n", "<br>") | safe }}</p> {% else %} <p></p> {% endif %} </div> </div> <div class="row border p-2 mb-2"> <div class="col-sm-3"><h5>期日:</h5></div> <div class="col-sm-9"><p>{{ todo["due_date"] }}</p></div> </div> <div class="d-flex justify-content-center mt-2"> <a href="{{ url_for('show_edit', t_id=todo['id']) }}" class="btn btn-primary mb-3 mr-2">編集する</a> <a href="{{ url_for('delete_todo', t_id=todo['id']) }}" class="btn btn-danger mb-3">削除する</a> </div> </div> </div> </div> </body> </html>
edit.html
<!DOCTYPE html> <html> <head> <title>タスク編集</title> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/5.0.0-alpha1/css/bootstrap.min.css"> </head> <body> <div class="container"> <div class="row justify-content-center mt-3"> <div class="col-md-8"> <a href="{{ url_for('show_detail', t_id=todo.get('id')) }}" class="btn btn-primary mb-3">戻る</a> <div class="card"> <div class="card-header text-center"> <h4>タスク編集</h4> </div> <div class="card-body"> <div id="error-message" class="text-danger mb-3">{{ error_msg }}</div> <form method="post" action="{{ url_for('edit_todo') }}"> <div class="form-group"> <label for="category">カテゴリ<span class="text-primary ml-3">※必須</span></label> <select class="form-control" id="category" name="category"> <option value="">選択してください</option> {% for category in categories %} <option value="{{category.id}}" {% if category.id|string()==todo.get('category_id') %} selected {% endif %}>{{ category.category_name }}</option> {% endfor %} </select> </div> {% if error_dict and error_dict["category"] %} <div id="error-message" class="text-danger mb-3">{{ error_dict["category"] }}</div> {% endif %} <div class="form-group mt-3"> <label for="title">タイトル <span class="text-primary ml-3">※必須</span></label> <input type="text" class="form-control" id="title" name="title" value="{{ todo.get('title', '') }}"> </div> {% if error_dict and error_dict["title"] %} <div id="error-message" class="text-danger mb-3">{{ error_dict["title"] }}</div> {% endif %} <div class="form-group mt-3"> <label for="task">タスク内容 <span class="text-primary ml-3">※必須</span></label> <input type="text" class="form-control" id="content" name="content" value="{{ todo.get('content', '') }}"> </div> {% if error_dict and error_dict["content"] %} <div id="error-message" class="text-danger mb-3">{{ error_dict["content"] }}</div> {% endif %} <div class="form-group mt-3"> <label for="memo">メモ</label> <textarea class="form-control" id="memo" name="memo" rows="3">{{ todo.get('memo', '') }}</textarea> </div> <div class="form-group mt-3"> <label for="due_date">タスク期日<span class="text-primary ml-3">※必須</span></label> <input type="date" class="form-control" id="due_date" name="due_date" value="{{ todo.get('due_date', '') }}"> </div> {% if error_dict and error_dict["due_date"] %} <div id="error-message" class="text-danger mb-3">{{ error_dict["due_date"] }}</div> {% endif %} <button type="submit" class="btn btn-primary mt-3">更新</button> <input type="hidden" name="id" value="{{ todo.get('id') }}"> </form> </div> </div> </div> </div> </div> </body> </html>
error.html
<!DOCTYPE html> <html> <head> <title>処理続行エラー</title> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/5.0.0-alpha1/css/bootstrap.min.css"> </head> <body> <div class="container mt-5 p-3 card border"> <h2 class="text-center">処理続行エラー</h2> <div class="mt-4"> <div class="card-body"> <div class="p-2 mb-3 text-center"> <p>{{ error_msg }}</p> </div> <div class="d-flex justify-content-center mt-3"> <a href="{{ url_for('show_login') }}" class="btn btn-primary mb-3">ログイン</a> </div> </div> </div> </div> </body> </html>