SQLAlchemy inter表约束

时间:2012-06-28 09:42:43

标签: sql 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)

我的问题是,我不确定如何在不成为拥有该集团的公司的成员的情况下强制执行用户不能成为Group或Role成员的约束。

1 个答案:

答案 0 :(得分: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_id表中存储Role;添加指向Group
  2. 的复合FK
  3. 还将group_idcompany_id存储在user_role表中,并将复合FK添加到Role,它基本上对您要求的内容负责
  4. 修改后的代码:

    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,你需要明确地配置关系。此外,您还需要添加一些代码(可能使用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)
    

    我可能将点 3)与一些数据库视图结合起来,这些数据库视图返回所有无效的user_role行,以便清理那些不是通过SA插入的行,而是通过一些其他方法(数据导入脚本等)