作者选择了 COVID-19 救援基金作为 Write for Donations计划的一部分接受捐款。
介绍
Flask 是使用 Python 语言构建 Web 应用程序的框架,而 SQLite 是可以与 Python 一起使用来存储应用程序数据的数据库引擎. 在本教程中,您将修改使用 Flask 和 SQLite 构建的应用程序中的项目。
此教程是如何使用Flask和SQLite的One-to-Many Database Relationships的续集(https://andsky.com/tech/tutorials/how-to-use-one-to-many-database-relationships-with-flask-and-sqlite)。遵循后,您成功创建了一个Flask应用程序来管理要做项目,在列表中组织项目,并将新项目添加到数据库中。在本教程中,您将添加功能来标记要做项目作为完整,编辑和删除项目,并将新列表添加到数据库中。
前提条件
在你开始遵循这个指南之前,你将需要:
- 一个本地的Python 3编程环境,请遵循在 [如何安装和设置Python 3的本地编程环境]系列(https://www.digitalocean.com/community/tutorial_series/how-to-install-and-set-up-a-local-programming-environment-for-python-3)的分布的教程。 在本教程中,我们将呼叫我们的项目目录
flask_todo
。 - (可选) 在 Step 1中,您将有选择克隆我们将在本教程中工作的任务应用程序。然而,您可以选择通过 如何使用Flask 和 SQLite 的多种数据库关系使用 One-to-Many Database Relationships工作。 您可以访问最终的代码 从此页面 。
- 了解创建这样的路线,渲染HTML模板,并连接到
步骤 1 - 设置 Web 应用程序
在此步骤中,您将设置任务应用程序,以便为修改做好准备. 如果您遵循了前提部分中的教程,并且仍然在本地机器中有代码和虚拟环境,您可以跳过此步骤。
首先使用Git来克隆上一节教程的代码库:
1git clone https://github.com/do-community/flask-todo
导航至全瓶
:
1cd flask-todo
创建一个新的虚拟环境:
1python -m venv env
激活环境:
1source env/bin/activate
安装瓶子:
1pip install Flask
然后,使用init_db.py
程序初始化数据库:
1python init_db.py
然后,设置以下环境变量:
1export FLASK_APP=app
2export FLASK_ENV=development
FLASK_APP
表示您目前正在开发的应用程序,在这种情况下是app.py
。FLASK_ENV
指定模式,将其设置为开发
模式,这将允许您调试应用程序(记住不要在生产环境中使用这种模式)。
然后运行开发服务器:
1flask run
如果你去你的浏览器,你会看到应用程序在以下URL上运行在 http://127.0.0.1:5000/
。
要关闭开发服务器,请使用CTRL + C
键组合。
接下来,您将更改应用程序以添加标记项目为完整的功能。
步骤 2 – 标记要做项目为完整
在此步骤中,您将添加一个按钮,将每个任务标记为完成。
为了能够将项目标记为完整,您将添加一个新的列到数据库中的项目
表中,以便为每个项目有一个标记,以便您知道它是否已完成,然后您将在您的app.py
文件中创建一个新的路线,以根据用户的操作更改该列的值。
作为提醒,当前项目
表中的列如下:
id
: 项目的 ID.list_id
: 项目所属的列表的 ID.created
: 项目的创建日期.content
: 项目的内容.
首先,打开schema.sql
以更改项目
表:
1nano schema.sql
在项目
表中添加一个名为完成
的新列:
1[label flask_todo/schema.sql]
2DROP TABLE IF EXISTS lists;
3DROP TABLE IF EXISTS items;
4
5CREATE TABLE lists (
6 id INTEGER PRIMARY KEY AUTOINCREMENT,
7 created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
8 title TEXT NOT NULL
9);
10
11CREATE TABLE items (
12 id INTEGER PRIMARY KEY AUTOINCREMENT,
13 list_id INTEGER NOT NULL,
14 created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
15 content TEXT NOT NULL,
16 done INTEGER NOT NULL DEFAULT 0,
17 FOREIGN KEY (list_id) REFERENCES lists (id)
18);
保存并关闭文件。
此新列将包含整数值0
或1
;值0
代表布尔值false
和1
代表值true
。默认值为0
,这意味着您添加的任何新项目将自动未完成,直到用户将项目标记为完整,在这种情况下完成
列的值将变为1
。
然后,使用init_db.py
程序重新启动数据库,将您对schema.sql
所做的更改应用:
1python init_db.py
接下来,打开app.py
进行更改:
1nano app.py
您将从index()
函数中获取项目的id
和完成
列的值,该函数从数据库中获取列表和项目,并将其发送到index.html
文件中进行显示。
1[label flask_todo/app.py]
2@app.route('/')
3def index():
4 conn = get_db_connection()
5 todos = conn.execute('SELECT i.id, i.done, i.content, l.title \
6 FROM items i JOIN lists l \
7 ON i.list_id = l.id ORDER BY l.title;').fetchall()
8
9 lists = {}
10
11 for k, g in groupby(todos, key=lambda t: t['title']):
12 lists[k] = list(g)
13
14 conn.close()
15 return render_template('index.html', lists=lists)
保存并关闭文件。
通过此更改,您可以使用i.id
获取使用任务项目的 ID 和使用i.done
的完成
列的值。
要了解这种变化,请打开list_example.py
,这是一个小型的示例程序,您可以使用它来了解数据库的内容:
1nano list_example.py
对 SQL 语句进行与之前相同的修改,然后更改最后一个print()
函数以显示项目 ID 和完成
值:
1[label flask_todo/list_example.py]
2from itertools import groupby
3from app import get_db_connection
4
5conn = get_db_connection()
6
7todos = conn.execute('SELECT i.id, i.done, i.content, l.title \
8 FROM items i JOIN lists l \
9 ON i.list_id = l.id ORDER BY l.title;').fetchall()
10
11lists = {}
12
13for k, g in groupby(todos, key=lambda t: t['title']):
14 lists[k] = list(g)
15
16for list_, items in lists.items():
17 print(list_)
18 for item in items:
19 print(' ', item['content'], '| id:',
20 item['id'], '| done:', item['done'])
保存和退出文件。
运行示例程序:
1python list_example.py
以下是产量:
1[secondary_label Output]
2Home
3 Buy fruit | id: 2 | done: 0
4 Cook dinner | id: 3 | done: 0
5Study
6 Learn Flask | id: 4 | done: 0
7 Learn SQLite | id: 5 | done: 0
8Work
9 Morning meeting | id: 1 | done: 0
没有一个项目被标记为完成,因此每个项目的完成
值为0
,这意味着假
。
打开app.py
:
1nano app.py
在文件的末尾添加一条路径 /do/
:
1[label flask_todo/app.py]
2. . .
3@app.route('/<int:id>/do/', methods=('POST',))
4def do(id):
5 conn = get_db_connection()
6 conn.execute('UPDATE items SET done = 1 WHERE id = ?', (id,))
7 conn.commit()
8 conn.close()
9 return redirect(url_for('index'))
此新路线只接受POST
请求。do()
视图函数采用id
参数,即您想要标记为完成的项目的ID。在该函数内,您打开数据库连接,然后使用UPDATE
SQL 语句将完成
列的值设置为1
,以便该项目被标记为完成。
您在 execute()
方法中使用 ?
定位符,并通过包含该 ID 的 Tuple 安全地将数据插入数据库,然后进行交易并关闭连接并重定向到索引页面。
添加路线以将项目标记为完成后,您需要另一个路线来取消此操作并将项目返回未完成状态。
1[label flask_todo/app.py]
2. . .
3@app.route('/<int:id>/undo/', methods=('POST',))
4def undo(id):
5 conn = get_db_connection()
6 conn.execute('UPDATE items SET done = 0 WHERE id = ?', (id,))
7 conn.commit()
8 conn.close()
9 return redirect(url_for('index'))
此路径类似于 /do/
路径,而 undo()
视图函数与 do()
函数完全相同,除非您将 done
值设置为 0
而不是 1
。
保存并关闭app.py
文件。
现在您需要一个按钮来标记完成
或未完成
项目,取决于项目的状态,打开index.html
模板文件:
1nano templates/index.html
更改for
元素内部的内部循环的内容,以显示如下:
1[label flask_todo/templates/index.html]
2{% block content %}
3 <h1>{% block title %} Welcome to FlaskTodo {% endblock %}</h1>
4 {% for list, items in lists.items() %}
5 <div class="card" style="width: 18rem; margin-bottom: 50px;">
6 <div class="card-header">
7 <h3>{{ list }}</h3>
8 </div>
9 <ul class="list-group list-group-flush">
10 {% for item in items %}
11 <li class="list-group-item"
12 {% if item['done'] %}
13 style="text-decoration: line-through;"
14 {% endif %}
15 >{{ item['content'] }}
16 {% if not item ['done'] %}
17 {% set URL = 'do' %}
18 {% set BUTTON = 'Do' %}
19 {% else %}
20 {% set URL = 'undo' %}
21 {% set BUTTON = 'Undo' %}
22 {% endif %}
23
24 <div class="row">
25 <div class="col-12 col-md-3">
26 <form action="{{ url_for(URL, id=item['id']) }}"
27 method="POST">
28 <input type="submit" value="{{ BUTTON }}"
29 class="btn btn-success btn-sm">
30 </form>
31 </div>
32 </div>
33 </li>
34 {% endfor %}
35 </ul>
36 </div>
37 {% endfor %}
38{% endblock %}
在这个为
循环中,如果项目被标记为完成,则使用 CSS 值为通过行
为文本装饰
属性,您可以从项目完成
的值中知道。 然后使用 Jinja 语法设置
来声明两个变量,即URL
和BUTTON
。 如果项目没有被标记为完成,则按钮将具有值 Do,并将 URL 引导到/do/
路线,如果项目被标记为完成
,则按钮将具有值 Undo,并将指向/undo/
. 然后,您将使用这些变量在输入
表格中,根据项目状态提交正确的请求。
运行服务器:
1flask run
您现在可以将项目标记为已完成的索引页面 http://127.0.0.1:5000/
. 接下来,您将添加编辑任务项目的能力。
步骤3 - 编辑要做的项目
在此步骤中,您将添加一个新页面来编辑项目,以便您可以更改每个项目的内容,并将项目分配到不同的列表中。
您将添加一个新的 /edit/
路径到 app.py
文件中,这将显示一个新的 edit.html
页面,用户可以修改现有项目,您还将更新 index.html
文件,为每个项目添加一个 Edit
按钮。
首先,打开 app.py 文件:
1nano app.py
然后在文件末尾添加以下路径:
1[label flask_todo/app.py]
2. . .
3@app.route('/<int:id>/edit/', methods=('GET', 'POST'))
4def edit(id):
5 conn = get_db_connection()
6
7 todo = conn.execute('SELECT i.id, i.list_id, i.done, i.content, l.title \
8 FROM items i JOIN lists l \
9 ON i.list_id = l.id WHERE i.id = ?', (id,)).fetchone()
10
11 lists = conn.execute('SELECT title FROM lists;').fetchall()
12
13 if request.method == 'POST':
14 content = request.form['content']
15 list_title = request.form['list']
16
17 if not content:
18 flash('Content is required!')
19 return redirect(url_for('edit', id=id))
20
21 list_id = conn.execute('SELECT id FROM lists WHERE title = (?);',
22 (list_title,)).fetchone()['id']
23
24 conn.execute('UPDATE items SET content = ?, list_id = ?\
25 WHERE id = ?',
26 (content, list_id, id))
27 conn.commit()
28 conn.close()
29 return redirect(url_for('index'))
30
31 return render_template('edit.html', todo=todo, lists=lists)
在此新视图函数中,您使用id
参数获取您要编辑的任务项目的 ID、所属列表的 ID、完成列的值、项目的内容以及使用 SQLJOIN
的列表标题。
如果请求是正常的 GET 请求,则条件 if request.method == 'POST' 不运行,因此应用程序执行最后一个
render_template()函数,将
todo' 和 lists' 都传送到
edit.html` 文件中。
但是,如果提交了表格,则条件 request.method == 'POST' 变成了
true,在这种情况下,您提取了用户提交的内容和列表标题。如果没有提交的内容,您闪烁了消息
Content is required!`,并重定向到相同的编辑页面。否则,您收集了用户提交的列表的ID;这允许用户从一个列表移动到另一个列表。
保存并关闭文件。
要使用此新路径,您需要一个名为edit.html的新模板文件:
1nano templates/edit.html
将以下内容添加到新文件中:
1[label flask_todo/templates/edit.html]
2{% extends 'base.html' %}
3
4{% block content %}
5
6<h1>{% block title %} Edit an Item {% endblock %}</h1>
7
8<form method="post">
9 <div class="form-group">
10 <label for="content">Content</label>
11 <input type="text" name="content"
12 placeholder="Todo content" class="form-control"
13 value="{{ todo['content'] or request.form['content'] }}"></input>
14 </div>
15
16 <div class="form-group">
17 <label for="list">List</label>
18 <select class="form-control" name="list">
19 {% for list in lists %}
20 {% if list['title'] == request.form['list'] %}
21 <option value="{{ request.form['list'] }}" selected>
22 {{ request.form['list'] }}
23 </option>
24
25 {% elif list['title'] == todo['title'] %}
26 <option value="{{ todo['title'] }}" selected>
27 {{ todo['title'] }}
28 </option>
29
30 {% else %}
31 <option value="{{ list['title'] }}">
32 {{ list['title'] }}
33 </option>
34 {% endif %}
35 {% endfor %}
36 </select>
37 </div>
38 <div class="form-group">
39 <button type="submit" class="btn btn-primary">Submit</button>
40 </div>
41</form>
42{% endblock %}
您将为内容输入使用 `{{ todo['content'] 或 request.form['content'] }} 的值,这意味着值将是任务项目的当前内容,或者用户在试图提交表单时提交的内容。
对于列表选择表格,您将循环通过列表
变量,如果列表标题与request.form
对象中存储的标题相同(从未成功的尝试中),然后将该列表标题设置为所选值。
保存并关闭文件。
然后打开index.html
,添加一个编辑
按钮:
1nano templates/index.html
用row
类更改div
标签的内容,以如下方式添加另一个列:
1[label flask_todo/templates/index.html]
2. . .
3<div class="row">
4 <div class="col-12 col-md-3">
5 <form action="{{ url_for(URL, id=item['id']) }}"
6 method="POST">
7 <input type="submit" value="{{ BUTTON }}"
8 class="btn btn-success btn-sm">
9 </form>
10 </div>
11 <div class="col-12 col-md-3">
12 <a class="btn btn-warning btn-sm"
13 href="{{ url_for('edit', id=item['id']) }}">Edit</a>
14 </div>
15</div>
保存并关闭文件。
这是一个 标准 <a>
链接标签,指向每个项目的相关 /edit/
路径。
运行服务器,如果你还没有:
1flask run
您现在可以去索引页面 http://127.0.0.1:5000/
并尝试修改任务项目. 在下一步,您将添加一个按钮来删除项目。
步骤 4 – 删除要做的项目
在此步骤中,您将添加删除特定任务项目的能力。
您首先需要添加一个新的 /delete/
路线,打开 app.py
:
1nano app.py
然后在文件末尾添加以下路径:
1[label flask_todo/app.py]
2. . .
3@app.route('/<int:id>/delete/', methods=('POST',))
4def delete(id):
5 conn = get_db_connection()
6 conn.execute('DELETE FROM items WHERE id = ?', (id,))
7 conn.commit()
8 conn.close()
9 return redirect(url_for('index'))
保存并关闭文件。
当发送一个POST
请求时,您使用DELETE
SQL语句删除与匹配的id
值的项目,然后执行交易并关闭数据库连接,然后返回索引页面。
接下来,打开templates/index.html
,添加一个删除
按钮:
1nano templates/index.html
在编辑
按钮下方添加以下突出的div
标签:
1[label flask_todo/templates/index.html]
2<div class="row">
3 <div class="col-12 col-md-3">
4 <form action="{{ url_for(URL, id=item['id']) }}"
5 method="POST">
6 <input type="submit" value="{{ BUTTON }}"
7 class="btn btn-success btn-sm">
8 </form>
9 </div>
10
11 <div class="col-12 col-md-3">
12 <a class="btn btn-warning btn-sm"
13 href="{{ url_for('edit', id=item['id']) }}">Edit</a>
14 </div>
15
16 <div class="col-12 col-md-3">
17 <form action="{{ url_for('delete', id=item['id']) }}"
18 method="POST">
19 <input type="submit" value="Delete"
20 class="btn btn-danger btn-sm">
21 </form>
22 </div>
23</div>
此新的提交按钮将向每个项目的 /delete/
路径发送一个 POST 请求。
保存并关闭文件。
然后运行开发服务器:
1flask run
转到索引页面并尝试新的删除
按钮 - 你现在可以删除任何你想要的项目。
现在您已经添加了删除现有任务项目的功能,您将继续在下一步中添加新列表的功能。
步骤 5 – 添加新列表
到目前为止,只能直接从数据库中添加列表. 在此步骤中,您将添加创建新列表的能力,当用户添加新项目时,而不是仅仅在现有列表之间进行选择。
首先,打开app.py
:
1nano app.py
然后,通过将下列突出的行添加到 if request.method == 'POST' 条件中来修改 'create()' 视图函数:
1[label flask_todo/app.py]
2. . .
3@app.route('/create/', methods=('GET', 'POST'))
4def create():
5 conn = get_db_connection()
6
7 if request.method == 'POST':
8 content = request.form['content']
9 list_title = request.form['list']
10
11 new_list = request.form['new_list']
12
13 # If a new list title is submitted, add it to the database
14 if list_title == 'New List' and new_list:
15 conn.execute('INSERT INTO lists (title) VALUES (?)',
16 (new_list,))
17 conn.commit()
18 # Update list_title to refer to the newly added list
19 list_title = new_list
20
21 if not content:
22 flash('Content is required!')
23 return redirect(url_for('index'))
24
25 list_id = conn.execute('SELECT id FROM lists WHERE title = (?);',
26 (list_title,)).fetchone()['id']
27 conn.execute('INSERT INTO items (content, list_id) VALUES (?, ?)',
28 (content, list_id))
29 conn.commit()
30 conn.close()
31 return redirect(url_for('index'))
32
33 lists = conn.execute('SELECT title FROM lists;').fetchall()
34
35 conn.close()
36 return render_template('create.html', lists=lists)
保存并关闭文件。
在这里,您将将一个名为new_list
的新表单字段的值保存到变量中。您将后来将这个字段添加到create.html
文件中。接下来,在list_title ==
New List和
new_list条件下,您会检查
list_title是否具有
New List的值,这表明用户希望创建一个新列表。您还会检查
new_list变量的值不是
None,如果满足了这个条件,您会使用一个
INSERT INTO SQL 语句将新提交的列表名称添加到
lists`表中。
接下来,打开create.html
,添加一个新的<option>
标签,让用户添加一个新列表:
1nano templates/create.html
通过在以下代码中添加突出标签来修改文件:
1[label flask_todo/templates/create.html]
2 <div class="form-group">
3 <label for="list">List</label>
4 <select class="form-control" name="list">
5 <option value="New List" selected>New List</option>
6 {% for list in lists %}
7 {% if list['title'] == request.form['list'] %}
8 <option value="{{ request.form['list'] }}" selected>
9 {{ request.form['list'] }}
10 </option>
11 {% else %}
12 <option value="{{ list['title'] }}">
13 {{ list['title'] }}
14 </option>
15 {% endif %}
16 {% endfor %}
17 </select>
18 </div>
19
20 <div class="form-group">
21 <label for="new_list">New List</label>
22 <input type="text" name="new_list"
23 placeholder="New list name" class="form-control"
24 value="{{ request.form['new_list'] }}"></input>
25 </div>
26
27 <div class="form-group">
28 <button type="submit" class="btn btn-primary">Submit</button>
29 </div>
保存并关闭文件。
您已添加了一个新的<option>
标签,以参考新列表
选项,这将允许用户指定他们想要创建一个新列表,然后您添加另一个<div>
输入字段名为new_list
,这个字段是用户将输入他们想要创建的新列表的标题。
最后,运行开发服务器:
1flask run
然后访问索引页面:
1http://127.0.0.1:5000/
现在的应用程序将如下:
通过新添加到应用程序中,用户现在可以将要完成的项目标记为完整或将完成的项目恢复到未完成的状态,编辑和删除现有项目,并为不同类型的要完成的任务创建新列表。
您可以在 DigitalOcean Community Repository中浏览该应用程序的完整源代码。
结论
您现在有一个完整的任务应用程序,用户可以创建新的任务项目,将项目标记为完整,并编辑或删除现有项目,以及创建新列表的能力。您已经修改了Flask Web应用程序,添加了新的功能,并修改了数据库项目,特别是在一对多关系中。