はじめに#
「OKAZAKI Shogo のひとりアドベントカレンダー2024」の10日目です。 ファイルアップロードすると同時に、 DB へのレコード登録をする機能と、登録したファイル情報の編集する機能を実装します。
アップロードされたファイル情報を登録する(INSERT)#
regist_file.py
に以下の機能を追加する:
- アップロードされたファイルサイズを計算する(
__get_file_size()
) - 現在時刻を取得し、登録日時と更新日時にセットする
- 入力された情報と日時の情報を File オブジェクトにセットし、DB に登録する
db.session.add()
で INSERT 文発行相当の処理を行うdb.session.commit()
でトランザクションをコミットする
1from typing import Optional
2from datetime import datetime, timedelta, timezone
3import os
4from flask import Blueprint, request, flash, redirect, url_for
5from werkzeug.utils import secure_filename
6from werkzeug.datastructures import FileStorage
7from app.models.file import File
8from instance.config import dev
9from app import db
10
11JST = timezone(timedelta(hours=+9), "JST")
12
13regist_file_bp = Blueprint("regist_file", __name__, url_prefix="/regist-file")
14
15
16def __get_file_extension_if_allowed(filename: str) -> Optional[str]:
17 # ファイル名に拡張子が含まれているかチェック
18 if "." in filename:
19 # 拡張子を取得(ドット以降)
20 extension = filename.rsplit(".", 1)[1].lower()
21 # 許容される拡張子かどうかを確認
22 if extension in dev.ALLOWED_EXTENSIONS:
23 return extension
24 return None
25
26# ---------- ここから新規追加 -----------
27def __get_file_size(uploaded_file: FileStorage) -> str:
28 file_size_bytes = len(uploaded_file.read())
29 # ファイルの読み取り位置をリセット
30 uploaded_file.stream.seek(0)
31
32 # ファイルサイズを適切な単位で表示
33 if file_size_bytes >= 1_000_000:
34 size_str = f"{file_size_bytes / 1_000_000:.2f} MB"
35 elif file_size_bytes >= 1_000:
36 size_str = f"{file_size_bytes / 1_000:.2f} KB"
37 else:
38 size_str = f"{file_size_bytes} bytes"
39
40 return size_str
41# ---------- ここまで新規追加 -----------
42
43@regist_file_bp.route("/", methods=["POST"])
44def index():
45 is_file_check_ok = True
46 file = request.files["file"]
47
48 if not file:
49 flash("ファイルが選択されていません")
50 is_file_check_ok = False
51
52 if request.form["title"] == "":
53 flash("タイトルが入力されていません")
54 is_file_check_ok = False
55
56 origin_filename = file.filename
57 ext = __get_file_extension_if_allowed(origin_filename)
58 if ext is None:
59 flash("ファイルの拡張子が正しくありません")
60 is_file_check_ok = False
61
62 if is_file_check_ok:
63 # ---------- ここから新規追加 -----------
64 now = datetime.now(JST)
65 filename = secure_filename(now.strftime("%Y%m%d_%H%M%S_") + origin_filename)
66 now_str = now.strftime("%Y-%m-%d %H:%M:%S")
67
68 title = request.form["title"]
69 description = request.form["description"]
70 file_size = __get_file_size(file)
71
72 upload_file = File(
73 file_name=filename,
74 display_name=title,
75 url=f"http://example.com/{filename}",
76 file_type=ext,
77 size=file_size,
78 description=description,
79 tag="example, test",
80 is_standard=0,
81 created_at=now_str,
82 created_by="admin",
83 updated_at=now_str,
84 updated_by="admin",
85 )
86
87 db.session.add(upload_file)
88 db.session.commit()
89 # ---------- ここまで新規追加 -----------
90
91 file.save(os.path.join(dev.UPLOAD_FOLDER, filename))
92 flash(f"{origin_filename}のアップロードが完了しました!")
93
94 return redirect(url_for("regist_file_form.index"))
ファイル情報の編集画面を作成する(SELECT, UPDATE)#
管理者用画面で以下のような遷移で編集できるようにする
- ファイル一覧を表示
- 選択したファイルを選択するとそのファイル編集画面に遷移する
- ファイル編集画面で「送信」を押すと、ファイル情報を更新する
先に完成した時の動作例を示す:
↑で「1」のリンクをクリック
説明を更新した上で「送信」をクリック
一覧に戻ると説明が更新されている
manage_file_list.html
#
管理者向けファイル一覧を表示するテンプレートを作成する。
ID のところは次のページに GET パラメータとして ID を渡して選択したファイルの情報を表示できるようにする。
1{% extends "layout.html" %}
2
3{% block content %}
4<div>
5 <h2>ファイル一覧(管理者向け)</h2>
6 <table>
7 <thead>
8 <tr>
9 <th scope="col"><font>No</font></th>
10 <th scope="col"><font>登録日</font></th>
11 <th scope="col"><font>更新日</font></th>
12 <th scope="col"><font>ファイル種類</font><br><font>サイズ</font></th>
13 <th scope="col"><font>タイトル</font><br><font size="2">説明</font></th>
14 </tr>
15 </thead>
16 <tbody>
17 {% for file in files %}
18 <tr>
19 <td><a href="/manage-file?id={{ file.file_id }}">{{ file.file_id }}</a></td>
20 <td>{{ file.created_at }}</td>
21 <td>{{ file.updated_at }}</td>
22 <td>{{ file.file_type }}<br>{{ file.size }}</td>
23 <td><a href={{ file.url }}>{{ file.display_name }}</a><br>{{ file.description }}</td>
24 </tr>
25 {% endfor %}
26 </tbody>
27 </table>
28</div>
29{% endblock %}
manage_file_list.py
#
単純に、先ほどのページを表示できるようにしているだけ。
1from flask import Blueprint, render_template
2
3from app.models.file import File
4
5
6manage_file_list_bp = Blueprint("manage_file_list_bp", __name__, url_prefix="/manage-file-list")
7
8
9@manage_file_list_bp.route("/", methods=["GET"])
10def index():
11 files = File.query.all()
12 return render_template("manage_file_list.html", files=files)
manage_file.html
#
選択したファイルを編集できる画面。 GET パラメータで受け取った ID のファイルを表示する。フォームのアクションは /update-file
を指定する。
1{% extends "layout.html" %}
2
3{% block content %}
4<div>
5 <h2>ファイル編集(管理者向け)</h2>
6 <form method="post" action="/update-file" enctype=multipart/form-data class="update-file-form">
7 {% with messages = get_flashed_messages() %}
8 {% if messages %}
9 <ul>
10 {% for message in messages %}
11 <li>{{ message }}</li>
12 {% endfor %}
13 </ul>
14 {% endif %}
15 {% endwith %}
16
17 <div class="regist-file-form">
18 <input type="hidden" name="file_id" id="file_id" value={{ file.file_id }} />
19 <label for="file_id">ファイル ID: </label><font>{{ file.file_id }}</font>
20 </div>
21 <div class="regist-file-form">
22 <label for="title">タイトル: </label>
23 <input type="text" name="title" id="title" value={{ file.display_name }} />
24 </div>
25 <div class="regist-file-form">
26 <label for="description">説明: </label>
27 <input type="text" name="description" id="description" value={{ file.description }} />
28 <br>タイトルで内容が十分わかるときは、説明を入力しなくても構いません。
29 </div>
30 <div class="regist-file-form">
31 <input type="submit" value="送信" />
32 </div>
33 </form>
34</div>
35{% endblock %}
manage_file.py
#
request.args.get("id")
で GET パラメータ id
で指定された値を取得する。指定された値を元にファイル情報を取得する。
File.query.filter(File.file_id == file_id).all()[0]
で指定した ID のファイル情報を取得する(SELECT 文を発行する処理に相当)。- 今回は 1 件しか取得できない前提。
1from flask import Blueprint, render_template, request
2
3from app.models.file import File
4
5
6manage_file_bp = Blueprint("manage_file_bp", __name__, url_prefix="/manage-file")
7
8
9@manage_file_bp.route("/", methods=["GET"])
10def index():
11 file_id = request.args.get("id")
12 file = File.query.filter(File.file_id == file_id).all()[0]
13 return render_template("manage_file.html", file=file, file_id=id)
update_file.py
#
編集画面から送信された内容を処理する。
file.[パラメータ名] = [値]
で当該フィールドの内容を更新する- 更新した内容は
db.session.commit()
でコミットされる
1from datetime import datetime, timedelta, timezone
2from flask import Blueprint, flash, redirect, render_template, request, url_for
3
4from app import db
5from app.models.file import File
6
7
8JST = timezone(timedelta(hours=+9), "JST")
9
10
11update_file_bp = Blueprint("update_file", __name__, url_prefix="/update-file")
12
13
14@update_file_bp.route("/", methods=["POST"])
15def index():
16 # 更新したいデータを取得
17 file_id = request.form["file_id"]
18 print(file_id)
19 file = File.query.get(file_id)
20 if not file:
21 return "File not found", 404
22
23 # リクエストデータから更新内容を取得
24 title = request.form["title"]
25 description = request.form["description"]
26
27 # 更新内容でデータを更新
28 file.display_name = title
29 file.description = description
30 file.updated_at = datetime.now(JST).strftime("%Y-%m-%d %H:%M:%S")
31
32 # データベースにコミット
33 db.session.commit()
34
35 flash(f"ファイル ID: {file.file_id}の更新が完了しました!")
36
37 return redirect(url_for("manage_file_bp.index", id=file_id))