我有三个表User,Device和Role。我已经创建了这样的多对多的黑白用户和设备关系;
#Many-to-Many relation between User and Devices
userDevices = db.Table("user_devices",
db.Column("id", db.Integer, primary_key=True),
db.Column("user_id", db.Integer, db.ForeignKey("user.id")),
db.Column("device_id", db.Integer, db.ForeignKey("device.id"))))
class User(db.Model):
__tablename__ = 'user'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(60), index=True, unique=True)
devices = db.relationship("Device", secondary=userDevices, backref=db.backref('users'), lazy="dynamic")
class Device(db.Model):
__tablename__ = 'device'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(60), unique=True)
这很好运作。我可以将设备d1
分配给用户u1
> d1.users.append(u1)
,并将用户分配给设备> u1.devices.append(d1)
和db.session.commit()
。
我想要的更多是扩展表user_devices
并在其中添加一列作为role_id
,这将成为Role表的ForeignKey。因此,此表user_devices
将清楚地描述特定Role
上特定User
的{{1}}。在表Device
中添加列role_id
后,我将user_devices
表描述为;
Role
以这种方式,如何为设备class Role(db.Model):
__tablename__ = 'role'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(60), unique=True)
device = db.relationship("Device", secondary=userDevices, backref=db.backref('roles'), lazy="dynamic")
上的用户r1
分配角色u1
?
这是我尝试过的:
d1
这将在表# First get the device, user and role
deviceRow = db.session.query(Device).filter(Device.name=="d1").first()
userRow = db.session.query(User).filter(User.username=="u1").first()
roleRow = db.session.query(Role).filter(Role.name == "r1").first()
# Then add the user on that device
deviceRow.users.append(userRow)
deviceRow.roles.append(roleRow)
有没有办法像这样将两个属性添加到表中?;
user_devices
以便仅在commit()之后创建一行?
答案 0 :(得分:2)
3个实体的关联不再是简单的多对多关系。您需要的是association object模式。为了使关联的处理更加容易,将其映射为模型类而不是简单的Table
:
class UserDevice(db.Model):
__tablename__ = "user_devices"
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
device_id = db.Column(db.Integer, db.ForeignKey("device.id"), nullable=False)
role_id = db.Column(db.Integer, db.ForeignKey("role.id"), nullable=False)
__table_args__ = (db.UniqueConstraint(user_id, device_id, role_id),)
user = db.relationship("User", back_populates="user_devices")
device = db.relationship("Device")
role = db.relationship("Role", back_populates="user_devices")
class User(db.Model):
__tablename__ = "user"
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(60), index=True, unique=True)
user_devices = db.relationship("UserDevice", back_populates="user")
class Role(db.Model):
__tablename__ = "role"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(60), unique=True)
user_devices = db.relationship("UserDevice", back_populates="role")
要将用户与设备和角色相关联,请创建一个新的UserDevice
对象:
device = db.session.query(Device).filter(Device.name == "d1").first()
user = db.session.query(User).filter(User.username == "u1").first()
role = db.session.query(Role).filter(Role.name == "r1").first()
assoc = UserDevice(user=user, device=device, role=role)
db.session.add(assoc)
db.session.commit()
请注意,ORM关系不再是Device
等对象的简单集合,而是UserDevice
对象。这是一件好事:例如,当您遍历user.user_devices
时,您会获得有关设备以及用户在设备上所扮演的角色的信息。如果确实希望在不需要角色信息的情况下也提供更简单的集合,则可以使用associationproxy
。
答案 1 :(得分:1)
有一种方法可以使三对多关系不是两个多对多关系的组合。您需要an association object,因为仅使用表的语法不允许3对多对多(因为secondary
明确指2对多对多)。>
以下是一般情况下的最小示例:
from sqlalchemy import ForeignKey, Column, Integer, String
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base, declared_attr
Base = declarative_base()
# Helper classes to simplify the other classes:
# 1. Adds ch column
# 2. Defines how to print it
class Ch:
ch = Column(String, nullable=False)
def __str__(self):
return self.ch
# 3. Automatically determines table name (for foreign key)
class AutoNamed:
@declared_attr
def __tablename__(cls):
return cls.__name__
class ABC(AutoNamed, Base):
a_id = Column(Integer, ForeignKey('A.a_id'), primary_key=True)
b_id = Column(Integer, ForeignKey('B.b_id'), primary_key=True)
c_id = Column(Integer, ForeignKey('C.c_id'), primary_key=True)
a = relationship('A', back_populates='abcs')
b = relationship('B', back_populates='abcs')
c = relationship('C', back_populates='abcs')
def __repr__(self):
return f'{self.a} {self.b} {self.c}'
class A(Ch, AutoNamed, Base):
a_id = Column(Integer, primary_key=True)
abcs = relationship('ABC', back_populates='a')
class B(Ch, AutoNamed, Base):
b_id = Column(Integer, primary_key=True)
abcs = relationship('ABC', back_populates='b')
class C(Ch, AutoNamed, Base):
c_id = Column(Integer, primary_key=True)
abcs = relationship('ABC', back_populates='c')
好的,现在来一点解释:
ABC
是一个关联表,需要3对多对多表中每个表的单个实例。A
,B
,C
中的每一个都将引用所有ABC
对象的引用,这些对象涉及在实例化ABC
实例时自动添加的涉及它们的对象。 / li>
relationship.secondary
时,对象上的属性是其他类型的列表(在这种情况下,parent.children
是Children
对象的列表) 。但是,在“关联对象”的文档中,将其翻译为关联对象时,尽管它们仍在parent
对象children
上命名属性,但实际上它是Association
对象的列表。在这里,我通过调用属性abcs
来明确这一点。您可以像平常一样实例化这些:
anA = A(ch='x')
anB = B(ch='y')
anC = C(ch='z')
anABC = ABC(a=anA, b=anB, c=anC)
sess.add(anABC)
作为健全性检查,以下是从中为SQLite生成的SQL。正是我们所期望的。
CREATE TABLE "A" (
ch VARCHAR NOT NULL,
a_id INTEGER NOT NULL,
PRIMARY KEY (a_id)
);
CREATE TABLE "B" (
ch VARCHAR NOT NULL,
b_id INTEGER NOT NULL,
PRIMARY KEY (b_id)
);
CREATE TABLE "C" (
ch VARCHAR NOT NULL,
c_id INTEGER NOT NULL,
PRIMARY KEY (c_id)
);
CREATE TABLE "ABC" (
a_id INTEGER NOT NULL,
b_id INTEGER NOT NULL,
c_id INTEGER NOT NULL,
PRIMARY KEY (a_id, b_id, c_id),
FOREIGN KEY(a_id) REFERENCES "A" (a_id),
FOREIGN KEY(b_id) REFERENCES "B" (b_id),
FOREIGN KEY(c_id) REFERENCES "C" (c_id)
);