3.1概念OMP
Object-Relation Mapping对象关系映射,调用模型对象生成对应的sql语句,通过ORM配置不同的数据库。
flasksqlalchemy 是一个简化了 SQLAlchemy 操作的flask扩展
优点:
对数据库的操作都转化成对类属性和方法的操作,不用编写各种数据库的 sql语句,只需要面向对象编程, 不需要面向数据库编写代码;实现了数据模型与数据库的解耦, 屏蔽了不同数据库操作上的差异,通过简单的配置就可以轻松更换数据库, 而不需要修改代码不在关注用的是 mysql,oracle等
缺点:
相比较直接使用SQL语句操作数据库,有性能损失,根据对象的操作转换成SQL语句,根据查询的结果转化成对象, 在映射过程中有性能损失.
3.2安装
框架安装 pip install flask-sqlalchemy
操作mysql数据库 pip install flask-mysqldb
3.3常用字段类型
Integer 普通整数,一般是32位
Float 浮点数
String(字符串长度) 字符串
Boolean 布尔类型
Enum 枚举类型
Text 文本类型
Date 日期
DateTime 日期和时间
Time 时间
3.4常用字段约束
primary_key 如果为True,代表表的主键
unique 如果为True,代表这列不允许出现重复的值
index 如果为True,为这列创建索引,提高查询效率
nullable 如果为True,允许有空值,如果为False,不允许有空值
default 为这列定义默认值
3.5常用关系选项
backref 在关系的另一模型中添加反向引用
secondary 指定多对多关系中关系表的名字
3.6创建一个应用程序对象
from flask import Flask
app = Flask(__name__)
3.7数据库连接配置
数据库使用URL指定
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:[email protected]:3306/test'
动态追踪修改设置
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
查询时会显示原始SQL语句
app.config['SQLALCHEMY_ECHO'] = True
3.8初始化数据库
先初始化一个db对象
from flask.ext.sqlalchemy import SQLAlchemy
db = SQLAlchemy()
再调用init_app()方法初始化数据库
db.init_app(app)
可以合在一起
db.SQLAlchemy(app)
根据模型用来创建以及删除表格的方法
db.drop_all()
db.create_all()
利用对象的会话(session)来操作数据库
Model声明基类,有个query属性,可以用来查询模型
3.9增删改操作
3.9.1增
db.session.add(模型对象)
db.session.add_all(模型对象列表)
3.9.2删
db.session.delete(模型对象)
3.9.3改
先查询到要修改的对象,比如是user
user.name=“要修改成的名字”
或者
User.query.filter_by(name=“xxx”).update({“name”:”要修改成的名字”})
3.9.4注意
所有操作都要提交
db.session.commit()
失败后回滚
db.session.rollback()
3.10定义模型类
模型基类
-------------------------------------------------
class BaseModel(object):
"""模型基类,为每个模型添加创建时间与更新时间"""
create_time = db.Column(db.DateTime, default=datetime.now) # 记录的创建时间
update_time = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now) # 记录的更新时间
-------------------------------------------------
多对多关系表
-------------------------------------------------
# 用户收藏表,建立用户与其收藏新闻多对多的关系
tb_user_collection = db.Table(
"info_user_collection", # 表名
db.Column("user_id", db.Integer, db.ForeignKey("info_user.id"), primary_key=True), # 新闻编号
db.Column("news_id", db.Integer, db.ForeignKey("info_news.id"), primary_key=True), # 分类编号
db.Column("create_time", db.DateTime, default=datetime.now) # 收藏创建时间
)
# 用户粉丝表,建立用户与其粉丝多对多的关系
tb_user_follows = db.Table(
"info_user_fans", # 表名
db.Column('follower_id', db.Integer, db.ForeignKey('info_user.id'), primary_key=True), # 粉丝id
db.Column('followed_id', db.Integer, db.ForeignKey('info_user.id'), primary_key=True) # 被关注人的id
)
-------------------------------------------------
用户模型类
-------------------------------------------------
class User(BaseModel, db.Model):
"""用户模型类"""
__tablename__ = "info_user"
id = db.Column(db.Integer, primary_key=True) # 用户编号
nick_name = db.Column(db.String(32), unique=True, nullable=False) # 用户昵称
password_hash = db.Column(db.String(128), nullable=False) # 加密的密码
mobile = db.Column(db.String(11), unique=True, nullable=False) # 手机号
avatar_url = db.Column(db.String(256)) # 用户头像路径
last_login = db.Column(db.DateTime, default=datetime.now) # 最后一次登录时间
is_admin = db.Column(db.Boolean, default=False) # 管理员
signature = db.Column(db.String(512)) # 用户签名
gender = db.Column( # 用户性别
db.Enum(
"MAN", # 男
"WOMAN" # 女
),
default="MAN")
# 当前用户收藏的所有新闻,secondary指定多对多关系中关系表的名字
# User.collection_news可以查到所有用户收藏新闻
collection_news = db.relationship("News", secondary=tb_user_collection, lazy="dynamic") # 用户收藏的新闻
# 用户所有的粉丝,添加了反向引用followed,
# User.followers可以查到用户所有粉丝;User.followed可以找到都关注了哪些人
followers = db.relationship('User',
secondary=tb_user_follows,
primaryjoin=id == tb_user_follows.c.followed_id,
secondaryjoin=id == tb_user_follows.c.follower_id,
backref=db.backref('followed', lazy='dynamic'),
lazy='dynamic')
# 当前用户所发布的新闻
# User.news_list可以查询到用户发布的新闻类表;New.user可以找到新闻的作者
news_list = db.relationship('News', backref='user', lazy='dynamic')
# password 加密处理
@property
def password(self):
raise AttributeError("当前属性不允许读取")
# 用对象.方法名调用passwoed属性
@password.setter
def password(self, value):
# self.password_hash 对value加密
self.password_hash = generate_password_hash(value)
def check_password(self, password):
"""校验密码"""
return check_password_hash(self.password_hash, password)
def to_dict(self):
resp_dict = {
"id": self.id,
"nick_name": self.nick_name,
"avatar_url": constants.QINIU_DOMIN_PREFIX + self.avatar_url if self.avatar_url else "",
"mobile": self.mobile,
"gender": self.gender if self.gender else "MAN",
"signature": self.signature if self.signature else "",
"followers_count": self.followers.count(),
"news_count": self.news_list.count()
}
return resp_dict
# strftime()时间输出格式
def to_admin_dict(self):
resp_dict = {
"id": self.id,
"nick_name": self.nick_name,
"mobile": self.mobile,
"register": self.create_time.strftime("%Y-%m-%d %H:%M:%S"),
"last_login": self.last_login.strftime("%Y-%m-%d %H:%M:%S"),
}
return resp_dict
-------------------------------------------------
新闻模型
-------------------------------------------------
class News(BaseModel, db.Model):
"""新闻模型"""
__tablename__ = "info_news"
id = db.Column(db.Integer, primary_key=True) # 新闻编号
title = db.Column(db.String(256), nullable=False) # 新闻标题
source = db.Column(db.String(64), nullable=False) # 新闻来源
digest = db.Column(db.String(512), nullable=False) # 新闻摘要
content = db.Column(db.Text, nullable=False) # 新闻内容
clicks = db.Column(db.Integer, default=0) # 浏览量
index_image_url = db.Column(db.String(256)) # 新闻列表图片路径
category_id = db.Column(db.Integer, db.ForeignKey("info_category.id")) # 新闻分类id外键
user_id = db.Column(db.Integer, db.ForeignKey("info_user.id")) # 当前新闻的作者id外键
status = db.Column(db.Integer, default=0) # 新闻状态 0审核通过,1审核中,-1审核不通过
reason = db.Column(db.String(256)) # 未通过原因,status = -1 的时候使用
# 当前新闻的所有评论,新闻是一,评论是多,News.comments可以查到新闻所有评论
comments = db.relationship("Comment", lazy="dynamic")
def to_review_dict(self):
resp_dict = {
"id": self.id,
"title": self.title,
"create_time": self.create_time.strftime("%Y-%m-%d %H:%M:%S"),
"status": self.status,
"reason": self.reason if self.reason else ""
}
return resp_dict
def to_basic_dict(self):
resp_dict = {
"id": self.id,
"title": self.title,
"source": self.source,
"digest": self.digest,
"create_time": self.create_time.strftime("%Y-%m-%d %H:%M:%S"),
"index_image_url": self.index_image_url,
"clicks": self.clicks,
}
return resp_dict
def to_dict(self):
resp_dict = {
"id": self.id,
"title": self.title,
"source": self.source,
"digest": self.digest,
"create_time": self.create_time.strftime("%Y-%m-%d %H:%M:%S"),
"content": self.content,
"comments_count": self.comments.count(),
"clicks": self.clicks,
"category": self.category.to_dict(), # 新闻属于的分类对象.方法
"index_image_url": self.index_image_url,
"author": self.user.to_dict() if self.user else None # 新闻的作者对象.方法
}
return resp_dict
-------------------------------------------------
评论点赞模型
-------------------------------------------------
class CommentLike(BaseModel, db.Model):
"""评论点赞模型"""
__tablename__ = "info_comment_like"
comment_id = db.Column("comment_id", db.Integer, db.ForeignKey("info_comment.id"), primary_key=True) # 评论编号
user_id = db.Column("user_id", db.Integer, db.ForeignKey("info_user.id"), primary_key=True) # 用户编号
-------------------------------------------------
新闻分类模型
-------------------------------------------------
class Category(BaseModel, db.Model):
"""新闻分类"""
__tablename__ = "info_category"
id = db.Column(db.Integer, primary_key=True) # 分类编号
name = db.Column(db.String(64), nullable=False) # 分类名
# Category.news_list分类的新闻列表,News.category可以查询到新闻的分类
news_list = db.relationship('News', backref='category', lazy='dynamic')
def to_dict(self):
resp_dict = {
"id": self.id,
"name": self.name
}
return resp_dict
3.11查询操作
Model声明基类,有个query属性,通过query对对象数据库进行查询。
3.11.1过滤器
逻辑查询
!逻辑非
返回名字不等于wang的所有数据
User.query.filter(User.name!='wang').all()
and_逻辑与
需要导入and_,可以省略
from sqlalchemy import and_
User.query.filter(and_(User.name!='wang',User.email.endswith('163.com'))).all()
User.query.filter(User.name!='wang',User.email.endswith('163.com')).all()
or_逻辑或
from sqlalchemy import or_
User.query.filter(or_(User.name!='wang',User.email.endswith('163.com'))).all()
not_ 取反
from sqlalchemy import not_
User.query.filter(not_(User.name=='chen')).all()
查询在 in_
from sqlalchemy import or_
查询id为 [1, 3, 5, 7, 9] 的用户列表
User.query.filter(User.id.in_([1, 3, 5, 7, 9])).all()
精确查询
filter_by精确查询
参数是值
User.query.filter_by(name='wang').all()
模糊查询
filter模糊查询
返回名字结尾字符为g的所有数据
User.query.filter(User.name.endswith('g')).all()
endswith()
contains()
startswith()
参数字符串
查询以.....结尾,查询包含什么,查询以.....开始
其他过滤器
limit 使用指定的值限定原查询返回的结果
offset() 偏移原查询返回的结果,返回一个新查询
order_by() 根据指定条件对原查询结果进行排序,返回一个新查询
group_by() 根据指定条件对原查询结果进行分组,返回一个新查询
3.11.2执行器
all()
无参数,返回查询到的所有对象
User.query.all()
get():参数为主键
参数是主键id,不存在没有返回内容
User.query.get()
count()
没有参数,查询数量
User.query.count()
first()
无参数,返回查询到的第一个对象
User.query.first()
first_or_404()
返回查询的第一个结果,如果未查到,返回404
get_or_404()
返回指定主键对应的行,如不存在,返回404
3.11.3分页查询
每页3个,查询第2页的数据
paginate = User.query.paginate(2, 3)
# 第1个参数代表查询第几页,第2个参数代表每页几个
paginate.items 当前页数据
paginate.pages 总页数
paginate.page 当前页
3.11.4查询id为4的用户[3种方式]
User.query.filter_by(id=4).first()
User.query.filter(User.id == 4).first()
User.query.get(4)
3.11.5排序查询
反序 User.query.order_by(User.email.desc()).all()
正序(默认)User.query.order_by(User.email).all()
3.12数据库迁移
3.12.1概念
作用:追踪数据库模式的变化,然后把变动应用到数据库中
安装Flask-Migrate扩展:pip install flask-migrate
扩展提供了一个MigrateCommand类,对象可以注册到flask-script的manager对象上
# 创建命令行对象,用命令行控制qpp
manager = Manager(app)
# 将app与db关联
Migrate(app, db)
# 将命令迁移到manager上
manager.add_command('db', MigrateCommand)
3.12.2创建迁移仓库
初始化命令 python database.py db init
创建migrations文件夹,所有迁移文件都放在里面
3.12.3创建迁移脚本
python 文件 db migrate -m '注释'
3.12.4更新数据库
python 文件 db upgrade
3.12.4查看历史版本
python 文件 db history
3.12.4进入历史版本
python 文件 db downgrade(upgrade) 版本号