利用Flask进行web开发中SQLAlchemy操作数据库和Bootstrap风格分页相关问题。
数据库
使用SQLAlchemy控制数据库,在主程序前面一部分使用db对象的继承来建立表的模型类,将表的模型类实例化就是一条记录,通过db的session对象,完成事务提交。关于数据库连接,我有若干问题:在查询时,是否一定要在表的模型类上查询,如果需要多个表联合查询,怎么不使用原生SQL而使用ORM方案实现?在完成大作业收集资料的过程中,我有发现网络上对SQLAlcemy中db对象直接进行查询的,那在db对象上查询和在模型类上查询有何区别?
查询动作中,查询是针对模型类中的query对象进行查询。ORM查询完的结果依然是一个query,只不过范围更加小。我的理解是,与原生的SQL一样,这里的query对象就是一个虚表,通过查询可以逐步缩小虚表的范围。既然结果还是query对象,就说明可以继续使用查询的方法,从而实现嵌套查询。可这还没有解决连接查询的相关问题。
此书数据库部分的后面一半在谈及数据一对一一对多多对多的关系中涉及到了链接查询。然而,书中的连接查询是基于数据库中的表已经有了外键约束的情况下进行的。我认为,外键没必要使用外键约束,外键约束会带来后期修改不便、并发易发生死锁、数据更新强制校验等等在维护、性能上的问题。所以在不使用原生SQL的情况下,这本书没有给出较好的链接查询方案,需要其他资料。至于两种查询方式的区别,书上只讲了在模型类上查询这一种方法,没有对比无法知晓,于是我借助之前大作业的数据库和flask程序,通过做实验了解了大概:两种方式本质上在于SQL子句FROM 上的不同。
在模型类上查询,语法是<table>.query.filter(<conditions>) ,是以模型类对应的表为信息来源,不能再链接其他的表:
>>> print(User.query.filter(User.uid=='012000000000'))
SELECT user.uid AS user_uid, user.name AS user_name
FROM user
WHERE user.uid = %(uid_1)s
>>> print(User.query(Reward).filter(User.uid==Reward.uid))
Traceback (most recent call last):
File "<console>", line 1, in <module>
TypeError: 'BaseQuery' object is not callable
在db的session对象上查询,语法是db.session.query(<tables>).filter(<conditions>) ,是以query方法参数中的表为信息来源,并进行连接查询:
>>> print(db.session.query(Reward, User).filter(User.uid==Reward.uid))
SELECT
reward.id AS reward_id, reward.uid AS reward_uid,
user.uid AS user_uid, user.name AS user_name
FROM reward, user
WHERE user.uid = reward.uid
这同时就解决了以上如何进行连接查询的问题。在原来写的代码中,我不会SQL,也不懂如何使用SQLAlchemy进行链接查询,我竟然分别在两个表中查询所有记录,在python中使用嵌套循环比对筛选结果,可见性能十分差劲,代码写的令人发指。
关于数据库还有一些问题:在flask环境下是如何初始化数据库的,后续重新启动web应用程序的时候会不会对数据库造成影响?是否能将数据库相关的代码单独放置到一个模块中,与主程序分离?数据库相关的配置信息是否可以与主程序分离,分离的话应该存储在哪里?分页又应该如何操作,怎么将客户端的分页对应到服务端上面的?
初始化数据库,需要在flask shell中调用主程序中实例化的db对象的create_all()方法。通过此方法创建后,数据库内就有对应的表了,再在主程序中修改模型定义,即使再次调用create_all()方法,也无法修改数据库中的表,就是说,web应用无法再对DBMS中的结构造成影响。书中给出的修改方案是调用drop_all()方法,将所有结构删除,直接重构。我认为,除了重构之外,还可以在主程序模型改变后,在DBMS内使用SQL语句进行对应的修改,当然,这样有不一致的风险,需要额外的注意。
一开始,没有仔细看书的我以为书上给出了分离的方案,然而,书上确实讲了有分离,但他分离的只是数据库的配置信息,将数据库的地址还有账户密码写入到了一个环境变量文件中。显然这无法达到我需要将数据库表的声明与主程序分离的目的。我将数据库表直接剪切出来,粘贴到另一个py文件中,然后再from app import db ,在主程序中再from db_setting import * ,看能否通过循环引用达到分离的目的。但是循环引用本来就是不允许的,主程序的还没加载好呢,数据库怎么回调主程序来生成对应的表结构呢?报错更是证明了这一点:ImportError: cannot import name 'db' from 'app' 。通过观察代码结构我发现,表结构是只依赖总的db对象的,db对象我既然不能通过包导入的方式引入,那是否可以通过函数的方式引入呢?python中支持在函数内声明一个类,这正好可以在函数中声明表结构然后return出去。我干脆将数据库表的声明以函数的形式写到加载配置的py文件中,在主程序上下文激活和数据库链接后,再用加载配置中的数据库表初始化函数(自己写的)来声明表结构:
def db_user(db):
class User(db.Model):
__tablename__ = "user"
uid = db.Column(db.CHAR(13), primary_key=True)
name = db.Column(db.VARCHAR(20), nullable=False)
return User
import app_init
''' do something... '''
User = app_init.db_user(db)
至于分页的问题,书中在这一章只提到了SQLAlchemy查询方法中有一个paginate() 函数,查询后返回一个Pagination对象,并没有说具体用法,往后翻翻大概是在搭建博客的实战部分有讲解。
分页
分页在后端是基于数据库查询的,在SQLAlchemy中,会将通过分页方法获得的数据经过打包,成为一个便于前端引用进行分页展示的对象。这个数据对象中有较多的数据,类属性具体明细有:items当前页面记录的列表形式,每个元素是一个元组类型 page当前的页数 per_page每页的元素个数 pages总页数 total记录的总数量 next_num prev_num下上一页的页数 has_next has_prev是否存在后前一页面 query分页的原查询(不带分页方法的查询内容) prev() next()前后一页的分页对象 。
结合bootstrap-flask插件,可以非常方便地实现分页功能。此插件默认的实现翻页的方式是通过在url中添加page参数实现的,是一个比较通用的接口。在页面中的使用方法也比较简单,首先是在后台程序中导入扩展并且注册:
from flask_bootstrap import Bootstrap4
bootstrap = Bootstrap4()
bootstrap.init_app(app)
然后再到模板页面中导入扩展的模板宏,直接以分页变量作为参数传到宏中运算即可:
{% from 'bootstrap/pagination.html' import render_pagination %}
...
<div class="row">
<div class="clo-12">
{{ render_pagination(articles) }}
</div> {# articles: SQLAlchemy Pagination #}
</div>
...
|