如何在SQLAlchemy中使用基于角色的区分符正确建模连接表继承
我可以从另一个表中指定一个区分列吗?我该怎么用声明式方法做到这一点?
这样做的原因是我有一个连接表继承的结构,
class User(Base):
id = Column(...)
class Customer(User):
customer_id = Column('id', ...)
class Mechanic(User):
mechanic_id = Column('id', ...)
class Role(Base):
id = Column(..)
但我想根据用户拥有的角色来区分。一个用户可以有买家角色、卖家角色,或者两者都有。
这样建模是否正确?
需要注意的是,买家和卖家都有特定的数据,这就是我使用连接表继承的原因。
2 个回答
1
在最一般的情况下,我会说“没有”。
从最基本的角度来看,买家和卖家这两个词并不是在描述角色,而是在描述两方之间的关系。这个关系可以是两个个人之间、两个公司之间,或者一个个人和一个公司之间。这个关系的建立是因为一方从另一方那里购买了至少一件东西。
你可能需要买家和卖家的表格(并且要有外键连接到用户表)来记录仅与买家或卖家相关的细节。但是如果没有这样的细节,像{buyer_id, seller_id, product, date_time}这样的销售记录表就是记录谁从谁那里买了什么的“正常”方式。
3
这里有一种笨拙且效率低下的方法来实现特定的结果,如果目标用户有多个角色,这种方法也会失败:
from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
role_to_user = Table('role_to_user', Base.metadata,
Column('role_id', Integer, ForeignKey('role.id'), primary_key=True),
Column('user_id', Integer, ForeignKey('user.id'), primary_key=True),
)
class Role(Base):
__tablename__ = 'role'
id = Column(Integer, primary_key=True)
name = Column(String, nullable=False)
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
discrim = select([Role.name])
discrim_col = discrim.label("foo")
type = column_property(discrim_col)
roles = relationship("Role", secondary=role_to_user)
__mapper_args__ = {'polymorphic_on': discrim_col}
User.discrim.append_whereclause(Role.id==role_to_user.c.role_id)
User.discrim.append_whereclause(role_to_user.c.user_id==User.id)
class Customer(User):
__tablename__ = 'customer'
id = Column(Integer, ForeignKey(User.id), primary_key=True)
__mapper_args__ = {'polymorphic_identity':'customer'}
class Mechanic(User):
__tablename__ = 'mechanic'
id = Column(Integer, ForeignKey(User.id), primary_key=True)
__mapper_args__ = {'polymorphic_identity':'mechanic'}
e = create_engine('sqlite://', echo=True)
Base.metadata.create_all(e)
s = Session(e)
m, c = Role(name='mechanic'), Role(name='customer')
s.add_all([m, c])
s.add_all([Mechanic(roles=[m]), Customer(roles=[c])])
s.commit()
print Session(e).query(User).all()
接下来,如果我需要根据零个或多个角色来分配行为,我会这样做:把行为放在角色里,而不是放在角色的拥有者身上:
from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
name = Column(String)
roles = relationship("Role", secondary= Table(
'role_to_user', Base.metadata,
Column('role_id', Integer,
ForeignKey('role.id'),
primary_key=True),
Column('user_id', Integer,
ForeignKey('user.id'),
primary_key=True),
),
lazy="subquery"
)
def operate_on_roles(self):
for role in self.roles:
role.operate(self)
class Role(Base):
__tablename__ = 'role'
id = Column(Integer, primary_key=True)
type = Column(String, nullable=False)
__mapper_args__ = {'polymorphic_on': type}
class CustomerRole(Role):
__tablename__ = 'customer'
id = Column(Integer, ForeignKey(Role.id), primary_key=True)
__mapper_args__ = {'polymorphic_identity':'customer'}
def operate(self, user):
print "user %s getting my car fixed!" % user.name
class MechanicRole(Role):
__tablename__ = 'mechanic'
id = Column(Integer, ForeignKey(Role.id), primary_key=True)
__mapper_args__ = {'polymorphic_identity':'mechanic'}
def operate(self, user):
print "user %s fixing cars!" % user.name
e = create_engine('sqlite://', echo=True)
Base.metadata.create_all(e)
s = Session(e)
m, c = MechanicRole(), CustomerRole()
s.add_all([m, c])
s.add_all([
User(name='u1', roles=[m, c]),
User(name='u2', roles=[c]),
User(name='u3', roles=[m]),
])
s.commit()
for user in s.query(User):
user.operate_on_roles()
第二个例子产生的输出是:
user u1 fixing cars!
user u1 getting my car fixed!
user u2 getting my car fixed!
user u3 fixing cars!
可以看到,第二个例子既清晰又高效,而第一个例子只是个理论上的想法。