python就业班第33天----数据库、数据库迁移、蓝图的使用
设置外键:可以使用变量.属性
,也可以使用数据库的表名.字段
关系属性:
class Role里:
users = db.relationship('User')
终端:
ro1 = Role.query.get(1)
ro1.users
以前:User.query.filter(User.role_id == role.id).all()
二者等效
不能用all(),all()返回个数组,first()、get()返回的都是对象
反向属性backref:backref的值,是前面那个参数(类的属性)
class Role里:users = db.relationship("User",backref="role")
终端里:
us = User.query.get(1)
us.role()
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
#设置数据库配置信息
app.config["SQLALCHEMY_DATABASE_URI"] = "mysql+pymysql://root:[email protected]:3306/basic16"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
#创建SQLAlchemy对象,关联app
db = SQLAlchemy(app)
# 编写模型类
class Role(db.Model):
__tablename__ = "roles"
id = db.Column(db.Integer,primary_key=True)
name = db.Column(db.String(32))
# 给Role添加关系属性users,查询的方式:role.users
# 给User添加关系属性role,查询的方式:user.role
users = db.relationship("User",backref="role",lazy="dynamic")
# 为了方便查看对象输出内容,重写repr
def __repr__(self):
return "<Role:%s,%s>"%(self.id,self.name)
class User(db.Model):
__tablename__ = "users"
id = db.Column(db.Integer,primary_key=True)
name = db.Column(db.String(32))
email = db.Column(db.String(32))
password = db.Column(db.String(32))
# 关键
role_id = db.Column(db.Integer,db.ForeignKey(Role.id))
# 为了方便查看对象输出内容,重写__repr__
def __repr__(self):
return "<User:%s,%s,%s,%s>"%(self.id,self.name,self.email,self.password)
@app.route("/"):
def hello_world():
return "helloworld"
if __name__ == "__main__":
# 先删除,后创建
db.drop_all()
db.create_all()
# 创建测试数据
ro1 = Role(name="admin")
db.session.add(ro1)
db.session.commit()
# 再次插入一条数据
ro2 = Role(name="user")
db.session.add(ro2)
db.session.commit()
# 多条用户数据
us1 = User(name='wang', email='[email protected]', password='123456', role_id=ro1.id)
us2 = User(name='zhang', email='[email protected]', password='201512', role_id=ro2.id)
us3 = User(name='chen', email='[email protected]', password='987654', role_id=ro2.id)
us4 = User(name='zhou', email='[email protected]', password='456789', role_id=ro1.id)
us5 = User(name='tang', email='[email protected]', password='158104', role_id=ro2.id)
us6 = User(name='wu', email='[email protected]', password='5623514', role_id=ro2.id)
us7 = User(name='qian', email='[email protected]', password='1543567', role_id=ro1.id)
us8 = User(name='liu', email='[email protected]', password='867322', role_id=ro1.id)
us9 = User(name='li', email='[email protected]', password='4526342', role_id=ro2.id)
us10 = User(name='sun', email='[email protected]', password='235523', role_id=ro2.id)
db.session.add_all([us1, us2, us3, us4, us5, us6, us7, us8, us9, us10])
db.session.commit()
app.run(debug=True)
懒惰属性:lazy,使用关系属性之后,默认会做子查询(不管用不用的到,都会查询,占用空间),可以将lazy设置为动态查询(只有你用到的时候才会查询,能节约空间)
默认:lazy='subquery'
建议设置:lazy='dynamic'
,只有用到了才会查询users = db.relationship('User',backref='role',lazy='dynamic')
role.users.all
注意点:relationship,backref,lazy的使用必须要有外键
案例:作者和书籍
from flask import Flask,render_template,request,flash,redirect
from flask_sqlalchemy import SQLAlchemy
from flask_wtf import CSRFProtect
app = Flask(__name__)
app.config["SECRET_KEY"] = "fjdkfjkdjkf"
#保护app
CSRFProtect(app)
# 数据库配置信息
app.config["SQLALCHEMY_DATABASE_URI"] = "mysql+pymysql://root:[email protected]:3306/library16"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
# 创建SQLAlchemy,关联app
db = SQLAlchemy(app)
# 创建模型类
# 作者类(一方)
class Author(db.Model):
__tablename__ = "authors"
id = db.Column(db.Integer,primary_key=True)
name = db.Column(db.String(32))
# 关系属性
books = db.relationship("Book",backref="author")
# 书籍类(多方)
class Book(db.Model):
__tablename__ = "books"
id = db.Column(db.Integer,primary_key=True)
name = db.Column(db.String(32))
# 外键
author_id = db.Column(db.Integer,db.ForeignKey(Author.id))
# author_id = db.Column(db.Integer,db.ForeignKey("authors.id")) # 等价上面
# 展示数据
@app.route("/")
def show_index():
# 1.查询所有作者
authors = Author.query.all()
# 2.携带作者信息,到界面展示
return render_template("file01library.html",authors=authors)
# 添加数据
app.route("/add_data",methods=["POST"])
def add_data():
# 1.获取到表单提交的数据
authorName = request.form.get("author")
bookName = request.form.get("book")
# 2.校验数据
# 2.1 根据传入作者名称,在数据库中查询作者对象
author = Author.query.filter(Author.name == authorName).first()
if author:
# 2.2 根据传递的数据名,作者对象,查询该作者是否有该数据
book = Book.query.filter(Book.name == bookName,Book.author_id == author.id).first()
if book:
flash("该作者,有该书")
else:
# 创建书籍对象,并添加到数据库
book = Book(name=bookName,author_id=author.id)
db.session.add(book)
db.session.commit()
else:
# 创建作者对象,添加到数据库
author = Author(name=authorName)
db.session.add(author)
db.session.commit()
# 创建数据对象,添加到数据库
book = Book(name=bookName,author_id=author.id)
db.session.add(book)
db.session.commit()
# 3.重定向
return redirect('/')
# 删除书籍
@app.route("/delete_book/<int:book_id>")
def delete_book(book_id):
# 1.根据传入的book_id,查询书籍
book = Book.query.get(book_id)
# 2.删除
db.session.delete(book)
db.session.commit()
# 3.重定向
return redirect("/")
# 删除作者
@app.route("/delete_author/<int:author_id>")
def delete_author(author_id)
# 1.根据传入的id查询作者对象
author = Author.query.get(author_id)
# 2.遍历删除作者的书籍
for book in author.books:
db.session.delete(book)
# 3.删除作者
db.session.delete(author)
db.session.commit()
# 4.重定向
return redirect("/")
if __name__ == "__main__":
# 为了演示方便,删除,在创建
db.drop_all()
db.create_all()
au1 = Author(name='老王')
au2 = Author(name='老尹')
au3 = Author(name='老刘')
# 把数据提交给用户会话
db.session.add_all([au1, au2, au3])
# 提交会话
db.session.commit()
bk1 = Book(name='老王回忆录', author_id=au1.id)
bk2 = Book(name='我读书少,你别骗我', author_id=au1.id)
bk3 = Book(name='如何才能让自己更骚', author_id=au2.id)
bk4 = Book(name='怎样征服美丽少女', author_id=au3.id)
bk5 = Book(name='如何征服英俊少男', author_id=au3.id)
# 把数据提交给用户会话
db.session.add_all([bk1, bk2, bk3, bk4, bk5])
# 提交会话
db.session.commit()
app.run(debug=True)
在file01library.html里面写:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/add_data" method="post">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<label>作者:</label><input type="text" name="author"><br>
<label>书籍:</label><input type="text" name="book"><br>
<input type="submit" value="添加"><br>
{% for message in get_flashed_messages() %}
<span>{{ message }}</span>
{% endfor %}
</form>
<ul>
{% for author in authors %}
<li>作者:{{ author.name }}<a href="/delete_author/{{ author.id }}">删除</a></li>
<ul>
{% for book in author.books %}
<li>书籍:{{ book.name }}<a href="{{ url_for("delete_book",book_id=book.id) }}">删除</a></li>
{% endfor %}
</ul>
{% endfor %}
</ul>
</body>
</html>
一对多:
一个授课老师与多个学生
一个学生与所学的课程
用户和发的帖子
多对多:学生选课
拆成两个一对多来表示
多方:中间表,外键写在该表中
一方:比如学生表,关系苏醒写在该表中
先设置中间表,中间(关系)表达式:
# 学生,课程,中间表
student_course_tb = db.Table(
"student_course_tb",
db.Column("student_id",db.Integer,db.ForeignKey("students.id")),
db.Column("course_id",db.Integer,db.ForeignKey("courses.id"))
)
然后正常建表,在其中的一个表格里面写个relationship(),里面的secondary写中间表表名
格式:在class Student(db.Model)里面写:
courses = db.relationship("Course",backref="students",secondary="student_course_tb")
例:学生,课程,多对多演部分代码
student_course_tb = db.Table(
"student_course_tb",
db.Column("student_id",db.Integer,db.ForeignKey("students.id")),
db.Column("course_id",db.Integer,db.ForeignKey("courses.id"))
)
class Student(db.Model):
__tablename__ = "students"
id = db.Column(db.Integer,primary_key=True)
name = db.Column(db.String(32))
courses = db.relationship("Course",backref="students",secondary="student_course_tb")
def __repr__(self):
return "<Student:%s,%s>"%(self.id,self.name)
class Course(db.Model):
__tablename__ = "courses"
id = db.Column(db.Integer,primary_key=True)
name = db.Column(db.String(32))
def __repr__(self):
return "<Course:%s,%s>"%(self.id,self.name)
数据测试:
stu1 = Student(name='张三')
stu2 = Student(name='李四')
stu3 = Student(name='王五')
cou1 = Course(name='语文')
cou2 = Course(name='数学')
cou3 = Course(name='英语')
stu1.couses = [cou1, cou2]
stu1.couses = [cou1, cou2, cou3]
stu1.couses = [cou1]
db.session.add_all([stu1, stu2, stu3])
db.session.add_all([cou1, cou2, cou3])
db.session.commit()
使用对象.query.all()
获得个列表
设置列表:stu1.courses = [cou2,cou3]
自关联一对多:(一对多在一张表里面)
自关联:在一张表里面
朋友圈评论:主评论和子评论都放一个表里
自关联多对多:用户互相关注
数据库的迁移
目的:为了备份表结构
- 1.安装扩展
pip install flask_migrate
pip install flask_script
- 2.导入扩展中的三个类
from flask_migrate import Migrate,MigrateCommand
from flask_script import Manager
- …
通过Manager管理app
使用Migrate关联app,db
给Manager添加操作命令,使用MigrateCommand
from flask import Flask
from flask_migrate import Migrate,MigrateCommand
from flask_script import Manager
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
# 设置数据库配置信息
app.config["SQLALCHEMY_DATABASE_URI"] = "mysql://root:[email protected]:3306/basic18"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
# 创建SQLAlchemy对象,关联app
db = SQLAlchemy(app)
# 通过Manager管理app
manager = Manager(app)
# - 使用Migrate,关联app,db
Migrate(app,db)
# 给manager添加操作命令,使用MigrateCommand
manager.add_command("db",migrateCommand)
# 模型类
class Student(db.Model):
__tablename__ = "students"
id = db.Column(db.Integer,primary_key=True)
name = db.Column(db.String(32))
age = db.Column(db.Integer)
@app.route("/")
def hello_world():
return "helloworld"
if __name__ == "__main__":
manager.run()
使用终端进行迁移操作:
生成迁移文件:python xxx.py db init
将模型类生成迁移脚本:python xxx.py db migrate-m '注释'
将迁移脚本更新到数据库:python xxx.py db upgrade
然后在编写刚刚的python文件,控制表结构,比如添加表格、字段之类的
改完之后,在生成迁移脚本、进行迁移即可。
要想使用之前的表结构,进行降级
降级:python xxx.py db downgrade
查看所有的版本号:python xxx.py db history
指定版本:python xxx.py db current
降级到指定版本:python xxx.py db downgrade 版本号
数据库迁移不是为了备份数据,而是为了备份表结构,如果要备份数据,需要使用工具,比如Navicat,mysqlworkbench等等工具(很简单,把数据生成sql数据)
如果进行降级的操作,会丢失数据,请谨慎操作。升级不会。
蓝图
蓝图:Blueprint,用来模块开发
a导b,b导a(跟写的顺序有关,没创建的时候也没事,只要结构合理,允许循环导包)问题:循环导包报错!!!导入函数被装饰器装饰的问题
先导入,后装饰
方法1:
将detail函数写在其它文件里面,导入,想要装饰detail,还可以用:app.route("/detail")(detail)
最好不用,强耦合一个地方发生变化,其它地方也要跟着变
在demo05modules.py里面写:
def detail2():
return "detail"
在demo06product.py里面写:
from flask import Flask
from demo06product import detail2
app = Flask(__name__)
@app.route("/")
def index():
return "index"
app.route("/detail")(detail2)
if __name__ == "__main__":
app.run(debug=True)
使用另外种方法
方法2:使用蓝图开发
操作步骤:先建个py文件,里面放视图函数(123步);然后在函数入口的文件里面注册蓝图(第四步)
1.导入蓝图类from flask import Blueprint
2.创建蓝图对象blue = Blueprint("蓝图名称",__name__)
两个都是必填
3.使用蓝图装饰视图函数@blue.route("/路径")
4.将蓝图对象注册到appapp.register_blueprint(blue)
好处:只需要更改视图函数名称,其它地方都没有动(弱耦合)
blue = Blueprint("蓝图名称",__name__)
参数1:蓝图名字,方便输出视图函数的时候,容易查看视图函数属于哪个蓝图
参数2:name,表示当前蓝图属于哪个模块
其它参数:url_prefix='/前缀名'
使用前缀,浏览器访问的时候需要加前缀名
注意:/不能丢
template_folder = 'templates'
模板文件夹
要是导的时候提示不对,用jinja2标记包下面的templates
包和文件夹的区别:包里面有__init__文件,只要包被执行,该文件就会被执行
使用蓝图+包的步骤:
- 1.建个包,在包的__init__里面导入蓝图并创建蓝图对象,设置好template_folder的属性值;
- 2.在包里面建模块py文件(也可以同时建template和static)
- 3.在包的__init__里面,追加导入模块py文件(写在__init__的最后面)
使用地址栏访问包里的静态资源static,创建蓝图对象时必须设置url_prefix属性,不然是包里的static还是项目根目录下的static重复,默认使用项目下的static,找不到资源
_init_.py里面写
from flask import Blueprint
use_blue = Blueprint("user",__name__,url_prefix="/user",
static_folder="static",template_folder="templates")
from user import views
views.py里面写
from user import user_blue
from flask import render_template
@user_blue.route("/")
def index():
return "index"
@user_blue.route("message")
def message():
return render_template("message.html")
message.html里面写:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>Message</h1>
</body>
<html>
在demo09package_blueprint.py里面写
from flask import Flask
from user import user_blue
app = Flask(__name__)
app.register_blueprint(user_blue)
if __name__ == "__main__":
print(app.url_app)
app.run(debug=True)