FlaskおよびSQLiteとの1対多のデータベース関係でアイテムを変更する方法
序章
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
ブラウザにアクセスすると、次の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
テーブル:
- nano schema.sql
名前の付いた新しい列を追加します done
に items
テーブル:
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
アイテムの価値と done
の列 index()
データベースからリストとアイテムをフェッチし、それらを index.html
表示用のファイル。 SQLステートメントに必要な変更は、次のファイルで強調表示されています。
@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
、これは、データベースの内容を理解するために使用できる小さなサンプルプログラムです。
- nano list_example.py
以前と同じSQLステートメントの変更を実行してから、最後のステートメントを変更します print()
アイテムIDと値を表示する関数 done
:
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/
ファイルの最後に:
. . .
@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を含むタプルを渡して、データベースにデータを安全に挿入します。 次に、トランザクションをコミットして接続を閉じ、インデックスページにリダイレクトします。
アイテムを完了としてマークするルートを追加した後、このアクションを元に戻し、アイテムを未完了のステータスに戻すための別のルートが必要です。 ファイルの最後に次のルートを追加します。
. . .
@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()
の値を設定する以外の関数 done
に 0
それ以外の 1
.
保存して閉じます app.py
ファイル。
アイテムの状態に応じて、やることアイテムを完了または未完了としてマークするためのボタンが必要になりました。 index.html
テンプレートファイル:
- nano templates/index.html
インナーの内容を変更する for
内部のループ <ul>
次のように見える要素:
{% 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つの変数を宣言するには、 URL
と BUTTON
. アイテムが完了としてマークされていない場合、ボタンの値は 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
次に、ファイルの最後に次のルートを追加します。
. . .
@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()
関数、両方を渡す todo
と lists
に edit.html
ファイル。
ただし、フォームが送信された場合、条件 request.method == 'POST'
になります true
、この場合、ユーザーが送信したコンテンツとリストタイトルを抽出します。 コンテンツが送信されなかった場合は、メッセージをフラッシュします Content is required!
同じ編集ページにリダイレクトします。 それ以外の場合は、ユーザーが送信したリストのIDを取得します。 これにより、ユーザーはToDoアイテムをあるリストから別のリストに移動できます。 次に、 UPDATE
ToDo項目のコンテンツをユーザーが送信した新しいコンテンツに設定するSQLステートメント。 リストIDについても同じようにします。 最後に、変更をコミットして接続を閉じ、ユーザーをインデックスページにリダイレクトします。
ファイルを保存して閉じます。
この新しいルートを使用するには、という新しいテンプレートファイルが必要です。 edit.html
:
- nano 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"
次のように別の列を追加するクラス:
. . .
<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
次に、ファイルの最後に次のルートを追加します。
. . .
@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
ボタン:
- nano templates/index.html
以下の強調表示を追加します div
下のタグ Edit
ボタン:
<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/
各アイテムのルート。
ファイルを保存して閉じます。
次に、開発サーバーを実行します。
- flask run
インデックスページに移動して、新しいものを試してください Delete
ボタン-必要なアイテムを削除できるようになりました。
既存のやること項目を削除する機能を追加したので、次のステップで新しいリストを追加する機能を追加します。
ステップ5—新しいリストを追加する
これまでのところ、リストはデータベースから直接追加することしかできません。 このステップでは、既存のリストから選択するだけでなく、ユーザーが新しいアイテムを追加したときに新しいリストを作成する機能を追加します。 と呼ばれる新しいオプションを組み込みます New List
、これを選択すると、ユーザーは作成したい新しいリストの名前を入力できます。
まず、開く app.py
:
- nano app.py
次に、 create()
次の強調表示された行をに追加して、関数を表示します。 if request.method == 'POST'
調子:
. . .
@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
次のコードで強調表示されたタグを追加して、ファイルを変更します。
<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
、このフィールドは、ユーザーが作成する新しいリストのタイトルを入力する場所です。
最後に、開発サーバーを実行します。
- flask run
次に、インデックスページにアクセスします。
http://127.0.0.1:5000/
アプリケーションは次のようになります。
アプリケーションに新たに追加されたことで、ユーザーはTo Doアイテムを完了としてマークしたり、完了したアイテムを未完了状態に復元したり、既存のアイテムを編集および削除したり、さまざまな種類のToDoタスクの新しいリストを作成したりできるようになりました。
アプリケーションの完全なソースコードは、 DigitalOcean CommunityRepositoryで閲覧できます。
結論
これで、新しいリストを作成する機能に加えて、ユーザーが新しいTo Doアイテムを作成し、アイテムを完了としてマークし、既存のアイテムを編集または削除できる完全なToDoアプリケーションができました。 Flask Webアプリケーションを変更し、それに新しい機能を追加し、特に1対多の関係でデータベースアイテムを変更しました。 Flaskを使用してアプリに認証を追加する方法-ログインを学習して、Flaskアプリケーションにセキュリティを追加することで、このアプリケーションをさらに開発できます。