1.ORM关系之多对多
1.1 什么时候使用多对多关系
例如,我们我们的项目中,一个用户可以拥有多个角色,同样的,一个角色可以给多个用户。通俗来说,一个用户可以购买多个商品,多个商品可以被一个用户购买
1.2操作流程
- 多对多的关系需要通过一张中间表来绑定他们之间的关系
- 先把两个需要做多对多的模型定义出来
- 使用Table定义一个中间表,中间表一般包含两个模型的外键字段就可以啦,并且让他们两个来作为一个“复合主键”
- 在两个需要做多对多的模型中随便选择一个模型,定义一个relationship属性,来绑定三者之间的关系,在使用relationship的时候,需要传入一个secondary=中间表对象名
简易版代码可以参考:本人的另一篇专门讲解的博客
2.操作代码
2.1为什么要使用多对多关系
在本数据库结构中,menu是角色的权利,不同的权利赋给不同的用户。所以这里相当于要把menu(记录管理权限)和role(角色)进行多对多的映射
2.2 关联关系的创建
# /flask_shop/models.py
# 此文件用于建立数据库表的模型
# 需要针对数据库的模型
# from enum import unique
from flask_shop import db
# 对数据加密 检查密码
from werkzeug.security import generate_password_hash,check_password_hash
from datetime import datetime
# 创建公用数据库模型
class BaseModel:
# 记录创建的时间
create_time = db.Column(db.DateTime,default=datetime.now)
# 记录修改密码的时间
update_time = db.Column(db.DateTime, default=datetime.now, onupdate = datetime.now)
# 创建第三张表,用与多对多的映射关系
trm = db.Table('t_role_menu',
db.Column('rid', db.Integer, db.ForeignKey('t_role.id')),
db.Column('mid', db.Integer, db.ForeignKey('t_menu.id'))
)
# 建立菜单的数据库
class Menu(db.Model):
__tablename__ = 't_menu'
id = db.Column(db.Integer, primary_key = True)
name = db.Column(db.String(32),unique = True, nullable = False)
level = db.Column(db.Integer)
path = db.Column(db.String(32))
# pid = db.Column(db.Integer)
pid = db.Column(db.Integer, db.ForeignKey('t_menu.id'))
# 建立一个自连接的数据,用于做子表
children = db.relationship('Menu')
roles = db.relationship('Role', secondary = trm)
# 由于前端的接收需要给出json数据,不可以直接去获取,所以得单独返回
def to_dict(self):
return {
'id':self.id,
'name': self.name,
'level':self.level,
'path':self.path,
'pid':self.pid,
# 子表中的内容需要单独返回,可以做一个递归或者循环
# 'children': self.get_child_list()
}
def get_child_list(self):
obj_child = self.children
data = []
for o in obj_child:
data.append(o.to_dict)
return data
# 角色管理
class Role(db.Model):
__tablename__ = 't_role'
id = db.Column(db.Integer, primary_key = True)
name = db.Column(db.String(32), unique=True, nullable = True)
desc = db.Column(db.String(32))
# backref反向关联,方便子表查询主表数据
users = db.relationship('User', backref='role')
menu = db.relationship('Menu', secondary = trm)
def to_dict(self):
return {
'id':self.id,
'name': self.name,
'desc':self.desc
}
3.处理报错
Additional arguments should be named _, got ‘secondary’
错误代码
roles = db.Column('Role', secondary = trm)
正确代码
roles = db.relationship('Role', secondary = trm)
4.获取角色管理员权限
4.1方法
角色管理员获取存在子集关系的数据时方法:
略有删减,可以看完整版代码
# 角色管理
class Role(db.Model):
__tablename__ = 't_role'
id = db.Column(db.Integer, primary_key = True)
name = db.Column(db.String(32), unique=True, nullable = True)
desc = db.Column(db.String(32))
# backref反向关联,方便子表查询主表数据
users = db.relationship('User', backref='role')
menus = db.relationship('Menu', secondary = trm)
def to_dict(self):
return {
'id':self.id,
'name': self.name,
'desc':self.desc,
'menu': self.get_menu_dict()
}
def get_menu_dict(self):
menu_list = []
for m in self.menus:
# 一共就两级,二级的应该放在一级的当作children输出
if m.level == 1:
first_dict = m.to_dict()
first_dict['children'] = []
for s in self.menus:
# 只有二级才需要加入到children,并判断是否为关联关系
if s.level == 2 and s.pid == m.id:
first_dict['children'].append(s.to_dict())
menu_list.append(first_dict)
return menu_list
4.2Postman测试
5.完整代码
# /flask_shop/models.py
# 此文件用于建立数据库表的模型
# 需要针对数据库的模型
# from enum import unique
from flask_shop import db
# 对数据加密 检查密码
from werkzeug.security import generate_password_hash,check_password_hash
from datetime import datetime
# 创建公用数据库模型
class BaseModel:
# 记录创建的时间
create_time = db.Column(db.DateTime,default=datetime.now)
# 记录修改密码的时间
update_time = db.Column(db.DateTime, default=datetime.now, onupdate = datetime.now)
# 需要继承数据库中的模型
class User(db.Model,BaseModel):
__tablename__ = 't_user'
id = db.Column(db.Integer, primary_key = True)
name = db.Column(db.String(32), unique=True, nullable=False)
pwd = db.Column(db.String(128))
nick_name = db.Column(db.String(32))
phone = db.Column(db.String(11))
email = db.Column(db.String(32))
# 建立用户与角色的关系(多对一)
rid = db.Column(db.Integer, db.ForeignKey('t_role.id'))
# 定义两个装饰器,用于访问密码时使用
@property
def password(self):
return self.pwd
# 用户存储时使用,存的是加密过后的数据,若要访问,得通过property装饰器
# 用户先输入密码,传入到t_pwd,通过函数的加密返回给self.pwd;用户需要访问时,找到property
# self.pwd,是数据库中的密码(加密过后);t_pwd是用户输入的密码(真实密码)
@password.setter
def password(self,t_pwd):
self.pwd = generate_password_hash(t_pwd)
# 用户二次访问之后,需要把加密过后的密码转成真实密码,与用户输入的密码进行比对
def check_password(self,t_pwd):
return check_password_hash(self.pwd,t_pwd)
def to_dict(self):
return {
'id':self.id,
'name': self.name,
'nick_name':self.nick_name,
'phone':self.phone,
'email':self.email,
# 此时的role是根据下面backref的role定义的,获取到他的名称
# 防止角色为空
'role_name': self.role.name if self.role else ''
}
# 创建第三张表,用与多对多的映射关系
trm = db.Table('t_role_menu',
db.Column('rid', db.Integer, db.ForeignKey('t_role.id')),
db.Column('mid', db.Integer, db.ForeignKey('t_menu.id'))
)
# 建立菜单的数据库
class Menu(db.Model):
__tablename__ = 't_menu'
id = db.Column(db.Integer, primary_key = True)
name = db.Column(db.String(32),unique = True, nullable = False)
level = db.Column(db.Integer)
path = db.Column(db.String(32))
# pid = db.Column(db.Integer)
pid = db.Column(db.Integer, db.ForeignKey('t_menu.id'))
# 建立一个自连接的数据,用于做子表
children = db.relationship('Menu')
roles = db.relationship('Role', secondary = trm)
# 由于前端的接收需要给出json数据,不可以直接去获取,所以得单独返回
def to_dict(self):
return {
'id':self.id,
'name': self.name,
'level':self.level,
'path':self.path,
'pid':self.pid,
# 子表中的内容需要单独返回,可以做一个递归或者循环
# 'children': self.get_child_list()
}
def get_child_list(self):
obj_child = self.children
data = []
for o in obj_child:
data.append(o.to_dict())
return data
# 角色管理
class Role(db.Model):
__tablename__ = 't_role'
id = db.Column(db.Integer, primary_key = True)
name = db.Column(db.String(32), unique=True, nullable = True)
desc = db.Column(db.String(32))
# backref反向关联,方便子表查询主表数据
users = db.relationship('User', backref='role')
menus = db.relationship('Menu', secondary = trm)
def to_dict(self):
return {
'id':self.id,
'name': self.name,
'desc':self.desc,
'menu': self.get_menu_dict()
}
def get_menu_dict(self):
menu_list = []
for m in self.menus:
# 一共就两级,二级的应该放在一级的当作children输出
if m.level == 1:
first_dict = m.to_dict()
first_dict['children'] = []
for s in self.menus:
# 只有二级才需要加入到children,并判断是否为关联关系
if s.level == 2 and s.pid == m.id:
first_dict['children'].append(s.to_dict())
menu_list.append(first_dict)
return menu_list
6. 完整测试结果
{
“status”: 200,
“data”: [
{
“id”: 1,
“name”: “荣耀管理员”,
“desc”: “拥有神操作的管理员”,
“menu”: [
{
“id”: 5,
“name”: “订单管理”,
“level”: 1,
“path”: null,
“pid”: 1,
“children”: []
},
{
“id”: 2,
“name”: “用户管理”,
“level”: 1,
“path”: null,
“pid”: 1,
“children”: [
{
“id”: 21,
“name”: “用户列表”,
“level”: 2,
“path”: “/user_list”,
“pid”: 2
}
]
},
{
“id”: 3,
“name”: “权限管理”,
“level”: 1,
“path”: null,
“pid”: 1,
“children”: [
{
“id”: 31,
“name”: “角色列表”,
“level”: 2,
“path”: “/role_list”,
“pid”: 3
},
{
“id”: 32,
“name”: “权限列表”,
“level”: 2,
“path”: “/menu_list”,
“pid”: 3
}
]
},
{
“id”: 4,
“name”: “商品管理”,
“level”: 1,
“path”: null,
“pid”: 1,
“children”: []
},
{
“id”: 6,
“name”: “数据管理”,
“level”: 1,
“path”: null,
“pid”: 1,
“children”: []
}
]
},
{
“id”: 2,
“name”: “钻石管理员”,
“desc”: “拥有不俗的手速的管理员”,
“menu”: [
{
“id”: 2,
“name”: “用户管理”,
“level”: 1,
“path”: null,
“pid”: 1,
“children”: [
{
“id”: 21,
“name”: “用户列表”,
“level”: 2,
“path”: “/user_list”,
“pid”: 2
}
]
},
{
“id”: 3,
“name”: “权限管理”,
“level”: 1,
“path”: null,
“pid”: 1,
“children”: [
{
“id”: 31,
“name”: “角色列表”,
“level”: 2,
“path”: “/role_list”,
“pid”: 3
}
]
},
{
“id”: 4,
“name”: “商品管理”,
“level”: 1,
“path”: null,
“pid”: 1,
“children”: []
}
]
},
{
“id”: 3,
“name”: “铂金管理员”,
“desc”: “正常手速”,
“menu”: []
},
{
“id”: 4,
“name”: “黄金管理员”,
“desc”: “一般手速”,
“menu”: []
}
],
“msg”: “获取角色列表成功”
}