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)

python就业班第33天----数据库、数据库迁移、蓝图的使用

懒惰属性: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)

python就业班第33天----数据库、数据库迁移、蓝图的使用
python就业班第33天----数据库、数据库迁移、蓝图的使用
python就业班第33天----数据库、数据库迁移、蓝图的使用

使用另外种方法
方法2:使用蓝图开发

操作步骤:先建个py文件,里面放视图函数(123步);然后在函数入口的文件里面注册蓝图(第四步)
1.导入蓝图类
from flask import Blueprint
2.创建蓝图对象
blue = Blueprint("蓝图名称",__name__) 两个都是必填
3.使用蓝图装饰视图函数
@blue.route("/路径")
4.将蓝图对象注册到app
app.register_blueprint(blue)

好处:只需要更改视图函数名称,其它地方都没有动(弱耦合)

python就业班第33天----数据库、数据库迁移、蓝图的使用
python就业班第33天----数据库、数据库迁移、蓝图的使用
python就业班第33天----数据库、数据库迁移、蓝图的使用

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,找不到资源

python就业班第33天----数据库、数据库迁移、蓝图的使用
_init_.py里面写

from flask import Blueprint
use_blue = Blueprint("user",__name__,url_prefix="/user",
					static_folder="static",template_folder="templates")

from user import views

python就业班第33天----数据库、数据库迁移、蓝图的使用

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")

python就业班第33天----数据库、数据库迁移、蓝图的使用
message.html里面写:

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Title</title>
</head>

<body>
<h1>Message</h1>
</body>
<html>

python就业班第33天----数据库、数据库迁移、蓝图的使用
在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)

python就业班第33天----数据库、数据库迁移、蓝图的使用
python就业班第33天----数据库、数据库迁移、蓝图的使用

python就业班第33天----数据库、数据库迁移、蓝图的使用

python就业班第33天----数据库、数据库迁移、蓝图的使用

python就业班第33天----数据库、数据库迁移、蓝图的使用