作者选择自由和开源基金作为Write for DOnations计划的一部分来接受捐赠。

简介

Flask是一个轻量级的 Python Web 框架,它提供了有用的工具和功能,用于用 Python 语言创建 Web 应用程序。SQLAlchemy是一个 SQL 工具包,可为关系数据库提供高效、高性能的数据库访问。它提供了与 SQLite、MySQL 和 PostgreSQL 等多个数据库引擎交互的方法。它使您可以访问数据库的 SQL 功能。它还为您提供了一个对象关系映射器 (ORM),它允许您使用简单的 Python 对象和方法进行查询和处理数据。Flask-SQLAlchemy是一个 Flask 扩展,它使 SQLAlchemy 与 Flask 一起使用更容易,为您提供通过 SQLAlchemy 在 Flask 应用程序中与数据库交互的工具和方法。

在本教程中,您将使用 Flask 和 Flask-SQLAlchemy 创建一个员工管理系统,其中包含一个包含员工表的数据库。每个员工都将有一个唯一的 ID、名字、姓氏、唯一的电子邮件、年龄的整数值、他们加入公司的日期以及用于确定员工当前是活跃还是活跃的布尔值。不在办公室。

您将使用 Flask shell 查询表,并根据列值(例如,电子邮件)获取表记录。您将在某些条件下检索员工的记录,例如仅获取在职员工或获取不在办公室的员工列表。您将按列值对结果进行排序,并对查询结果进行计数和限制。最后,您将使用分页在 Web 应用程序中的每页显示一定数量的员工。

先决条件

  • 本地 Python 3 编程环境。按照如何为 Python 3系列安装和设置本地编程环境中的分发教程进行操作。在本教程中,我们将调用我们的项目目录flask_app

  • 了解基本的 Flask 概念,例如路由、视图函数和模板。如果您不熟悉 Flask,请查看如何使用 Flask 和 Python 创建您的第一个 Web 应用程序和如何在 Flask 应用程序中使用模板。

  • 了解基本的 HTML 概念。您可以查看我们的如何使用 HTML 构建网站教程系列以了解背景知识。

  • 了解基本的 Flask-SQLAlchemy 概念,例如设置数据库、创建数据库模型以及将数据插入数据库。有关背景知识,请参阅如何使用 Flask-SQLAlchemy 与 Flask 应用程序中的数据库进行交互。

第一步——设置数据库和模型

在这一步中,您将安装必要的包,并设置您的 Flask 应用程序、Flask-SQLAlchemy 数据库以及代表您将存储员工数据的employee表的员工模型。您将在employee表中插入一些员工,并添加一个路由和一个页面,所有员工都显示在应用程序的索引页面上。

首先,激活虚拟环境后,安装 Flask 和 Flask-SQLAlchemy:

pip install Flask Flask-SQLAlchemy

安装完成后,您将收到最后一行的输出:

Output
Successfully installed Flask-2.1.2 Flask-SQLAlchemy-2.5.1 Jinja2-3.1.2 MarkupSafe-2.1.1 SQLAlchemy-1.4.37 Werkzeug-2.1.2 click-8.1.3 greenlet-1.1.2 itsdangerous-2.1.2

安装所需的软件包后,在flask_app目录中打开一个名为app.py的新文件。该文件将包含用于设置数据库和 Flask 路由的代码:

nano app.py

将以下代码添加到app.py中。此代码将设置一个 SQLite 数据库和一个代表employee表的员工数据库模型,您将使用该表来存储您的员工数据:

烧瓶_app/app.py

import os
from flask import Flask, render_template, request, url_for, redirect
from flask_sqlalchemy import SQLAlchemy


basedir = os.path.abspath(os.path.dirname(__file__))

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] =\
        'sqlite:///' + os.path.join(basedir, 'database.db')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db = SQLAlchemy(app)


class Employee(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    firstname = db.Column(db.String(100), nullable=False)
    lastname = db.Column(db.String(100), nullable=False)
    email = db.Column(db.String(100), unique=True, nullable=False)
    age = db.Column(db.Integer, nullable=False)
    hire_date = db.Column(db.Date, nullable=False)
    active = db.Column(db.Boolean, nullable=False)

    def __repr__(self):
        return f'<Employee {self.firstname} {self.lastname}>'

保存并关闭文件。

在这里,您导入os模块,它使您可以访问各种操作系统接口。您将使用它为您的database.db数据库文件构建文件路径。

flask包中,导入应用程序所需的帮助器:Flask类用于创建 Flask 应用程序实例,render_template()用于呈现模板,request对象用于处理请求,url_for()用于构造 URL,redirect()函数用于重定向用户。有关路由和模板的更多信息,请参阅如何在 Flask 应用程序中使用模板。

然后,您从 Flask-SQLAlchemy 扩展中导入SQLAlchemy类,这使您可以访问 SQLAlchemy 中的所有函数和类,以及将 Flask 与 SQLAlchemy 集成的帮助程序和功能。您将使用它来创建连接到 Flask 应用程序的数据库对象。

要为您的数据库文件构建路径,您需要将基本目录定义为当前目录。您使用os.path.abspath()函数来获取当前文件目录的绝对路径。特殊的__file__变量保存当前app.py文件的路径名。您将基本目录的绝对路径存储在名为basedir的变量中。

然后创建一个名为app的 Flask 应用程序实例,用于配置两个 Flask-SQLAlchemy配置键:

  • SQLALCHEMY_DATABASE_URI:数据库 URI,用于指定要与之建立连接的数据库。在这种情况下,URI 遵循格式sqlite:///path/to/database.db。您使用os.path.join()函数智能地加入您构建并存储在basedir变量中的基本目录,文件名是database.db。这将连接到flask_app目录中的database.db数据库文件。启动数据库后,将创建该文件。

  • SQLALCHEMY_TRACK_MODIFICATIONS:启用或禁用跟踪对象修改的配置。您将其设置为False以禁用跟踪,这会使用更少的内存。有关更多信息,请参阅 Flask-SQLAlchemy 文档中的配置页面。

通过设置数据库 URI 并禁用跟踪来配置 SQLAlchemy 后,您使用SQLAlchemy类创建一个数据库对象,传递应用程序实例以将您的 Flask 应用程序与 SQLAlchemy 连接。您将数据库对象存储在名为db的变量中,您将使用该变量与数据库进行交互。

设置应用程序实例和数据库对象后,您从db.Model类继承创建名为Employee的数据库模型。此模型代表employee表,它具有以下列:

  • id:员工ID,整数主键。

  • firstname:员工的名字,最大长度为100个字符的字符串。nullable=False表示此列不应为空。

  • lastname:员工的姓氏,最大长度为100个字符的字符串。nullable=False表示此列不应为空。

  • email:员工邮箱,最大长度为100个字符的字符串。unique=True表示每封电子邮件都应该是唯一的。nullable=False表示它的值不应该为空。

  • age:员工年龄,整数值。

  • hire_date:员工入职日期。您将db.Date设置为列类型以将其声明为包含日期的列。

  • active:一个包含布尔值的列,用于指示员工当前是在职还是不在办公室。

特殊的__repr__函数允许您为每个对象提供一个字符串表示,以便在调试时识别它。在这种情况下,您使用员工的名字和姓氏来表示每个员工对象。

现在您已经设置了数据库连接和员工模型,您将编写一个 Python 程序来创建您的数据库和employee表,并用一些员工数据填充该表。

在您的flask_app目录中打开一个名为init_db.py的新文件:

nano init_db.py

添加以下代码删除现有的数据库表,从一个干净的数据库开始,创建employee表,并在其中插入 9 个员工:

烧瓶_app/init_db.py

from datetime import date
from app import db, Employee

db.drop_all()
db.create_all()

e1 = Employee(firstname='John',
              lastname='Doe',
              email='jd@example.com',
              age=32,
              hire_date=date(2012, 3, 3),
              active=True
              )

e2 = Employee(firstname='Mary',
              lastname='Doe',
              email='md@example.com',
              age=38,
              hire_date=date(2016, 6, 7),
              active=True
              )

e3 = Employee(firstname='Jane',
              lastname='Tanaka',
              email='jt@example.com',
              age=32,
              hire_date=date(2015, 9, 12),
              active=False
              )

e4 = Employee(firstname='Alex',
              lastname='Brown',
              email='ab@example.com',
              age=29,
              hire_date=date(2019, 1, 3),
              active=True
              )

e5 = Employee(firstname='James',
              lastname='White',
              email='jw@example.com',
              age=24,
              hire_date=date(2021, 2, 4),
              active=True
              )

e6 = Employee(firstname='Harold',
              lastname='Ishida',
              email='hi@example.com',
              age=52,
              hire_date=date(2002, 3, 6),
              active=False
              )

e7 = Employee(firstname='Scarlett',
              lastname='Winter',
              email='sw@example.com',
              age=22,
              hire_date=date(2021, 4, 7),
              active=True
              )

e8 = Employee(firstname='Emily',
              lastname='Vill',
              email='ev@example.com',
              age=27,
              hire_date=date(2019, 6, 9),
              active=True
              )

e9 = Employee(firstname='Mary',
              lastname='Park',
              email='mp@example.com',
              age=30,
              hire_date=date(2021, 8, 11),
              active=True
              )

db.session.add_all([e1, e2, e3, e4, e5, e6, e7, e8, e9])

db.session.commit()

在这里,您从datetime模块导入date()类,以使用它来设置员工雇用日期。

您导入数据库对象和Employee模型。您调用db.drop_all()函数删除所有现有表,以避免数据库中存在已填充的employee表的可能性,这可能会导致问题。这将在您执行init_db.py程序时删除所有数据库数据。有关创建、修改和删除数据库表的更多信息,请参阅如何使用 Flask-SQLAlchemy 与 Flask 应用程序中的数据库进行交互。

然后创建Employee模型的几个实例,它们代表您将在本教程中查询的员工,并使用db.session.add_all()函数将它们添加到数据库会话中。最后,您提交事务并将更改应用到使用db.session.commit()的数据库。

保存并关闭文件。

执行init_db.py程序:

python init_db.py

要查看您添加到数据库中的数据,请确保您的虚拟环境已激活,然后打开 Flask shell 以查询所有员工并显示他们的数据:

flask shell

运行以下代码查询所有员工并显示他们的数据:

from app import db, Employee


employees = Employee.query.all()

for employee in employees:
    print(employee.firstname, employee.lastname)
    print('Email:', employee.email)
    print('Age:', employee.age)
    print('Hired:', employee.hire_date)
    if employee.active:
        print('Active')
    else:
        print('Out of Office')
    print('----')

您使用query属性的all()方法来获取所有员工。您遍历结果,并显示员工信息。对于active列,您使用条件语句来显示员工的当前状态,'Active''Out of Office'

您将收到以下输出:

OutputJohn Doe
Email: jd@example.com
Age: 32
Hired: 2012-03-03
Active
----
Mary Doe
Email: md@example.com
Age: 38
Hired: 2016-06-07
Active
----
Jane Tanaka
Email: jt@example.com
Age: 32
Hired: 2015-09-12
Out of Office
----
Alex Brown
Email: ab@example.com
Age: 29
Hired: 2019-01-03
Active
----
James White
Email: jw@example.com
Age: 24
Hired: 2021-02-04
Active
----
Harold Ishida
Email: hi@example.com
Age: 52
Hired: 2002-03-06
Out of Office
----
Scarlett Winter
Email: sw@example.com
Age: 22
Hired: 2021-04-07
Active
----
Emily Vill
Email: ev@example.com
Age: 27
Hired: 2019-06-09
Active
----
Mary Park
Email: mp@example.com
Age: 30
Hired: 2021-08-11
Active
----

您可以看到我们添加到数据库中的所有员工都正确显示。

退出 Flask 外壳:

exit()

接下来,您将创建一个 Flask 路由来显示员工。打开app.py进行编辑:

nano app.py

在文件末尾添加以下路由:

烧瓶_app/app.py

...

@app.route('/')
def index():
    employees = Employee.query.all()
    return render_template('index.html', employees=employees)

保存并关闭文件。

这会查询所有员工,呈现一个index.html模板,并将您获取的员工传递给它。

创建一个模板目录和一个基本模板:

mkdir templates
nano templates/base.html

将以下内容添加到base.html:

烧瓶_app/templates/base.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{% block title %} {% endblock %} - FlaskApp</title>
    <style>
        .title {
            margin: 5px;
        }

        .content {
            margin: 5px;
            width: 100%;
            display: flex;
            flex-direction: row;
            flex-wrap: wrap;
        }

        .employee {
            flex: 20%;
            padding: 10px;
            margin: 5px;
            background-color: #f3f3f3;
            inline-size: 100%;
        }

        .name {
            color: #00a36f;
            text-decoration: none;
        }

        nav a {
            color: #d64161;
            font-size: 3em;
            margin-left: 50px;
            text-decoration: none;
        }

        .pagination {
            margin: 0 auto;
        }

        .pagination span {
            font-size: 2em;
            margin-right: 10px;
        }

        .page-number {
            color: #d64161;
            padding: 5px;
            text-decoration: none;
        }

        .current-page-number {
            color: #666
        }

    </style>
</head>
<body>
    <nav>
        <a href="{{ url_for('index') }}">FlaskApp</a>
        <a href="#">About</a>
    </nav>
    <hr>
    <div class="content">
        {% block content %} {% endblock %}
    </div>
</body>
</html>

保存并关闭文件。

在这里,您使用标题栏并添加一些 CSS 样式。您添加了一个导航栏,其中包含两项,一项用于索引页面,一项用于非活动的关于页面。此导航栏将在从该基本模板继承的模板中在整个应用程序中重用。内容块将替换为每个页面的内容。有关模板的更多信息,请查看如何在 Flask 应用程序中使用模板。

接下来,打开您在app.py中渲染的新index.html模板:

nano templates/index.html

将以下代码添加到文件中:

烧瓶_app/templates/index.html

{% extends 'base.html' %}

{% block content %}
    <h1 class="title">{% block title %} Employees {% endblock %}</h1>
    <div class="content">
        {% for employee in employees %}
            <div class="employee">
                <p><b>#{{ employee.id }}</b></p>
                <b>
                    <p class="name">{{ employee.firstname }} {{ employee.lastname }}</p>
                </b>
                <p>{{ employee.email }}</p>
                <p>{{ employee.age }} years old.</p>
                <p>Hired: {{ employee.hire_date }}</p>
                {% if employee.active %}
                    <p><i>(Active)</i></p>
                {% else %}
                    <p><i>(Out of Office)</i></p>
                {% endif %}
            </div>
        {% endfor %}
    </div>
{% endblock %}

在这里,您遍历员工并显示每个员工的信息。如果员工处于活动状态,则添加 (Active) 标签,否则显示 (Out of Office) 标签。

保存并关闭文件。

在激活虚拟环境的flask_app目录中,使用FLASK_APP环境变量告诉 Flask 应用程序(在本例中为app.py)。然后将FLASK_ENV环境变量设置为development以在开发模式下运行应用程序并访问调试器。有关 Flask 调试器的更多信息,请参阅如何处理 Flask 应用程序中的错误。使用以下命令执行此操作:

export FLASK_APP=app
export FLASK_ENV=development

接下来,运行应用程序:

flask run

在开发服务器运行的情况下,使用浏览器访问以下 URL:

http://127.0.0.1:5000/

您将在类似于以下内容的页面中看到您添加到数据库中的员工:

员工页面

让服务器保持运行,打开另一个终端,然后继续下一步。

您已经在索引页面上显示了您在数据库中的员工。接下来,您将使用 Flask shell 使用不同的方法查询员工。

第二步——查询记录

在这一步中,您将使用 Flask shell 来查询记录,并使用多种方法和条件过滤和检索结果。

激活编程环境后,设置FLASK_APPFLASK_ENV变量,然后打开 Flask shell:

export FLASK_APP=app
export FLASK_ENV=development
flask shell

导入db对象和Employee模型:

from app import db, Employee

检索所有记录

正如您在上一步中看到的,您可以使用query属性上的all()方法来获取表中的所有记录:

all_employees = Employee.query.all()
print(all_employees)

输出将是代表所有员工的对象列表:

Output
[<Employee John Doe>, <Employee Mary Doe>, <Employee Jane Tanaka>, <Employee Alex Brown>, <Employee James White>, <Employee Harold Ishida>, <Employee Scarlett Winter>, <Employee Emily Vill>, <Employee Mary Park>]

检索第一条记录

同样,可以使用first()方法获取第一条记录:

first_employee = Employee.query.first()
print(first_employee)

输出将是一个包含第一个员工数据的对象:

Output<Employee John Doe>

按ID检索记录

在大多数数据库表中,记录用唯一的 ID 标识。 Flask-SQLAlchemy 允许您通过get()方法使用其 ID 获取记录:

employee5 = Employee.query.get(5)
employee3 = Employee.query.get(3)
print(f'{employee5} | ID: {employee5.id}')
print(f'{employee3} | ID: {employee3.id}')

Output<Employee James White> | ID: 5
<Employee Jane Tanaka> | ID: 3

按列值检索一条或多条记录

要使用其中一列的值获取记录,请使用filter_by()方法。例如,使用其 ID 值获取记录,类似于get()方法:

employee = Employee.query.filter_by(id=1).first()
print(employee)

Output<Employee John Doe>

您使用first()是因为filter_by()可能会返回多个结果。

注意: 对于通过 ID 获取记录,使用get()方法是更好的方法。

再举一个例子,您可以使用员工的年龄来获取员工:

employee = Employee.query.filter_by(age=52).first()
print(employee)

Output<Employee Harold Ishida>

对于查询结果包含多个匹配记录的示例,请使用firstname列和名字Mary,这是两个员工共享的名称:

mary = Employee.query.filter_by(firstname='Mary').all()
print(mary)

Output[<Employee Mary Doe>, <Employee Mary Park>]

在这里,您使用all()来获取完整列表。您也可以使用first()仅获得第一个结果:

mary = Employee.query.filter_by(firstname='Mary').first()
print(mary)

Output<Employee Mary Doe>

您已经通过列值获取记录。接下来,您将使用逻辑条件查询您的表。

第三步——使用逻辑条件过滤记录

在复杂的、功能齐全的 Web 应用程序中,您经常需要使用复杂的条件从数据库中查询记录,例如根据考虑到员工位置、可用性、角色和职责的条件组合来获取员工。在这一步中,您将练习使用条件运算符。您将在query属性上使用filter()方法,使用具有不同运算符的逻辑条件过滤查询结果。例如,您可以使用逻辑运算符来获取当前不在办公室的员工列表,或需要升职的员工,还可以提供员工休假时间的日历等。

等于

您可以使用的最简单的逻辑运算符是相等运算符==,其行为方式与filter_by()类似。例如,要获取firstname列的值为Mary的所有记录,可以使用filter()方法,如下所示:

mary = Employee.query.filter(Employee.firstname == 'Mary').all()
print(mary)

在这里,您使用语法Model.column == value作为filter()方法的参数。filter_by()方法是此语法的快捷方式。

结果与filter_by()方法的结果相同,条件相同:

Output[<Employee Mary Doe>, <Employee Mary Park>]

filter_by()一样,也可以使用first()的方法得到第一个结果:

mary = Employee.query.filter(Employee.firstname == 'Mary').first()
print(mary)

Output<Employee Mary Doe>

不等于

filter()方法允许您使用!=Python 运算符来获取记录。例如,要获取不在办公室的员工列表,可以使用以下方法:

out_of_office_employees = Employee.query.filter(Employee.active != True).all()
print(out_of_office_employees)

Output[<Employee Jane Tanaka>, <Employee Harold Ishida>]

在这里,您使用Employee.active != True条件来过滤结果。

小于

您可以使用<运算符来获取给定列的值小于给定值的记录。例如,要获取 32 岁以下的员工列表:

employees_under_32 = Employee.query.filter(Employee.age < 32).all()

for employee in employees_under_32:
    print(employee.firstname, employee.lastname)
    print('Age: ', employee.age)
    print('----')

Output
Alex Brown
Age:  29
----
James White
Age:  24
----
Scarlett Winter
Age:  22
----
Emily Vill
Age:  27
----
Mary Park
Age:  30
----

对小于或等于给定值的记录使用<=运算符。例如,要在上一个查询中包含 32 岁的员工:

employees_32_or_younger = Employee.query.filter(Employee.age <=32).all()

for employee in employees_32_or_younger:
    print(employee.firstname, employee.lastname)
    print('Age: ', employee.age)
    print('----')

Output
John Doe
Age:  32
----
Jane Tanaka
Age:  32
----
Alex Brown
Age:  29
----
James White
Age:  24
----
Scarlett Winter
Age:  22
----
Emily Vill
Age:  27
----
Mary Park
Age:  30
----

大于

类似地,>运算符获取给定列的值大于给定值的记录。例如,要让员工超过 32 岁:

employees_over_32 = Employee.query.filter(Employee.age > 32).all()

for employee in employees_over_32:
    print(employee.firstname, employee.lastname)
    print('Age: ', employee.age)
    print('----')

OutputMary Doe
Age:  38
----
Harold Ishida
Age:  52
----

>=运算符用于大于或等于给定值的记录。例如,您可以在前面的查询中再次包含 32 岁的员工:

employees_32_or_older = Employee.query.filter(Employee.age >=32).all()

for employee in employees_32_or_older:
    print(employee.firstname, employee.lastname)
    print('Age: ', employee.age)
    print('----')

Output
John Doe
Age:  32
----
Mary Doe
Age:  38
----
Jane Tanaka
Age:  32
----
Harold Ishida
Age:  52
----

SQLAlchemy 还提供了一种方法来获取记录,其中列的值与给定值列表中的值匹配,使用列上的in_()方法,如下所示:

names = ['Mary', 'Alex', 'Emily']
employees = Employee.query.filter(Employee.firstname.in_(names)).all()
print(employees)

Output[<Employee Mary Doe>, <Employee Alex Brown>, <Employee Emily Vill>, <Employee Mary Park>]

在这里,您使用语法为Model.column.in_(iterable)的条件,其中iterable是您可以遍历](https://docs.python.org/3.8/glossary.html#term-iterable)的任何类型的[对象。再举一个例子,您可以使用range()Python 函数来获取某个年龄段的员工。以下查询获取所有 30 多岁的员工。

employees_in_30s = Employee.query.filter(Employee.age.in_(range(30, 40))).all()
for employee in employees_in_30s:
    print(employee.firstname, employee.lastname)
    print('Age: ', employee.age)
    print('----')

OutputJohn Doe
Age:  32
----
Mary Doe
Age:  38
----
Jane Tanaka
Age:  32
----
Mary Park
Age:  30
----

不在

in_()方法类似,您可以使用not_in()方法获取列值不在给定迭代中的记录:

names = ['Mary', 'Alex', 'Emily']
employees = Employee.query.filter(Employee.firstname.not_in(names)).all()
print(employees)

Output
[<Employee John Doe>, <Employee Jane Tanaka>, <Employee James White>, <Employee Harold Ishida>, <Employee Scarlett Winter>]

在这里,您可以获取除names列表中的名字之外的所有员工。

您可以使用db.and_()函数将多个条件连接在一起,该函数的工作方式类似于 Python 的and运算符。

例如,假设您想获取所有 32 岁的员工并且当前处于活动状态。首先,您可以使用filter_by()方法检查谁是 32(如果需要,您也可以使用filter()):

for employee in Employee.query.filter_by(age=32).all():
    print(employee)
    print('Age:', employee.age)
    print('Active:', employee.active)
    print('-----')

Output<Employee John Doe>
Age: 32
Active: True
-----
<Employee Jane Tanaka>
Age: 32
Active: False
-----

在这里,您会看到 John 和 Jane 是 32 岁的员工。约翰很活跃,简不在办公室。

要获得 32 **和 ** 活跃的员工,您将对filter()方法使用两个条件:

  • Employee.age == 32

  • Employee.active == True

要将这两个条件结合在一起,请使用db.and_()函数,如下所示:

active_and_32 = Employee.query.filter(db.and_(Employee.age == 32,
                                      Employee.active == True)).all()
print(active_and_32)

Output[<Employee John Doe>]

在这里,您使用语法filter(db.and_(condition1, condition2))

对查询使用all()会返回与这两个条件匹配的所有记录的列表。可以使用first()方法得到第一个结果:

active_and_32 = Employee.query.filter(db.and_(Employee.age == 32,
                                      Employee.active == True)).first()
print(active_and_32)

Output<Employee John Doe>

对于更复杂的示例,您可以使用db.and_()date()函数来获取在特定时间跨度内被雇用的员工。在此示例中,您将获得 2019 年雇用的所有员工:

from datetime import date

hired_in_2019 = Employee.query.filter(db.and_(Employee.hire_date >= date(year=2019, month=1, day=1), Employee.hire_date < date(year=2020, month=1, day=1))).all()

for employee in hired_in_2019:
    print(employee, ' | Hired: ', employee.hire_date)

Output<Employee Alex Brown>  | Hired:  2019-01-03
<Employee Emily Vill>  | Hired:  2019-06-09

此处导入date()函数,并使用db.and_()函数过滤结果以组合以下两个条件:

  • Employee.hire_date >= date(year=2019, month=1, day=1):这是True,适用于 2019 年 1 月的第一天或之后雇用的员工。

  • Employee.hire_date < date(year=2020, month=1, day=1):这是True,适用于 2020 年 1 月 1 日之前雇用的员工。

结合这两个条件可以获得从 2019 年第一天到 2020 年第一天之前雇用的员工。

db.and_()类似,db.or_()函数结合了两个条件,其行为类似于 Python 中的or运算符。它获取满足两个条件之一的所有记录。例如,要获取 32 岁 52 岁的员工,可以将两个条件与db.or_()函数组合如下:

employees_32_or_52 = Employee.query.filter(db.or_(Employee.age == 32, Employee.age == 52)).all()

for e in employees_32_or_52:
    print(e, '| Age:', e.age)

Output<Employee John Doe> | Age: 32
<Employee Jane Tanaka> | Age: 32
<Employee Harold Ishida> | Age: 52

您还可以在传递给filter()方法的条件中对字符串值使用startswith()endswith()方法。例如,要获取名字以字符串'M'开头和姓以字符串'e'结尾的所有员工:

employees = Employee.query.filter(db.or_(Employee.firstname.startswith('M'), Employee.lastname.endswith('e'))).all()

for e in employees:
    print(e)

Output<Employee John Doe>
<Employee Mary Doe>
<Employee James White>
<Employee Mary Park>

在这里,您结合了以下两个条件:

  • Employee.firstname.startswith('M'):匹配名字以'M'开头的员工。

  • Employee.lastname.endswith('e'):匹配姓氏以'e'结尾的员工。

您现在可以在 Flask-SQLAlchemy 应用程序中使用逻辑条件过滤查询结果。接下来,您将对从数据库中获得的结果进行排序、限制和计数。

第 4 步 — 排序、限制和计数结果

在 Web 应用程序中,您通常需要在显示记录时对其进行排序。例如,您可能有一个页面来显示每个部门的最新员工,以让团队的其他成员了解新员工,或者您可以通过首先显示最老员工来对员工进行排序,以识别长期员工。在某些情况下,您还需要限制您的结果,例如在小侧边栏上仅显示最近的三名员工。而且您经常需要统计查询的结果,例如,显示当前活跃的员工数量。在此步骤中,您将学习如何对结果进行排序、限制和计数。

订购结果

要使用特定列的值对结果进行排序,请使用order_by()方法。例如,要按员工的名字排序结果:

employees = Employee.query.order_by(Employee.firstname).all()
print(employees)

Output[<Employee Alex Brown>, <Employee Emily Vill>, <Employee Harold Ishida>, <Employee James White>, <Employee Jane Tanaka>, <Employee John Doe>, <Employee Mary Doe>, <Employee Mary Park>, <Employee Scarlett Winter>]

如输出所示,结果按员工名字的字母顺序排列。

您可以按其他列排序。例如,您可以使用姓氏来订购员工:

employees = Employee.query.order_by(Employee.lastname).all()
print(employees)

Output[<Employee Alex Brown>, <Employee John Doe>, <Employee Mary Doe>, <Employee Harold Ishida>, <Employee Mary Park>, <Employee Jane Tanaka>, <Employee Emily Vill>, <Employee James White>, <Employee Scarlett Winter>]

您还可以按雇用日期对员工进行排序:

em_ordered_by_hire_date = Employee.query.order_by(Employee.hire_date).all()

for employee in em_ordered_by_hire_date:
    print(employee.firstname, employee.lastname, employee.hire_date)

Output
Harold Ishida 2002-03-06
John Doe 2012-03-03
Jane Tanaka 2015-09-12
Mary Doe 2016-06-07
Alex Brown 2019-01-03
Emily Vill 2019-06-09
James White 2021-02-04
Scarlett Winter 2021-04-07
Mary Park 2021-08-11

如输出所示,此订单是从最早雇用到最新雇用的结果。要颠倒顺序并使其从最新的雇员降到最早的雇员,请使用desc()方法,如下所示:

em_ordered_by_hire_date_desc = Employee.query.order_by(Employee.hire_date.desc()).all()

for employee in em_ordered_by_hire_date_desc:
    print(employee.firstname, employee.lastname, employee.hire_date)

OutputMary Park 2021-08-11
Scarlett Winter 2021-04-07
James White 2021-02-04
Emily Vill 2019-06-09
Alex Brown 2019-01-03
Mary Doe 2016-06-07
Jane Tanaka 2015-09-12
John Doe 2012-03-03
Harold Ishida 2002-03-06

您还可以将order_by()方法与filter()方法结合起来对过滤结果进行排序。以下示例获取 2021 年雇用的所有员工并按年龄排序:

from datetime import date
hired_in_2021 = Employee.query.filter(db.and_(Employee.hire_date >= date(year=2021, month=1, day=1), Employee.hire_date < date(year=2022, month=1, day=1))).order_by(Employee.age).all()

for employee in hired_in_2021:
    print(employee.firstname, employee.lastname,
          employee.hire_date, '| Age', employee.age)

OutputScarlett Winter 2021-04-07 | Age 22
James White 2021-02-04 | Age 24
Mary Park 2021-08-11 | Age 30

在这里,您使用带有两个条件的db.and_()函数:Employee.hire_date >= date(year=2021, month=1, day=1)表示在 2021 年第一天或之后聘用的员工,Employee.hire_date < date(year=2022, month=1, day=1)表示在 2022 年第一天之前聘用的员工。然后您使用order_by()方法对结果员工按年龄排序.

限制结果

在大多数实际情况下,查询数据库表时,您可能会获得多达数百万个匹配结果,有时需要将结果限制在一定数量。要限制 Flask-SQLAlchemy 中的结果,可以使用limit()方法。以下示例查询employee表并仅返回前三个匹配结果:

employees = Employee.query.limit(3).all()
print(employees)

Output[<Employee John Doe>, <Employee Mary Doe>, <Employee Jane Tanaka>]

您可以将limit()与其他方法一起使用,例如filterorder_by。例如,您可以使用limit()方法获取 2021 年雇用的最后两名员工,如下所示:

from datetime import date
hired_in_2021 = Employee.query.filter(db.and_(Employee.hire_date >= date(year=2021, month=1, day=1), Employee.hire_date < date(year=2022, month=1, day=1))).order_by(Employee.age).limit(2).all()

for employee in hired_in_2021:
    print(employee.firstname, employee.lastname,
          employee.hire_date, '| Age', employee.age)

OutputScarlett Winter 2021-04-07 | Age 22
James White 2021-02-04 | Age 24

在这里,您使用上一节中的相同查询和额外的limit(2)方法调用。

计数结果

统计一个查询的结果个数,可以使用count()方法。例如,要获取当前在数据库中的员工数量:

employee_count = Employee.query.count()
print(employee_count)

Output9

您可以将count()方法与其他类似limit()的查询方法结合使用。例如,要获取 2021 年雇用的员工人数:

from datetime import date
hired_in_2021_count = Employee.query.filter(db.and_(Employee.hire_date >= date(year=2021, month=1, day=1), Employee.hire_date < date(year=2022, month=1, day=1))).order_by(Employee.age).count()
print(hired_in_2021_count)

Output3

在这里,您使用之前用于获取 2021 年雇用的所有员工的相同查询。您使用count()检索条目数,即 3。

您已经在 Flask-SQLAlchemy 中对查询结果进行了排序、限制和计数。接下来,您将学习如何将查询结果拆分为多个页面,以及如何在 Flask 应用程序中创建分页系统。

步骤 5 — 在多页上显示长记录列表

在此步骤中,您将修改主路由以使索引页面在多个页面上显示员工,以便更轻松地浏览员工列表。

首先,您将使用 Flask shell 来演示如何使用 Flask-SQLAlchemy 中的分页功能。如果您还没有打开 Flask shell:

flask shell

假设您要将表中的员工记录拆分为多个页面,每页有两个项目。您可以使用paginate()查询方法执行此操作,如下所示:

page1 = Employee.query.paginate(page=1, per_page=2)
print(page1)
print(page1.items)

Output<flask_sqlalchemy.Pagination object at 0x7f1dbee7af80>
[<Employee John Doe>, <Employee Mary Doe>]

您使用paginate()查询方法的page参数来指定您要访问的页面,在本例中为第一页。per_page参数指定每个页面必须具有的项目数。在这种情况下,您将其设置为2以使每个页面有两个项目。

这里的page1变量是一个 pagination 对象,它使您可以访问用于管理分页的属性和方法。

您可以使用items属性访问页面的项目。

访问下一页,可以像这样使用分页对象的next()方法,返回的结果也是一个分页对象:

page2 = page1.next()

print(page2.items)
print(page2)

Output[<Employee Jane Tanaka>, <Employee Alex Brown>]

<flask_sqlalchemy.Pagination object at 0x7f1dbee799c0>

您可以使用prev()方法获取上一页的分页对象。在以下示例中,您访问第四页的分页对象,然后访问其上一页的分页对象,即第 3 页:

page4 = Employee.query.paginate(page=4, per_page=2)
print(page4.items)
page3 = page4.prev()
print(page3.items)

Output[<Employee Scarlett Winter>, <Employee Emily Vill>]

[<Employee James White>, <Employee Harold Ishida>]

您可以使用page属性访问当前页码,如下所示:

print(page1.page)
print(page2.page)

Output1
2

要获取总页数,请使用分页对象的pages属性。在以下示例中,page1.pagespage2.pages返回相同的值,因为总页数是一个常数:

print(page1.pages)
print(page2.pages)

Output5
5

对于项目总数,使用分页对象的total属性:

print(page1.total)
print(page2.total)

Output9
9

在这里,由于查询了所有员工,所以分页中的项目总数为 9,因为数据库中有 9 个员工。

以下是分页对象具有的其他一些属性:

  • prev_num:前页码。

  • next_num:下一页的页码。

  • has_next:True如果有下一页。

  • has_prev:True如果有前一页。

  • per_page:每页的项目数。

分页对象还有一个iter_pages()方法,您可以循环访问以访问页码。例如,您可以像这样打印所有页码:

pagination = Employee.query.paginate(page=1, per_page=2)

for page_num in pagination.iter_pages():
    print(page_num)

Output1
2
3
4
5

以下是如何使用分页对象和iter_pages()方法访问所有页面及其项目的演示:

pagination = Employee.query.paginate(page=1, per_page=2)

for page_num in pagination.iter_pages():
    print('PAGE', pagination.page)
    print('-')
    print(pagination.items)
    print('-'*20)
    pagination = pagination.next()

Output
PAGE 1
-
[<Employee John Doe>, <Employee Mary Doe>]
--------------------
PAGE 2
-
[<Employee Jane Tanaka>, <Employee Alex Brown>]
--------------------
PAGE 3
-
[<Employee James White>, <Employee Harold Ishida>]
--------------------
PAGE 4
-
[<Employee Scarlett Winter>, <Employee Emily Vill>]
--------------------
PAGE 5
-
[<Employee Mary Park>]
--------------------

在这里,您创建一个从第一页开始的分页对象。您可以使用for循环和iter_pages()分页方法来循环浏览页面。您打印页码和页面项目,并使用next()方法将pagination对象设置为其下一页的分页对象。

您还可以使用filter()order_by()方法与paginate()方法对过滤和排序的查询结果进行分页。例如,您可以让超过 30 岁的员工按年龄排序结果,然后对结果进行分页,如下所示:

pagination = Employee.query.filter(Employee.age > 30).order_by(Employee.age).paginate(page=1, per_page=2)

for page_num in pagination.iter_pages():
    print('PAGE', pagination.page)
    print('-')
    for employee in pagination.items:
        print(employee, '| Age: ', employee.age)
    print('-'*20)
    pagination = pagination.next()

OutputPAGE 1
-
<Employee John Doe> | Age:  32
<Employee Jane Tanaka> | Age:  32
--------------------
PAGE 2
-
<Employee Mary Doe> | Age:  38
<Employee Harold Ishida> | Age:  52
--------------------

现在您已经对 Flask-SQLAlchemy 中的分页工作原理有了深入的了解,您将编辑应用程序的索引页面以在多个页面上显示员工以便于导航。

退出 Flask 外壳:

exit()

要访问不同的页面,您将使用 URL 参数,也称为 URL 查询字符串,这是一种通过 URL 将信息传递给应用程序的方法。参数在?符号后的 URL 中传递给应用程序。例如,要传递具有不同值的page参数,您可以使用以下 URL:

http://127.0.0.1:5000/?page=1
http://127.0.0.1:5000/?page=3

这里,第一个 URL 将值1传递给 URL 参数page。第二个 URL 将值3传递给相同的参数。

打开app.py文件:

nano app.py

编辑索引路由,如下所示:

@app.route('/')
def index():
    page = request.args.get('page', 1, type=int)
    pagination = Employee.query.order_by(Employee.firstname).paginate(
        page, per_page=2)
    return render_template('index.html', pagination=pagination)

在这里,您使用request.args对象及其get()方法获取pageURL 参数的值。例如/?page=1将从pageURL 参数中获取值1。您将1作为默认值传递,并将intPython 类型作为参数传递给type参数以确保该值是整数。

接下来创建一个pagination对象,按名字对查询结果进行排序。您将pageURL 参数值传递给paginate()方法,并通过将值2传递给per_page参数将结果分成每页两个项目。

最后,将构建的pagination对象传递给渲染的index.html模板。

保存并关闭文件。

接下来,编辑index.html模板以显示分页项:

nano templates/index.html

通过添加指示当前页面的h2标题来更改内容div标记,并将for循环更改为循环通过pagination.items对象而不是employees对象,该对象不再可用:

<div class="content">
    <h2>(Page {{ pagination.page }})</h2>
    {% for employee in pagination.items %}
        <div class="employee">
            <p><b>#{{ employee.id }}</b></p>
            <b>
                <p class="name">{{ employee.firstname }} {{ employee.lastname }}</p>
            </b>
            <p>{{ employee.email }}</p>
            <p>{{ employee.age }} years old.</p>
            <p>Hired: {{ employee.hire_date }}</p>
            {% if employee.active %}
                <p><i>(Active)</i></p>
            {% else %}
                <p><i>(Out of Office)</i></p>
            {% endif %}
        </div>
    {% endfor %}
</div>

保存并关闭文件。

如果您还没有,请设置FLASK_APPFLASK_ENV环境变量并运行开发服务器:

export FLASK_APP=app
export FLASK_ENV=development
flask run

现在,导航到具有不同pageURL 参数值的索引页面:

http://127.0.0.1:5000/
http://127.0.0.1:5000/?page=2
http://127.0.0.1:5000/?page=4
http://127.0.0.1:5000/?page=19

正如您之前在 Flask shell 中看到的那样,您将看到不同的页面,每个页面都有两个项目,并且每个页面上的项目不同。

分页索引

如果给定的页码没有退出,你会得到一个404 Not FoundHTTP 错误,前面的 URL 列表中的最后一个 URL 就是这种情况。

接下来,您将创建一个分页小部件来在页面之间导航,您将使用分页对象的一些属性和方法来显示所有页码,每个页码链接到其专用页面,以及一个<<<按钮用于返回如果当前页面有前一页,如果存在则有一个>>>按钮用于转到下一页。

分页小部件将如下所示:

分页小工具分页小部件2

要添加它,请打开index.html:

nano templates/index.html

通过在内容div标记下方添加以下突出显示的div标记来编辑文件:

烧瓶_app/templates/index.html

<div class="content">
    {% for employee in pagination.items %}
        <div class="employee">
            <p><b>#{{ employee.id }}</b></p>
            <b>
                <p class="name">{{ employee.firstname }} {{ employee.lastname }}</p>
            </b>
            <p>{{ employee.email }}</p>
            <p>{{ employee.age }} years old.</p>
            <p>Hired: {{ employee.hire_date }}</p>
            {% if employee.active %}
                <p><i>(Active)</i></p>
            {% else %}
                <p><i>(Out of Office)</i></p>
            {% endif %}
        </div>
    {% endfor %}
</div>

<div class="pagination">
    {% if pagination.has_prev %}
        <span>
            <a class='page-number' href="{{ url_for('index', page=pagination.prev_num) }}">
                {{ '<<<' }}
            </a>
        </span>
    {% endif %}

    {% for number in pagination.iter_pages() %}
        {% if pagination.page != number %}
            <span>
                    <a class='page-number'
                        href="{{ url_for('index', page=number) }}">
                    {{ number }}
                    </a>
            </span>
        {% else %}
            <span class='current-page-number'>{{ number }}</span>
        {% endif %}
    {% endfor %}

    {% if pagination.has_next %}
        <span>
            <a class='page-number'
                href="{{ url_for('index', page=pagination.next_num) }}">
                {{ '>>>' }}
            </a>
        </span>
    {% endif %}
</div>

保存并关闭文件。

在这里,如果当前页面不是第一页,则使用条件if pagination.has_prev<<<链接添加到上一页。您使用url_for('index', page=pagination.prev_num)函数调用链接到上一页,在其中链接到索引视图函数,将pagination.prev_num值传递给pageURL 参数。

要显示指向所有可用页码的链接,您可以遍历pagination.iter_pages()方法的项目,该方法会在每个循环中为您提供一个页码。

您使用if pagination.page != number条件来查看当前页码是否与当前循环中的页码不同。如果条件为真,则链接到该页面以允许用户将当前页面更改为另一个页面。否则,如果当前页面与循环编号相同,则显示没有链接的编号。这允许用户知道分页小部件中的当前页码。

最后,您使用pagination.has_next条件来查看当前页面是否有下一页,在这种情况下,您使用url_for('index', page=pagination.next_num)调用和>>>链接链接到它。

在浏览器中导航到索引页面:http://127.0.0.1:5000/

您会看到分页小部件功能齐全:

分页小工具分页小部件2

在这里,您使用>>>移动到下一页,使用<<<移动上一页,但您也可以使用您喜欢的任何其他字符,例如><<img>标签中的图像。

您已经在多个页面上展示了员工,并学习了如何在 Flask-SQLAlchemy 中处理分页。您现在可以在您构建的其他 Flask 应用程序上使用您的分页小部件。

结论

您使用 Flask-SQLAlchemy 创建了一个员工管理系统。您根据列值和简单和复杂的逻辑条件查询了一个表并过滤了结果。您对查询结果进行排序、计数和限制。并且您创建了一个分页系统来在您的 Web 应用程序的每个页面上显示一定数量的记录,并在页面之间导航。

您可以将在本教程中学到的知识与我们其他一些 Flask-SQLAlchemy 教程中解释的概念结合使用,为您的员工管理系统添加更多功能:

  • 如何使用 Flask-SQLAlchemy 与 Flask 应用程序中的数据库进行交互了解如何添加、编辑或删除员工。

  • 如何在 Flask-SQLAlchemy 中使用一对多的数据库关系学习如何使用一对多的关系来创建一个部门表,将每个员工链接到他们所属的部门。

  • 如何使用 Flask-SQLAlchemy 使用多对多数据库关系学习如何使用多对多关系创建tasks表并将其链接到employee表,其中每个员工有许多任务,每个任务分配给多个员工。

如果您想了解更多关于 Flask 的信息,请查看如何使用 Flask系列构建 Web 应用程序中的其他教程。

Logo

学AI,认准AI Studio!GPU算力,限时免费领,邀请好友解锁更多惊喜福利 >>>

更多推荐