【Python特化】おすすめのオンラインプログラミングスクール

【Flask】Webアプリ開発におけるViewの役割

flask-mvt-view
本記事の要点
  • FlaskにおけるViewの役割を理解する
  • Modelとの連携を理解する
  • Templateとの連携を理解する
  • 具体的にViewへ記述するコードを理解する

上記をまとめ、具体的なコードやViewの基本知識が習得できます。

目次

FlaskにおけるViewの役割とは

Flaskは、UIを持つアプリを実装するデザインパターンとしてMVTモデル(Model/View/Template)を採用してます。

Model/View/Templateは以下の役割を持ちます。

MVTの役割
  • Model:ロジックを担当
  • View:入力を受け取りModelとTemplateを制御
  • Template:入出力を担当

一般的に、MVCモデル(Model/View/Controller)が有名ですが、MVTのViewはMVCのControllerに相当し、MVTのTemplateはMVCのViewに相当します。

具体的なルーティングとして図解を記載しておきます。

flask-mvt-view
flask-mvt-view

また、FlaskにおいてViewは以下の役割を持ちます。

Viewの具体的な役割
  • データベースを操作する
  • PRGに基づいてルーティングとテンプレートを制御する

つまり、ViewはModelとTemplateを制御することになります。

Viewを実装する際に必要な知識が以下の内容になります。

Viewに必要な基本知識
  • CRUD機能の理解
  • ルーティングの理解
  • PRG(Post/Redirect/Get)パターン
  • render_template
  • redirect
  • url_for
  • request

本記事は、規模の小さいToDoアプリを想定しディレクトリ構成しています。

FlaskにおけるModelの役割

FlaskにおいてModelは以下の役割を持ちます。

Modelの具体的な役割
  • データベースを作成する
  • データベースを操作する

つまり、Modelはデータベースのテーブルを定義することになります。

データベースを構築する際に必要な知識が以下の内容になります。

Modelに必要な基本知識
  • flask-sqlalchemy
  • flask-migrate
  • SQLite

FlaskにおけるModelの役割について詳細に知りたい人は、「【Flask】Webアプリ開発におけるModelの役割」を一読ください。

FlaskにおけるTemplateの役割

FlaskにおいてTemplateは以下の役割を持ちます。

Templateの具体的な役割
  • ViewからTemplateへのデータの受け渡し
  • ルーティング情報に沿ったテンプレート作成(各ページ)
  • <a>タグ, <form>タグ, <button>タグが持つルートデザイン設計
  • CSSによるUI/UXを意識したデザイン調整

つまり、Templateはユーザー行動を意識したデザインを考慮することになります。

Templateを実装する際に必要な知識が以下の内容になります。

Templateの具体的な役割
  • ルーティングの理解
  • テンプレートエンジン(jinja2)の理解
  • 共通テンプレートと継承
  • データ出力のロジック設計

Templateの作成方法や共通化/継承など具体的に知りたい人は、「【Flask】Webアプリ開発におけるTemplateの役割」を一読ください。

CRUD機能とは

CRUD(クラッド)とは、データベースを操作する上で最低限必要な機能になります。

CRUDの各機能
  • Create(データの作成)
  • Read(データの読み込み)
  • Update(データの更新)
  • Delete(データの削除)

上記の頭文字を取った略称がCRUDになります。

CRUD機能を理解すると、様々なアプリに利用できます。

ルーティングとは

ルーティングとは、リクエスト先のURIと実際に処理する関数を紐づけることを指します。

Flaskでは、関数の先頭にデコレータと呼ばれる関数@app.route()を追加することでルートを追加できます。

Flaskにてルーティングを利用する場合、以下の基本的な使い方を理解しましょう。

Flaskにおけるルーティングの使い方
  • ルーティングを設定する
  • flask routesコマンドでルーティング情報を確認する
  • FlaskのEndpoint(エンドポイント)を設定する
  • Ruleに変数を設定する

サンプルをもとに詳しくルーティングを知りたい人は、「【Python】Flaskによるルーティングの利用方法とテンプレート活用」を一読ください。

PRG(Post/Redirect/Get)とは

PRGとは、POST/REDIRECT/GETの頭文字を取った略称です。

PRGの一般的な流れ
  1. フォームデータをPOST(送信)しViewで受け取る
  2. Viewでデータ処理後にREDIRECT(再読み込み)
  3. GETで再読み込みしたデータやテンプレートを表示する

PRGパターンを使わない場合、フォームデータをPOSTし再ロードすると、元のPOSTしたコンテンツが再送信されフォームデータが二重で送信される可能性があります。

上記の問題を回避するために、多くのケースはPRGパターンを使います。

render_templateとは

render_template()関数は、テンプレートエンジンを使ってHTMLをレンダリング(描画)するために利用します。

render_template()関数にテンプレート名とキーワード引数として変数を渡すことで利用できます。

redirectとは

redirectは、WebサイトやページなどのURL変更/データ更新など実行した際、自動的に別のURLに転送する仕組みです。

url_forとは

url_for関数は、ルーティング情報であるエンドポイントのURLを利用する際に使います。

具体的な利用方法は後述しています。

requestとは

requestは、リクエスト情報を取得するために利用します。

requestオブジェクトを利用することで、request.methods属性を利用してリクエストされたメソッドをチェックしたり、データを取得を実施します。

MVTモデルを考慮したディレクトリ構成

本記事では、サンプルとしてFlaskによるMVTモデルに沿ったToDoアプリを想定します。

Flaskは自由度が高いため、様々なディレクトリ構成を組めますが一例として参考にしてください。

flask-project
├── .env
├── apps
│   ├── app.py
│   ├── static
│   │   └── bootstrap.min.css
│   └── todo
│       ├── __init__.py
│       ├── forms.py
│       ├── models.py
│       ├── static
│       │   └── style.css
│       ├── templates
│       │   └── todo
│       │       ├── base.html
│       │       ├── index.html
│       │       ├── todo_create.html
│       │       ├── todo_edit.html
│       │       ├── todo_done.html
│       │       └── todo_delete.html
│       └── views.py
├── local.sqlite
└── migrations

様々なディレクトリと.py, .html, .css等のファイルがありますが、views.pyを中心に解説します。

CRUD機能の実装

前提として本プログラムでは、テンプレートにおいて以下の流れを組んでいます。

ToDoアプリのページ構成
  • 全ページのベースファイル(base.html)
  • TOPページ(index.html)
  • タスク作成ページ(todo_create.html)
  • タスク編集ページ(todo_edit.html)
  • タスク完了ページ(todo_done.html)
  • ゴミ箱ページ(todo_delete.html)
TOPページの構成

以下のページがTOPページになります。

また、上部ナビゲーションバーのトグルをクリックすると左側からサイドバーが表示される仕様です。

サイドバーに表示する各ページは以下になります。

サイドバーの各ページ
  • 現在のタスク(index.html)
  • 完了したタスク(todo_done.html)
  • ゴミ箱(todo_delete.html)
タスク作成ページの構成

以下のページがタスク作成ページになります。

タスク作成時、タイトルとテキストエリアの入力がない場合にバリデーションチェックしています。

バリデーションチェックにおけるファイルとしてforms.pyを作成していますが、バリデーションチェックの言及は記事の目的と異なるため行いません。

チェックボタンを押下すると、データが作成されリダイレクトによってTOPページに遷移する仕様です。

タスク編集ページの構成

TOPページに表示される作成されたタスクカードには、以下の機能を追加しています。

タスクカードの各機能
  • 編集機能
  • 完了機能
  • ゴミ箱機能

編集機能であるアイコンボタン(左)を押下すると、タスク編集ページに遷移します。

右下のチェックボタンを押下すると、データの更新が行われてTOPページに遷移する仕様です。

タスク完了ページの構成

タスクカードの完了機能であるアイコンボタン(真ん中)を押下すると、データが更新されTOPページからタスクが消える仕様です。

タスク完了後は、完了したタスクページに遷移すると表示される仕様です。

タスク完了ページから、タスク未完了に戻したい場合もアイコンを押下すると未完了の状態に戻ります。

ゴミ箱ページの構成

タスクカードのゴミ箱機能であるアイコンボタン(右)を押下すると、データが更新され各ページからタスクが消える仕様です。

ゴミ箱ページに遷移するとタスクが表示される仕様です。

また、右下の削除ボタンを押下すると削除用のモーダルウィンドウが表示されます。

データの削除を選択すると、タスクが削除されます。

全ページのベースファイルであるbase.htmlがありますが、共通テンプレートとしてナビゲーションバーを継承しています。

ただし、Templateの言及は本記事の目的と異なるため行いません。

また、Viewで実装する基本的な項目は以下になります。

Viewで実装する項目
  • render_template, redirect, url_for, requestのインポート
  • デコレータ関数によるエンドポイント作成
  • データベースの操作
  • render_templateによるテンプレート呼び出し
  • redirect, url_forの実装
  • リクエスト情報の処理

ToDoアプリにおける初期画面の処理を実装する

初期表示で実装する項目は以下になります。

@todo.route("/", methods=["GET", "POST"])
def tasks():
    # データを降順ソート
    tasks = Tasks.query.order_by(Tasks.id.desc()).all()
    # 未完了/完了ボタン押下後の処理を追加
    if request.method == "POST":
        # Tasksモデルを利用してタスクを取得する
        tasks_id = request.args.get("tasks_id")
        flag = request.form["flag"]
        tasks = Tasks.query.filter_by(id=tasks_id).first()
        # ボタン押下後flagが"0"の場合
        if flag == "0":
            # 完了したタスクを更新し現在のタスク一覧画面にリダイレクト
            tasks.status = 1
            db.session.add(tasks)
            db.session.commit()
            return redirect(url_for("todo.tasks"))
        # ゴミ箱ボタン押下後flagが"1"の場合
        if flag == "1":
            # 削除対象のタスクを更新し現在のタスク一覧画面にリダイレクト
            tasks.delete = 1
            db.session.add(tasks)
            db.session.commit()
            return redirect(url_for("todo.tasks"))
    return render_template("todo/index.html", tasks=tasks)
base.html
<!doctype html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>ToDoApp</title>
    <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" />
    <link rel="stylesheet" href="{{ url_for('static', filename='css/bootstrap.min.css') }}" />
    <link rel="stylesheet" href="{{ url_for('todo.static', filename='css/style.css') }}" />
  </head>
  <body>
    <nav class="navbar navbar-dark bg-dark">
      <div class="container-fluid">
        <button class="navbar-toggler" type="button" data-bs-toggle="offcanvas" data-bs-target="#offcanvasDarkNavbar" aria-controls="offcanvasDarkNavbar" aria-label="Toggle navigation">
          <span class="navbar-toggler-icon"></span>
        </button>
        <a class="navbar-brand" href="/todo">ToDoApp</a>
        <div class="offcanvas offcanvas-start text-bg-dark" tabindex="-1" id="offcanvasDarkNavbar" aria-labelledby="offcanvasDarkNavbarLabel">
          <div class="offcanvas-header">
            <h5 class="offcanvas-title" id="offcanvasDarkNavbarLabel">ToDoApp</h5>
            <button type="button" class="btn-close btn-close-white" data-bs-dismiss="offcanvas" aria-label="Close"></button>
          </div>
          <div class="offcanvas-body">
            <ul class="navbar-nav justify-content-end flex-grow-1 pe-3">
              <li class="nav-item">
                <a class="nav-link d-flex align-items-center" href="/todo">
                  <span class="material-symbols-outlined pe-2">check_box_outline_blank</span>
                  現在のタスク
                </a>
              </li>
              <li class="nav-item">
                <a class="nav-link d-flex align-items-center" href="/todo/done">
                  <span class="material-symbols-outlined pe-2">check_box</span>
                  完了したタスク
                </a>
              </li>
              <li class="nav-item">
                <a class="nav-link d-flex align-items-center" href="/todo/delete">
                  <span class="material-symbols-outlined pe-2">delete</span>
                  ゴミ箱
                </a>
              </li>
            </ul>
          </div>
        </div>
      </div>
    </nav>
    {% block content %}{% endblock %}
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script>
  </body>
</html>
index.html
{% extends "todo/base.html" %}
{% block content %}
{% for task in tasks %}
{% if task.status == 0 and task.delete == 0 %}
<div class="card mx-2 my-2">
  <div class="card-header align-items-center">
    <h5 class="card-title">{{ task.title }}</h5>
  </div>
  <div class="card-body">
    <p class="card-text" style="white-space:pre-wrap;">{{ task.content }}</p>
    <hr>
    <div class="card-btn-group d-flex align-items-center justify-content-end">
      <a class="btn d-inline-flex align-items-center" href="{{ url_for('todo.edit_tasks', tasks_id=task.id) }}" style="color: #198754;">
        <span class="material-symbols-outlined">edit</span>
      </a>
      <form action="{{ url_for('todo.tasks', tasks_id=task.id) }}" method="POST" class="mx-1">
        <button type="submit" name="flag" value="0" class="btn d-inline-flex align-items-center" style="color: #0d6efd;">
          <span class="material-symbols-outlined">radio_button_unchecked</span>
        </button>
      </form>
      <form action="{{ url_for('todo.tasks', tasks_id=task.id) }}" method="POST" class="mx-1">
        <button type="submit" name="flag" value="1" class="btn d-inline-flex align-items-center">
          <span class="material-symbols-outlined">delete</span>
        </button>
      </form>
    </div>
  </div>
</div>
{% endif %}
{% endfor %}
<div class="fab-container">
  <a href="/todo/create" class="pushcircle">
    <span class="material-symbols-outlined">add</span>
  </a>       
</div>
{% endblock %}

BlueprintとModelの具体的な解説は割愛しています。

views.pyのtasks関数の流れ
  • デコレータ関数によるルート作成とメソッドの許可
  • if文によるPOST時の各処理
  • タスクカードの各処理(編集/完了/ゴミ箱)とリダイレクト
  • render_templateによるGET時のテンプレート呼び出しとキーワード引数

Viewの役割としてModelとTemplateを制御するため、上記のようにデータベース操作とテンプレート側の処理を考慮する必要があります。

index.htmlでは、未完了のタスクのみを表示するため、flagを用意し”0″”1″の判定によって各カラムの値を変更しています。

また、データベースの更新後にurl_forが指定する関数を呼び出すためにredirect(リダイレクト)しています。

ToDoアプリにおけるタスク作成画面の処理を実装する

タスク作成画面で実装する項目は以下になります。

@todo.route("/create", methods=["GET", "POST"])
def create_tasks():
    # TasksFormをインスタンス化
    tasksForm = TasksForm()
    # フォームの値をバリデートする
    if tasksForm.validate_on_submit():
        # tasksを作成
        tasks = Tasks(
            title = tasksForm.title.data,
            content = tasksForm.content.data,
        )
        # tasksを追加してコミットする
        db.session.add(tasks)
        db.session.commit()
        # 現在のタスク一覧画面へリダイレクト
        return redirect(url_for("todo.tasks"))
    return render_template("todo/todo_create.html", form=tasksForm)
todo_create.html
{% extends "todo/base.html" %}
{% block content %}
<form action="{{ url_for('todo.create_tasks') }}" method="POST" novalidate="novalidate">
  {{ form.hidden_tag() }}
  <div class="card mx-2 my-2">
    <div class="card-header align-items-center">
      <h5 class="card-title">
        {{ form.title(class="form-control", placeholder="タスク名") }}
      </h5>
      {% for error in form.title.errors %}
      <span style="color: red;">{{ error }}</span>
      {% endfor %}
    </div>
    <div class="card-body">
      {{ form.content(class="form-control", placeholder="タスク内容", style="white-space:pre-wrap;") }}
      {% for error in form.content.errors %}
      <span style="color: red;">{{ error }}</span>
    {% endfor %}
    </div>
  </div>
  <div class="fab-container">
    <div class="pushcircle">
      {{ form.submit(class="form-create-btn material-symbols-outlined") }}
    </div>
  </div>
</form>
{% endblock %}
forms.py
from flask_wtf import FlaskForm
from wtforms import StringField, TextAreaField, SubmitField
from wtforms.validators import DataRequired, length

# tasks作成時と編集時のformクラス
class TasksForm(FlaskForm):
    # tasksフォームのtitle属性のラベルとバリデータを設定
    title = StringField(
        "タイトル名",
        validators = [
            DataRequired(message="タイトル名は必須です"),
            length(max=30, message="30文字以内で入力してください"),
        ],
    )
    # tasksフォームのcontent属性のラベルとバリデータを設定
    content = TextAreaField(
        "タスク内容",
        validators = [
            DataRequired(message="タスク内容は必須です"),
        ],
    )
    # tasksフォームsubmitの文言設定
    submit = SubmitField("check")

forms.pyの具体的な解説は割愛しています。

views.pyのcreate_tasks関数の流れ
  • デコレータ関数によるルート作成とメソッドの許可
  • if文によるPOST時の処理(formによるバリデーションチェック)
  • タスクカードの作成(データベースの作成)
  • render_templateによるGET時のテンプレート呼び出しとキーワード引数

こちらもデコレータ関数による/createといったエンドポイントを作成し、メソッド(GET, POST)を許可しています。

flask-wtfによるバリデーションチェックで問題なければデータベースにてタスク作成しています。

また、データベースの更新後にurl_forが指定する関数を呼び出すためにredirect(リダイレクト)しています。

ToDoアプリにおけるタスク編集画面の処理を実装する

タスク編集画面で実装する項目は以下になります。

@todo.route("/edit/<tasks_id>", methods=["GET", "POST"])
def edit_tasks(tasks_id):
    # TasksFormをインスタンス化
    tasksForm = TasksForm()
    # Tasksモデルを利用してタスクを取得する
    tasks = Tasks.query.filter_by(id=tasks_id).first()
    # formからsubmitされた場合はTasksを更新し現在のタスク一覧画面にリダイレクト
    if tasksForm.validate_on_submit():
        tasks.title = tasksForm.title.data
        tasks.content = tasksForm.content.data
        db.session.add(tasks)
        db.session.commit()
        return redirect(url_for("todo.tasks"))
    # GETの場合は編集用HTMLファイルを返す
    return render_template("todo/todo_edit.html", tasks=tasks, form=tasksForm)
todo_edit.html
{% extends "todo/base.html" %}
{% block content %}
<form action="{{ url_for('todo.edit_tasks', tasks_id=tasks.id) }}" method="POST" novalidate="novalidate">
  {{ form.hidden_tag() }}
  <div class="card mx-2 my-2">
    <div class="card-header align-items-center">
      <h5 class="card-title">
        {{ form.title(class="form-control", placeholder="タスク名", value=tasks.title) }}
      </h5>
      {% for error in form.title.errors %}
      <span style="color: red;">{{ error }}</span>
      {% endfor %}
    </div>
    <div class="card-body">
      {% set f = form.content.process_data(tasks.content) %}
      {{ form.content(class="form-control", placeholder="タスク内容") }}
      {% for error in form.content.errors %}
      <span style="color: red;">{{ error }}</span>
    {% endfor %}
    </div>
  </div>
  <div class="fab-container">
    <div class="pushcircle">
      {{ form.submit(class="form-create-btn material-symbols-outlined") }}
    </div>
  </div>
</form>
{% endblock %}
views.pyのedit_tasks関数の流れ
  • デコレータ関数によるルート作成/変数指定とメソッドの許可
  • if文によるPOST時の処理(formによるバリデーションチェック)
  • タスクカードの編集(データベースの更新)
  • render_templateによるGET時のテンプレート呼び出しとキーワード引数

編集画面ではURIパラメータによるデータ取得(url_forによるデータの受け渡し)のため、関数に引数を設定しています。

また、URIパラメータを利用する場合、エンドポイントに変数<tasks_id>を設置する必要があります。

編集においてもバリデーションチェック後に、データベースの更新を実施しています。

データベースの更新後にurl_forが指定する関数を呼び出すためにredirect(リダイレクト)しています。

ToDoアプリにおけるタスク完了画面の処理を実装する

タスク完了画面で実装する項目は以下になります。

@todo.route("/done", methods=["GET", "POST"])
def done_tasks():
    # データを降順ソート
    tasks = Tasks.query.order_by(Tasks.id.desc()).all()
    # 未完了/完了ボタン押下後の処理を追加
    if request.method == "POST":
        # Tasksモデルを利用してタスクを取得する
        tasks_id = request.args.get("tasks_id")
        flag = request.form["flag"]
        tasks = Tasks.query.filter_by(id=tasks_id).first()
        # ボタン押下後tasks.statusが"1(完了)"の場合
        if flag == "0":
            # 未完了に戻したTasksを更新し完了したタスク一覧画面にリダイレクト
            tasks.status = 0
            db.session.add(tasks)
            db.session.commit()
            return redirect(url_for("todo.done_tasks"))
        # ゴミ箱ボタン押下後tasks.deleteが"0(削除)"の場合
        if flag == "1":
            # Tasks.deleteを更新し現在のタスク一覧画面にリダイレクト
            tasks.delete = 1
            db.session.add(tasks)
            db.session.commit()
            return redirect(url_for("todo.done_tasks"))
    return render_template("todo/todo_done.html", tasks=tasks)
todo_done.html
{% extends "todo/base.html" %}
{% block content %}
{% for task in tasks %}
{% if task.status == 1 and task.delete == 0 %}
<div class="card mx-2 my-2">
  <div class="card-header align-items-center">
    <h5 class="card-title">{{ task.title }}</h5>
  </div>
  <div class="card-body">
    <p class="card-text" style="white-space:pre-wrap;">{{ task.content }}</p>
    <hr>
    <div class="card-btn-group d-flex align-items-center justify-content-end">
      <a class="btn d-inline-flex align-items-center disabled" href="{{ url_for('todo.edit_tasks', tasks_id=task.id) }}" style="border: none; color: #198754;">
        <span class="material-symbols-outlined">edit</span>
      </a>
      <form action="{{ url_for('todo.done_tasks', tasks_id=task.id) }}" method="POST" class="mx-1">
        <button type="submit" name="flag" value="0" class="btn d-inline-flex align-items-center" style="color: #d63384;">
          <span class="material-symbols-outlined">radio_button_checked</span>
        </button>
      </form>
      <form action="{{ url_for('todo.done_tasks', tasks_id=task.id) }}" method="POST" class="mx-1">
        <button type="submit" name="flag" value="1" class="btn d-inline-flex align-items-center">
          <span class="material-symbols-outlined">delete</span>
        </button>
      </form>
    </div>
  </div>
</div>
{% endif %}
{% endfor %}
<div class="fab-container">
  <a tabindex="-1" class="pushcircle-disabled">
    <span class="material-symbols-outlined">add</span>
  </a>       
</div>
{% endblock %}
views.pyのdone_tasks関数の流れ
  • デコレータ関数によるルート作成とメソッドの許可
  • if文によるPOST時の処理
  • タスクカードの各処理(編集/完了/ゴミ箱)とリダイレクト
  • render_templateによるGET時のテンプレート呼び出しとキーワード引数

todo_done.htmlでは、完了タスクのみを表示するため、flagを用意し”0″”1″の判定によって各カラムの値を変更しています。

また、データベースの更新後にurl_forが指定する関数を呼び出すためにredirect(リダイレクト)しています。

ToDoアプリにおけるゴミ箱画面の処理を実装する

ゴミ箱画面で実装する項目は以下になります。

@todo.route("/delete", methods=["GET", "POST"])
def delete_tasks():
    # データを降順ソートしdelete対象レコードのみを取得
    tasks = Tasks.query.order_by(Tasks.id.desc()).filter_by(delete=1).all()
    # 元に戻すボタン押下後の処理を追加
    if request.method == "POST":
        tasks_id = request.args.get("tasks_id")
        flag = request.form["flag"]
        tasks = Tasks.query.filter_by(id=tasks_id).first()
        # ボタン押下後tasks.statusが"1(完了)"の場合
        if flag == "0":
            # 未完了に戻したTasksを更新し完了したタスク一覧画面にリダイレクト
            tasks.delete = 0
            db.session.add(tasks)
            db.session.commit()
            return redirect(url_for("todo.delete_tasks"))
        if flag == "1":
            db.session.query(Tasks).filter(Tasks.delete==1).delete()
            db.session.commit()
            return redirect(url_for("todo.delete_tasks"))
    return render_template("todo/todo_delete.html", tasks=tasks)
todo_delete.html
{% extends "todo/base.html" %}
{% block content %}
{% if not tasks == [] %}
{% for task in tasks %}
{% if task.delete == 1 %}
<div class="card mx-2 my-2">
  <div class="card-header align-items-center">
    <h5 class="card-title">{{ task.title }}</h5>
  </div>
  <div class="card-body">
    <p class="card-text" style="white-space:pre-wrap;">{{ task.content }}</p>
    <hr>
    <div class="card-btn-group d-flex align-items-center justify-content-end">
      <a class="btn d-inline-flex align-items-center disabled" href="{{ url_for('todo.edit_tasks', tasks_id=task.id) }}" style="border: none; color: #198754;">
        <span class="material-symbols-outlined">edit</span>
      </a>
      {% if task.status == 0 %}
      <button type="submit" class="btn d-inline-flex align-items-center disabled" style="border: none; color: #0d6efd;">
        <span class="material-symbols-outlined">radio_button_unchecked</span>
      </button>
      {% elif task.status == 1 %}
        <button type="submit" class="btn d-inline-flex align-items-center disabled" style="border: none; color: #d63384;">
          <span class="material-symbols-outlined">radio_button_checked</span>
        </button>
        {% endif %}
      <form action="{{ url_for('todo.delete_tasks', tasks_id=task.id) }}" method="POST" class="mx-1">
        <button type="submit" name="flag" value="0" class="btn d-inline-flex align-items-center" style="border: none;">
          <span class="material-symbols-outlined">undo</span>
        </button>
      </form>
    </div>
  </div>
</div>
{% endif %}
{% endfor %}
<!-- Button trigger modal -->
<div class="fab-container">
    <div class="pushcircle-delete">
    <button type="button"class="form-delete-btn" data-bs-toggle="modal" data-bs-target="#deleteModal">
      <span class="material-symbols-outlined">delete</span>
    </button>
  </div>
<!-- Modal -->
<div class="modal fade" data-bs-backdrop="false" id="deleteModal" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="false">
  <div class="modal-dialog modal-dialog-centered">
    <div class="modal-content">
      <div class="modal-header">
        <h1 class="modal-title fs-5" id="deleteModalLabel">完全に削除</h1>
        <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
      </div>
      <div class="modal-body">
        ゴミ箱を完全に空にしますか?<br>
        削除の操作は取り消しできません。
      </div>
      <div class="modal-footer">
        <button type="button" class="btn" style="color: #495057;"data-bs-dismiss="modal">キャンセル</button>
        <form action="{{ url_for('todo.delete_tasks') }}" method="POST">
        <button type="submit" name="flag" value="1" class="btn" style="color: #d63384;"><span>削除</span></button>
    </form>  
    </div>
    </div>
  </div>
</div>
</form>
{% else %}
<div class="fab-container">
  <div tabindex="-1" class="pushcircle-disabled">
    <span class="material-symbols-outlined">delete</span>
  </div>
</div>
{% endif %}
<script>
    const deleteModal = document.getElementById('deleteModal')
    const deleteInput = document.getElementById('myInput')
  
    deleteModal.addEventListener('shown.bs.modal', () => {
      deleteInput.focus()
    })
</script>
{% endblock %}
views.pyのdelete_tasks関数の流れ
  • デコレータ関数によるルート作成とメソッドの許可
  • if文によるPOST時の処理
  • タスクカードの各処理(編集/完了/ゴミ箱)とリダイレクト
  • 削除ボタンによるタスク削除(データベースの削除)
  • render_templateによるGET時のテンプレート呼び出しとキーワード引数

ゴミ箱画面から元に戻すアイコンボタンを設置し、flagによってデータベースの変更を実施しています。

また、flag == “1” の処理は、データベースからのタスク削除を実施します。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

sugiのアバター sugi SUGI

【経歴】玉川大学工学部卒業→新卒SIer企業入社→2年半後に独立→プログラミングスクール運営/受託案件→フリーランスエンジニア&SEOコンサル→Python特化のコンテンツサイトJob Code運営中

コメント

コメントする

目次