序章

Flaskは、Python言語を使用してWebアプリケーションを構築するためのフレームワークであり、SQLiteは、Pythonでアプリケーションデータを格納するために使用できるデータベースエンジンです。 このチュートリアルでは、1対多の関係でFlaskとSQLiteを使用して構築されたアプリケーションのアイテムを変更します。

このチュートリアルは、FlaskおよびSQLiteで1対多のデータベース関係を使用する方法の続きです。 それを実行した後、To Doアイテムを管理し、リスト内のアイテムを整理し、データベースに新しいアイテムを追加するためのFlaskアプリケーションを正常に作成しました。 このチュートリアルでは、To Doアイテムを完了としてマークしたり、アイテムを編集および削除したり、データベースに新しいリストを追加したりする機能を追加します。 チュートリアルの終わりまでに、アプリケーションには、完了したToDoの編集および削除ボタンと取り消し線が含まれます。

前提条件

このガイドに従う前に、次のものが必要です。

  • ローカルのPython3プログラミング環境については、 Python3シリーズのローカルプログラミング環境をインストールしてセットアップする方法のチュートリアルに従ってください。 このチュートリアルでは、プロジェクトディレクトリを呼び出します flask_todo.

  • (オプション)ステップ1 では、このチュートリアルで作業するToDoアプリケーションのクローンを作成するオプションがあります。 ただし、オプションでFlaskおよびSQLiteで1対多のデータベース関係を使用する方法を使用できます。 このページから最終コードにアクセスできます。

  • ルートの作成、HTMLテンプレートのレンダリング、SQLiteデータベースへの接続などの基本的なFlaskの概念の理解。 これらの概念に精通していない場合は、 Python 3 でFlaskを使用してWebアプリケーションを作成する方法、および Python3でsqlite3モジュールを使用する方法を確認してください。

ステップ1—Webアプリケーションのセットアップ

このステップでは、変更の準備ができるようにToDoアプリケーションを設定します。 前提条件セクションのチュートリアルに従い、ローカルマシンにコードと仮想環境が残っている場合は、この手順をスキップできます。

最初にGitを使用して、前のチュートリアルのコードのリポジトリのクローンを作成します。

  1. git clone https://github.com/do-community/flask-todo

案内する flask-todo:

  1. cd flask-todo

次に、新しい仮想環境を作成します。

  1. python -m venv env

環境をアクティブにします。

  1. source env/bin/activate

Flaskをインストールします:

  1. pip install Flask

次に、を使用してデータベースを初期化します。 init_db.py プログラム:

  1. python init_db.py

次に、次の環境変数を設定します。

  1. export FLASK_APP=app
  2. export FLASK_ENV=development

FLASK_APP 現在開発中のアプリケーションを示します。 app.py この場合。 FLASK_ENV モードを指定します—に設定します development 開発モードの場合、これによりアプリケーションをデバッグできます。 (実稼働環境ではこのモードを使用しないでください。)

次に、開発サーバーを実行します。

  1. flask run

ブラウザにアクセスすると、次のURLでアプリケーションが実行されます。 http://127.0.0.1:5000/.

開発サーバーを閉じるには、 CTRL + C キーの組み合わせ。

次に、アプリケーションを変更して、アイテムを完了としてマークする機能を追加します。

ステップ2—To-Doアイテムを完了としてマークする

このステップでは、各ToDo項目を完了としてマークするボタンを追加します。

アイテムを完了としてマークできるようにするには、新しい列をに追加します items データベース内のテーブルに各アイテムのマーカーを設定して、アイテムが完了したかどうかを確認してから、新しいルートを作成します。 app.py ユーザーのアクションに応じてこの列の値を変更するファイル。

念のため、 items テーブルは現在次のとおりです。

  • id:アイテムのID。
  • list_id:アイテムが属するリストのID。
  • created:アイテムの作成日。
  • content:アイテムのコンテンツ。

まず、開く schema.sql を変更するには items テーブル:

  1. nano schema.sql

名前の付いた新しい列を追加します doneitems テーブル:

フラスコ_todo/schema.sql
DROP TABLE IF EXISTS lists;
DROP TABLE IF EXISTS items;

CREATE TABLE lists (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    title TEXT NOT NULL
);

CREATE TABLE items (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    list_id INTEGER NOT NULL,
    created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    content TEXT NOT NULL,
    done INTEGER NOT NULL DEFAULT 0,
    FOREIGN KEY (list_id) REFERENCES lists (id)
);

ファイルを保存して閉じます。

この新しい列は整数値を保持します 0 また 1; 値 0 ブール値を表します false1 値を表します true. デフォルトは 0、つまり、ユーザーがアイテムに完了のマークを付けるまで、追加した新しいアイテムは自動的に未完成になります。この場合、 done 列がに変わります 1.

次に、を使用してデータベースを再度初期化します。 init_db.py 実行した変更を適用するプログラム schema.sql:

  1. python init_db.py

次に、開く app.py 変更の場合:

  1. nano app.py

フェッチします id アイテムの価値と done の列 index() データベースからリストとアイテムをフェッチし、それらを index.html 表示用のファイル。 SQLステートメントに必要な変更は、次のファイルで強調表示されています。

フラスコ_todo/app.py
@app.route('/')
def index():
    conn = get_db_connection()
    todos = conn.execute('SELECT i.id, i.done, i.content, l.title \
                          FROM items i JOIN lists l \
                          ON i.list_id = l.id ORDER BY l.title;').fetchall()

    lists = {}

    for k, g in groupby(todos, key=lambda t: t['title']):
        lists[k] = list(g)

    conn.close()
    return render_template('index.html', lists=lists)

ファイルを保存して閉じます。

この変更により、を使用してToDoアイテムのIDを取得します。 i.id との値 done を使用した列 i.done.

この変更を理解するには、 list_example.py、これは、データベースの内容を理解するために使用できる小さなサンプルプログラムです。

  1. nano list_example.py

以前と同じSQLステートメントの変更を実行してから、最後のステートメントを変更します print() アイテムIDと値を表示する関数 done:

フラスコ_todo/list_example.py
from itertools import groupby
from app import get_db_connection

conn = get_db_connection()

todos = conn.execute('SELECT i.id, i.done, i.content, l.title \
                      FROM items i JOIN lists l \
                      ON i.list_id = l.id ORDER BY l.title;').fetchall()

lists = {}

for k, g in groupby(todos, key=lambda t: t['title']):
    lists[k] = list(g)

for list_, items in lists.items():
    print(list_)
    for item in items:
        print('    ', item['content'], '| id:',
              item['id'], '| done:', item['done'])

ファイルを保存して終了します。

サンプルプログラムを実行します。

  1. python list_example.py

出力は次のとおりです。

Output
Home Buy fruit | id: 2 | done: 0 Cook dinner | id: 3 | done: 0 Study Learn Flask | id: 4 | done: 0 Learn SQLite | id: 5 | done: 0 Work Morning meeting | id: 1 | done: 0

どのアイテムも完了としてマークされていないため、 done 各アイテムについては 0、つまり false. ユーザーがこの値を変更してアイテムを完了としてマークできるようにするには、に新しいルートを追加します。 app.py ファイル。

開ける app.py:

  1. nano app.py

ルートを追加する /do/ ファイルの最後に:

フラスコ_todo/app.py
. . .
@app.route('/<int:id>/do/', methods=('POST',))
def do(id):
    conn = get_db_connection()
    conn.execute('UPDATE items SET done = 1 WHERE id = ?', (id,))
    conn.commit()
    conn.close()
    return redirect(url_for('index'))

この新しいルートはのみ受け入れます POST リクエスト。 The do() ビュー機能は id 引数-これは、完了としてマークするアイテムのIDです。 関数内でデータベース接続を開き、次に UPDATE の値を設定するSQLステートメント done 列から 1 アイテムが完了としてマークされるようにします。

あなたは ? execute()メソッドのプレースホルダーであり、IDを含むタプルを渡して、データベースにデータを安全に挿入します。 次に、トランザクションをコミットして接続を閉じ、インデックスページにリダイレクトします。

アイテムを完了としてマークするルートを追加した後、このアクションを元に戻し、アイテムを未完了のステータスに戻すための別のルートが必要です。 ファイルの最後に次のルートを追加します。

フラスコ_todo/app.py
. . .
@app.route('/<int:id>/undo/', methods=('POST',))
def undo(id):
    conn = get_db_connection()
    conn.execute('UPDATE items SET done = 0 WHERE id = ?', (id,))
    conn.commit()
    conn.close()
    return redirect(url_for('index'))

このルートは、 /do/ ルート、および undo() ビュー機能は、 do() の値を設定する以外の関数 done0 それ以外の 1.

保存して閉じます app.py ファイル。

アイテムの状態に応じて、やることアイテムを完了または未完了としてマークするためのボタンが必要になりました。 index.html テンプレートファイル:

  1. nano templates/index.html

インナーの内容を変更する for 内部のループ <ul> 次のように見える要素:

フラスコ_todo/templates / index.html
{% block content %}
    <h1>{% block title %} Welcome to FlaskTodo {% endblock %}</h1>
    {% for list, items in lists.items() %}
        <div class="card" style="width: 18rem; margin-bottom: 50px;">
            <div class="card-header">
                <h3>{{ list }}</h3>
            </div>
            <ul class="list-group list-group-flush">
                {% for item in items %}
                    <li class="list-group-item"
                    {% if item['done'] %}
                    style="text-decoration: line-through;"
                    {% endif %}
                    >{{ item['content'] }}
                    {% if not item ['done'] %}
                        {% set URL = 'do' %}
                        {% set BUTTON = 'Do' %}
                    {% else %}
                        {% set URL = 'undo' %}
                        {% set BUTTON = 'Undo' %}
                    {% endif %}



                  <div class="row">
                        <div class="col-12 col-md-3">
                            <form action="{{ url_for(URL, id=item['id']) }}"
                                method="POST">
                                <input type="submit" value="{{ BUTTON }}"
                                    class="btn btn-success btn-sm">
                            </form>
                        </div>
                    </div>
                    </li>
                {% endfor %}
            </ul>
        </div>
    {% endfor %}
{% endblock %}

これで for ループ、あなたは使用します line-through のCSS値 text-decoration アイテムが完了としてマークされている場合はプロパティ。 item['done']. 次に、Jinja構文を使用します set 2つの変数を宣言するには、 URLBUTTON. アイテムが完了としてマークされていない場合、ボタンの値は Do になり、URLは /do/ ルート、およびアイテムが完了としてマークされている場合、ボタンの値は元に戻すになり、 /undo/. その後、これらの変数の両方を input アイテムの状態に応じて適切なリクエストを送信するフォーム。

サーバーを実行します。

  1. flask run

インデックスページでアイテムを完了としてマークできるようになりました http://127.0.0.1:5000/. 次に、やること項目を編集する機能を追加します。

ステップ3—To-Doアイテムの編集

このステップでは、アイテムを編集するための新しいページを追加して、各アイテムのコンテンツを変更し、アイテムを別のリストに割り当てることができるようにします。

新しいを追加します /edit/ へのルート app.py 新しいファイルをレンダリングします edit.html ユーザーが既存のアイテムを変更できるページ。 また、更新します index.html 追加するファイル Edit 各アイテムへのボタン。

まず、 app.py ファイル:

  1. nano app.py

次に、ファイルの最後に次のルートを追加します。

フラスコ_todo/app.py
. . .
@app.route('/<int:id>/edit/', methods=('GET', 'POST'))
def edit(id):
    conn = get_db_connection()

    todo = conn.execute('SELECT i.id, i.list_id, i.done, i.content, l.title \
                         FROM items i JOIN lists l \
                         ON i.list_id = l.id WHERE i.id = ?', (id,)).fetchone()

    lists = conn.execute('SELECT title FROM lists;').fetchall()

    if request.method == 'POST':
        content = request.form['content']
        list_title = request.form['list']

        if not content:
            flash('Content is required!')
            return redirect(url_for('edit', id=id))

        list_id = conn.execute('SELECT id FROM lists WHERE title = (?);',
                                 (list_title,)).fetchone()['id']

        conn.execute('UPDATE items SET content = ?, list_id = ?\
                      WHERE id = ?',
                     (content, list_id, id))
        conn.commit()
        conn.close()
        return redirect(url_for('index'))

    return render_template('edit.html', todo=todo, lists=lists)

この新しい表示機能では、 id 編集するToDoアイテムのID、それが属するリストのID、の値をフェッチするための引数 done 列、アイテムのコンテンツ、およびSQLを使用したリストタイトル JOIN. このデータをに保存します todo 変数。 次に、データベースからすべてのTo Doリストを取得し、それらをデータベースに保存します。 lists 変数。

リクエストが通常のGETリクエストの場合、条件 if request.method == 'POST' 実行されないため、アプリケーションは最後に実行します render_template() 関数、両方を渡す todolistsedit.html ファイル。

ただし、フォームが送信された場合、条件 request.method == 'POST' になります true、この場合、ユーザーが送信したコンテンツとリストタイトルを抽出します。 コンテンツが送信されなかった場合は、メッセージをフラッシュします Content is required! 同じ編集ページにリダイレクトします。 それ以外の場合は、ユーザーが送信したリストのIDを取得します。 これにより、ユーザーはToDoアイテムをあるリストから別のリストに移動できます。 次に、 UPDATE ToDo項目のコンテンツをユーザーが送信した新しいコンテンツに設定するSQLステートメント。 リストIDについても同じようにします。 最後に、変更をコミットして接続を閉じ、ユーザーをインデックスページにリダイレクトします。

ファイルを保存して閉じます。

この新しいルートを使用するには、という新しいテンプレートファイルが必要です。 edit.html:

  1. nano templates/edit.html

この新しいファイルに次の内容を追加します。

フラスコ_todo/templates / edit.html
{% extends 'base.html' %}

{% block content %}

<h1>{% block title %} Edit an Item {% endblock %}</h1>

<form method="post">
    <div class="form-group">
        <label for="content">Content</label>
        <input type="text" name="content"
               placeholder="Todo content" class="form-control"
               value="{{ todo['content'] or request.form['content'] }}"></input>
    </div>

    <div class="form-group">
        <label for="list">List</label>
        <select class="form-control" name="list">
            {% for list in lists %}
                {% if list['title'] == request.form['list'] %}
                    <option value="{{ request.form['list'] }}" selected>
                        {{ request.form['list'] }}
                    </option>

                {% elif list['title'] == todo['title'] %}
                    <option value="{{ todo['title'] }}" selected>
                        {{ todo['title'] }}
                    </option>

                {% else %}
                    <option value="{{ list['title'] }}">
                        {{ list['title'] }}
                    </option>
                {% endif %}
            {% endfor %}
        </select>
    </div>
    <div class="form-group">
        <button type="submit" class="btn btn-primary">Submit</button>
    </div>
</form>
{% endblock %}

値を使用します {{ todo['content'] or request.form['content'] }} コンテンツ入力用。 これは、値がTo Doアイテムの現在のコンテンツか、フォームの送信に失敗したときにユーザーが送信した内容のいずれかになることを意味します。

リスト選択フォームでは、ループします lists 変数であり、リストのタイトルがに格納されているものと同じである場合 request.form オブジェクト(失敗した試行から)、次にそのリストのタイトルを選択した値として設定します。 それ以外の場合、リストのタイトルがに保存されているものと等しい場合 todo 変数を選択し、それを選択した値として設定します。 これは、変更前のToDo項目の現在のリストタイトルです。 残りのオプションは、 selected 属性。

ファイルを保存して閉じます。

次に、開きます index.html 追加するには Edit ボタン:

  1. nano templates/index.html

内容を変更する div のタグ "row" 次のように別の列を追加するクラス:

フラスコ_todo/templates / index.html
. . .
<div class="row">
    <div class="col-12 col-md-3">
        <form action="{{ url_for(URL, id=item['id']) }}"
            method="POST">
            <input type="submit" value="{{ BUTTON }}"
                class="btn btn-success btn-sm">
        </form>
    </div>
    <div class="col-12 col-md-3">
        <a class="btn btn-warning btn-sm"
        href="{{ url_for('edit', id=item['id']) }}">Edit</a>
    </div>
</div>

ファイルを保存して閉じます。

これは標準リンクタグ関連するを指します /edit/ 各アイテムのルート。

まだ実行していない場合は、サーバーを実行します。

  1. flask run

これで、インデックスページに移動できます http://127.0.0.1:5000/ やること項目を変更してみてください。 次のステップでは、アイテムを削除するためのボタンを追加します。

ステップ4—ToDoアイテムの削除

このステップでは、特定のToDoアイテムを削除する機能を追加します。

最初に新しいものを追加する必要があります /delete/ ルート、オープン app.py:

  1. nano app.py

次に、ファイルの最後に次のルートを追加します。

フラスコ_todo/app.py
. . .
@app.route('/<int:id>/delete/', methods=('POST',))
def delete(id):
    conn = get_db_connection()
    conn.execute('DELETE FROM items WHERE id = ?', (id,))
    conn.commit()
    conn.close()
    return redirect(url_for('index'))

ファイルを保存して閉じます。

The delete() ビュー関数は id 口論。 いつ POST リクエストが送信されたら、 DELETE 一致するアイテムを削除するSQLステートメント id 値を指定してから、トランザクションをコミットしてデータベース接続を閉じ、インデックスページに戻ります。

次に、開く templates/index.html 追加するには Delete ボタン:

  1. nano templates/index.html

以下の強調表示を追加します div 下のタグ Edit ボタン:

フラスコ_todo/templates / index.html
<div class="row">
    <div class="col-12 col-md-3">
        <form action="{{ url_for(URL, id=item['id']) }}"
            method="POST">
            <input type="submit" value="{{ BUTTON }}"
                class="btn btn-success btn-sm">
        </form>
    </div>

    <div class="col-12 col-md-3">
        <a class="btn btn-warning btn-sm"
        href="{{ url_for('edit', id=item['id']) }}">Edit</a>
    </div>

    <div class="col-12 col-md-3">
        <form action="{{ url_for('delete', id=item['id']) }}"
            method="POST">
            <input type="submit" value="Delete"
                class="btn btn-danger btn-sm">
        </form>
    </div>
</div>

この新しい送信ボタンは、POSTリクエストをに送信します /delete/ 各アイテムのルート。

ファイルを保存して閉じます。

次に、開発サーバーを実行します。

  1. flask run

インデックスページに移動して、新しいものを試してください Delete ボタン-必要なアイテムを削除できるようになりました。

既存のやること項目を削除する機能を追加したので、次のステップで新しいリストを追加する機能を追加します。

ステップ5—新しいリストを追加する

これまでのところ、リストはデータベースから直接追加することしかできません。 このステップでは、既存のリストから選択するだけでなく、ユーザーが新しいアイテムを追加したときに新しいリストを作成する機能を追加します。 と呼ばれる新しいオプションを組み込みます New List、これを選択すると、ユーザーは作成したい新しいリストの名前を入力できます。

まず、開く app.py:

  1. nano app.py

次に、 create() 次の強調表示された行をに追加して、関数を表示します。 if request.method == 'POST' 調子:

フラスコ_todo/app.py
. . .
@app.route('/create/', methods=('GET', 'POST'))
def create():
    conn = get_db_connection()

    if request.method == 'POST':
        content = request.form['content']
        list_title = request.form['list']

        new_list = request.form['new_list']

        # If a new list title is submitted, add it to the database
        if list_title == 'New List' and new_list:
            conn.execute('INSERT INTO lists (title) VALUES (?)',
                         (new_list,))
            conn.commit()
            # Update list_title to refer to the newly added list
            list_title = new_list

        if not content:
            flash('Content is required!')
            return redirect(url_for('index'))

        list_id = conn.execute('SELECT id FROM lists WHERE title = (?);',
                                 (list_title,)).fetchone()['id']
        conn.execute('INSERT INTO items (content, list_id) VALUES (?, ?)',
                     (content, list_id))
        conn.commit()
        conn.close()
        return redirect(url_for('index'))

    lists = conn.execute('SELECT title FROM lists;').fetchall()

    conn.close()
    return render_template('create.html', lists=lists)

ファイルを保存して閉じます。

ここでは、という新しいフォームフィールドの値を保存します new_list 変数で。 このフィールドは後で追加します create.html ファイル。 次に、 list_title == 'New List' and new_list 状態、あなたはかどうかを確認します list_title 値があります 'New List'、これは、ユーザーが新しいリストを作成したいことを示します。 また、の値が new_list 変数は None、この条件が満たされた場合は、 INSERT INTO 新しく送信されたリストタイトルをに追加するSQLステートメント lists テーブル。 トランザクションをコミットしてから、の値を更新します list_title 後で使用するために新しく追加されたリストの変数と一致する変数。

次に、開く create.html 新しいを追加するには <option> ユーザーが新しいリストを追加できるようにするタグ:

  1. nano templates/create.html

次のコードで強調表示されたタグを追加して、ファイルを変更します。

フラスコ_todo/templates / create.html
    <div class="form-group">
        <label for="list">List</label>
        <select class="form-control" name="list">
            <option value="New List" selected>New List</option>
            {% for list in lists %}
                {% if list['title'] == request.form['list'] %}
                    <option value="{{ request.form['list'] }}" selected>
                        {{ request.form['list'] }}
                    </option>
                {% else %}
                    <option value="{{ list['title'] }}">
                        {{ list['title'] }}
                    </option>
                {% endif %}
            {% endfor %}
        </select>
    </div>

    <div class="form-group">
        <label for="new_list">New List</label>
        <input type="text" name="new_list"
                placeholder="New list name" class="form-control"
                value="{{ request.form['new_list'] }}"></input>
    </div>

    <div class="form-group">
        <button type="submit" class="btn btn-primary">Submit</button>
    </div>

ファイルを保存して閉じます。

新しいを追加しました <option> 参照するタグ New List オプションを選択すると、ユーザーは新しいリストを作成することを指定できます。 次に、別の <div> 名前の入力フィールドを持つ new_list、このフィールドは、ユーザーが作成する新しいリストのタイトルを入力する場所です。

最後に、開発サーバーを実行します。

  1. flask run

次に、インデックスページにアクセスします。

http://127.0.0.1:5000/

アプリケーションは次のようになります。

アプリケーションに新たに追加されたことで、ユーザーはTo Doアイテムを完了としてマークしたり、完了したアイテムを未完了状態に復元したり、既存のアイテムを編集および削除したり、さまざまな種類のToDoタスクの新しいリストを作成したりできるようになりました。

アプリケーションの完全なソースコードは、 DigitalOcean CommunityRepositoryで閲覧できます。

結論

これで、新しいリストを作成する機能に加えて、ユーザーが新しいTo Doアイテムを作成し、アイテムを完了としてマークし、既存のアイテムを編集または削除できる完全なToDoアプリケーションができました。 Flask Webアプリケーションを変更し、それに新しい機能を追加し、特に1対多の関係でデータベースアイテムを変更しました。 Flaskを使用してアプリに認証を追加する方法-ログインを学習して、Flaskアプリケーションにセキュリティを追加することで、このアプリケーションをさらに開発できます。