2012-06-28 96 views
0

我試圖對系統具有用戶,公司,組和角色模型的情況建模。關係如下。SQLAlchemy間表約束

class User(Base): 
    __tablename__ = 'users' 
    id = Column(Integer, primary_key=True) 
    email = Column(Unicode(50), unique=True, nullable=False) 
    first_name = Column(Unicode(50), nullable=False) 
    last_name = Column(Unicode(50), nullable=False) 
    company_id = Column(Integer, ForeignKey('companies.id')) 

class Company(Base): 
    __tablename__ = 'companies' 
    id = Column(Integer, primary_key=True) 
    name = Column(Unicode(200), unique=True, nullable=False) 

    users = relationship("User", backref="company") 
    groups = relationship("Group", backref="company") 

class Group(Base): 
    __tablename__ = 'groups' 
    id = Column(Integer, primary_key=True) 
    name = Column(Unicode(60), nullable=False) 
    company_id = Column(Integer, ForeignKey('companies.id'), nullable=False) 
    roles = relationship("Role", backref="group") 

class Role(Base): 
    __tablename__ = 'roles' 
    id = Column(Integer, primary_key=True) 
    name = Column(Unicode(60), nullable=False) 
    group_id = Column(Integer, ForeignKey('groups.id'), nullable=False) 

我的問題是,我不知道我怎麼可以強制用戶無法成爲集團成員或角色沒有被擁有該集團的成員公司的約束。

+0

您的示例代碼是否缺少模型中'Users'鏈接到Groups的部分? – van

+0

是的。我不確定最好的方法來做到這一點,所以我把它排除了。我假設向羣組和角色添加users = relationship(「User」,backref =「group | role」)是正確的? –

回答

0

1)要回答在評論的問題:
以模型爲出發點,我會簡單地添加用戶和角色之間的M-N關係,以完成關係模型:

class UserRole(Base): 
    __tablename__ = 'user_role' 
    id = Column(Integer, primary_key=True) 
    user_id = Column(Integer, ForeignKey('users.id'), nullable=False) 
    role_id = Column(Integer, ForeignKey('roles.id'), nullable=False) 

class User(Base): 
    # ... 
    roles = relationship("Role", secondary=UserRole.__table__, backref="users") 

2)要回答這個問題的約束:

答案很簡單:與當前型號,你不能強制執行數據庫級(我不知道一個RDBMS其公關除了簡單的FK之外,還有用於交叉表約束的工具)。但是,對於一些冗餘數據,您可以這樣做:

  1. 存儲company_idRole表中;添加複合FK指向Group
  2. 店也group_idcompany_iduser_role表,並添加複合FK到Role基本上是負責什麼你問

修改後的代碼:

class Role(Base): 
    __tablename__ = 'roles' 
    id = Column(Integer, primary_key=True) 
    name = Column(Unicode(60), nullable=False) 
    group_id = Column(Integer, ForeignKey('groups.id'), nullable=False) 
    company_id = Column(Integer, ForeignKey('companies.id'), nullable=False) 
    __table_args__ = (ForeignKeyConstraint([company_id, group_id], [Group.company_id, Group.id]), {}) 

class UserRole(Base): 
    __tablename__ = 'user_role' 
    id = Column(Integer, primary_key=True) 
    user_id = Column(Integer, ForeignKey('users.id'), nullable=False) 
    group_id = Column(Integer, ForeignKey('groups.id'), nullable=False) 
    company_id = Column(Integer, ForeignKey('companies.id'), nullable=False) 
    role_id = Column(Integer, ForeignKey('roles.id'), nullable=False) 
    __table_args__ = (ForeignKeyConstraint([company_id, group_id, role_id], [Role.company_id, Role.group_id, Role.id]),) 

但是這還不夠,你必須修改更多的代碼,因爲在不同的表之間有多個FK,你需要配置關係explici TLY。您還需要添加一些代碼(可能使用ORM事件)自動添加父代的父代,等等。

3)解決方案?

你絕對可以使用SA Attribute Events來驗證你的模型:

from sqlalchemy import event 
def append_user_role(target, value, initiator): 
    assert target.company, "Cannot validate until the company is set" 
    assert target.company == value.group.company#, "Cannot add person %s (from company %s) to a Role %s (from company %s)" % (target, target.company, value, value.group.company) 
event.listen(User.roles, 'append', append_user_role) 

我可能會與返回所有user_role行其是無效的,以清潔的一些數據庫視圖結合點3)那些不是通過SA插入,而是通過其他方式插入(數據導入腳本等)

+0

謝謝你。事件似乎是解決這個問題的最好方法。我在組和角色上都使用了@validates('users')來確定在將用戶添加到組/角色之前已滿足約束條件,並且在用戶使用'remove'清除組/角色時使用了一個事件公司或集團的變化。儘管我現在必須確保任何外部應用程序也能夠滿足這些條件。 –

+0

這聽起來像是一個非常好的解決方案。驗證在系統之外執行的更改始終是一項挑戰。 – van