FlaskおよびSQLiteとの1対多のデータベース関係でアイテムを変更する方法
著者はCOVID-19救済基金を選択し、 Write forDOnationsプログラムの一環として寄付を受け取りました。
序章
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を使用して、前のチュートリアルのコードのリポジトリのクローンを作成します。
git clone https://github.com/do-community/flask-todo
flask-todo
に移動します。
cd flask-todo
次に、新しい仮想環境を作成します。
python -m venv env
環境をアクティブにします。
source env/bin/activate
Flaskをインストールします:
pip install Flask
次に、init_db.py
プログラムを使用してデータベースを初期化します。
python init_db.py
次に、次の環境変数を設定します。
export FLASK_APP=app export FLASK_ENV=development
FLASK_APP
は、現在開発中のアプリケーションを示します。この場合はapp.py
です。 FLASK_ENV
はモードを指定します。開発モードの場合は、development
に設定します。これにより、アプリケーションをデバッグできます。 (実稼働環境ではこのモードを使用しないでください。)
次に、開発サーバーを実行します。
flask run
ブラウザにアクセスすると、http://127.0.0.1:5000/
の次のURLでアプリケーションが実行されます。
開発サーバーを閉じるには、CTRL + C
キーの組み合わせを使用します。
次に、アプリケーションを変更して、アイテムを完了としてマークする機能を追加します。
ステップ2—To-Doアイテムを完了としてマークする
このステップでは、各ToDo項目を完了としてマークするボタンを追加します。
アイテムを完了としてマークできるようにするには、データベースのitems
テーブルに新しい列を追加して、各アイテムのマーカーを設定し、アイテムが完了したかどうかを確認してから、 app.py
ファイルの新しいルート。ユーザーのアクションに応じて、この列の値を変更します。
念のため、items
テーブルの列は現在次のとおりです。
id
:アイテムのID。list_id
:アイテムが属するリストのID。created
:アイテムの作成日。content
:アイテムのコンテンツ。
まず、schema.sql
を開いて、items
テーブルを変更します。
nano schema.sql
done
という名前の新しい列をitems
テーブルに追加します。
フラスコ_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
はブール値false
を表し、1
は値true
を表します。 デフォルトは0
です。これは、ユーザーがアイテムに完了のマークを付けるまで、追加した新しいアイテムは自動的に未完成になることを意味します。完了のマークを付けると、done
列の値が1
。
次に、init_db.py
プログラムを使用してデータベースを再度初期化し、schema.sql
で実行した変更を適用します。
python init_db.py
次に、変更のためにapp.py
を開きます。
nano app.py
アイテムのid
と、index()
関数のdone
列の値をフェッチします。この関数は、データベースからリストとアイテムをフェッチして、表示用の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)
ファイルを保存して閉じます。
この変更により、i.id
を使用してToDo項目のIDを取得し、i.done
を使用してdone
列の値を取得します。
この変更を理解するには、list_example.py
を開きます。これは、データベースの内容を理解するために使用できる小さなサンプルプログラムです。
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'])
ファイルを保存して終了します。
サンプルプログラムを実行します。
python list_example.py
出力は次のとおりです。
OutputHome 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
を開きます:
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
リクエストのみを受け入れます。 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()
ビュー機能は、done
の値を設定することを除いて、do()
機能とまったく同じです。 1
の代わりに0
に。
app.py
ファイルを保存して閉じます。
アイテムの状態に応じて、やることアイテムを完了または未完了としてマークするためのボタンが必要になりました。index.html
テンプレートファイルを開きます。
nano templates/index.html
<ul>
要素内の内側のfor
ループの内容を次のように変更します。
フラスコ_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
ループでは、item['done']
の値からわかるように、アイテムが完了としてマークされている場合、text-decoration
プロパティにline-through
CSS値を使用します。 ]。 次に、Jinja構文set
を使用して、URL
とBUTTON
の2つの変数を宣言します。 アイテムが完了としてマークされていない場合、ボタンの値は Do になり、URLは/do/
ルートに転送されます。アイテムが完了としてマークされている場合、ボタンには元に戻すの値で、/undo/
を指します。 その後、アイテムの状態に応じて適切なリクエストを送信するinput
フォームでこれらの変数の両方を使用します。
サーバーを実行します。
flask run
インデックスページhttp://127.0.0.1:5000/
でアイテムに完了のマークを付けることができるようになりました。 次に、やること項目を編集する機能を追加します。
ステップ3—To-Doアイテムの編集
このステップでは、アイテムを編集するための新しいページを追加して、各アイテムのコンテンツを変更し、アイテムを別のリストに割り当てることができるようにします。
新しい/edit/
ルートをapp.py
ファイルに追加します。これにより、ユーザーが既存のアイテムを変更できる新しいedit.html
ページがレンダリングされます。 また、index.html
ファイルを更新して、各アイテムにEdit
ボタンを追加します。
まず、app.py
ファイルを開きます。
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
引数を使用して、編集するTo DoアイテムのID、それが属するリストのID、done
の値をフェッチします。列、アイテムのコンテンツ、およびSQLJOIN
を使用したリストタイトル。 このデータはtodo
変数に保存します。 次に、データベースからすべてのTo Doリストを取得し、それらをlists
変数に保存します。
リクエストが通常のGETリクエストの場合、条件if request.method == 'POST'
は実行されないため、アプリケーションは最後のrender_template()
関数を実行し、todo
とlists
の両方を渡します。 edit.html
ファイルに。
ただし、フォームが送信された場合、条件request.method == 'POST'
はtrue
になります。この場合、ユーザーが送信したコンテンツとリストタイトルを抽出します。 コンテンツが送信されなかった場合は、メッセージContent is required!
をフラッシュして、同じ編集ページにリダイレクトします。 それ以外の場合は、ユーザーが送信したリストのIDを取得します。 これにより、ユーザーはToDoアイテムをあるリストから別のリストに移動できます。 次に、UPDATE
SQLステートメントを使用して、やること項目のコンテンツをユーザーが送信した新しいコンテンツに設定します。 リストIDについても同じようにします。 最後に、変更をコミットして接続を閉じ、ユーザーをインデックスページにリダイレクトします。
ファイルを保存して閉じます。
この新しいルートを使用するには、edit.html
という新しいテンプレートファイルが必要です。
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
ボタンを追加します。
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/
各アイテムのルート。
まだ実行していない場合は、サーバーを実行します。
flask run
これで、インデックスページhttp://127.0.0.1:5000/
に移動して、やること項目の変更を試すことができます。 次のステップでは、アイテムを削除するためのボタンを追加します。
ステップ4—ToDoアイテムの削除
このステップでは、特定のToDoアイテムを削除する機能を追加します。
まず、新しい/delete/
ルートを追加し、app.py
を開く必要があります。
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'))
ファイルを保存して閉じます。
delete()
ビュー関数は、id
引数を受け入れます。 POST
要求が送信されたら、DELETE
SQLステートメントを使用して、一致するid
値を持つアイテムを削除し、トランザクションをコミットしてデータベース接続を閉じます。インデックスページに戻ります。
次に、templates/index.html
を開いて、Delete
ボタンを追加します。
nano templates/index.html
Edit
ボタンの下に次の強調表示されたdiv
タグを追加します。
フラスコ_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>
この新しい送信ボタンは、各アイテムの/delete/
ルートにPOSTリクエストを送信します。
ファイルを保存して閉じます。
次に、開発サーバーを実行します。
flask run
インデックスページに移動して、新しいDelete
ボタンを試してください。これで、必要なアイテムを削除できます。
既存のやること項目を削除する機能を追加したので、次のステップで新しいリストを追加する機能を追加します。
ステップ5—新しいリストを追加する
これまでのところ、リストはデータベースから直接追加することしかできません。 このステップでは、既存のリストから選択するだけでなく、ユーザーが新しいアイテムを追加したときに新しいリストを作成する機能を追加します。 New List
という新しいオプションを組み込みます。このオプションを選択すると、ユーザーは作成する新しいリストの名前を入力できます。
まず、app.py
を開きます。
nano app.py
次に、if request.method == 'POST'
条件に次の強調表示された行を追加して、create()
ビュー機能を変更します。
フラスコ_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>
タグを追加し、ユーザーが新しいリストを追加できるようにします。
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>
ファイルを保存して閉じます。
New List
オプションを参照するために、新しい<option>
タグを追加しました。これにより、ユーザーは新しいリストを作成することを指定できます。 次に、new_list
という名前の入力フィールドを持つ別の<div>
を追加します。このフィールドは、ユーザーが作成する新しいリストのタイトルを入力する場所です。
最後に、開発サーバーを実行します。
flask run
次に、インデックスページにアクセスします。
http://127.0.0.1:5000/
アプリケーションは次のようになります。
アプリケーションに新たに追加されたことで、ユーザーはTo Doアイテムを完了としてマークしたり、完了したアイテムを未完了状態に復元したり、既存のアイテムを編集および削除したり、さまざまな種類のToDoタスクの新しいリストを作成したりできるようになりました。
アプリケーションの完全なソースコードは、 DigitalOcean CommunityRepositoryで閲覧できます。
結論
これで、新しいリストを作成する機能に加えて、ユーザーが新しいTo Doアイテムを作成し、アイテムを完了としてマークし、既存のアイテムを編集または削除できる完全なToDoアプリケーションができました。 Flask Webアプリケーションを変更し、それに新しい機能を追加し、特に1対多の関係でデータベースアイテムを変更しました。 Flaskを使用してアプリに認証を追加する方法-ログインを学習して、Flaskアプリケーションにセキュリティを追加することで、このアプリケーションをさらに開発できます。