SQLalchemy在多对多关系中设置约束

Att*_*k68 2 python database many-to-many sqlalchemy

假设我有一组用户,每个用户都可以访问一组工具。相同的工具可能有许多用户具有访问权限,因此这是多对多关系:

class User(db.Model):
    __tablename__ = 'user'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String, unique=True)
    tools = db.relationship("Tool", secondary=user_tool_assoc_table,
                            back_populates='users')

class Tool(db.Model):
    __tablename__ = 'tool'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String, unique=False)

user_tool_assoc_table = db.Table('user_tool', db.Model.metadata,
    db.Column('user', db.Integer, db.ForeignKey('user.id')),
    db.Column('tool', db.Integer, db.ForeignKey('tool.id')))
Run Code Online (Sandbox Code Playgroud)

请注意,用户名是唯一的,但工具名不是。因此,User.name:Mike1并且User.name:Mike2可以访问Tool.name:HammerUser.name:John1并且User.name:John2可以分别访问,并且可以使用Tool.name:Hammer相同的名称访问,但是每个名称都不同Tool.ids

我想做一个约束,即在User.tools集合中永远不能有一个与另一个同名的工具,即

  • Tool如果已经存在具有该名称的用户,则用户不能创建一个新的内容作为其集合的一部分。Mike1无法创建一个称为Hammertools集合一部分的新工具。
  • 一个Tool存在于数据库不能被追加到tools,如果一个具有相同名称的设置已经存在的用户的集合,即John1的Hammer不能与Mike1共享,因为Mike1已有自己Hammer
  • James但是,Hammer由于他还没有锤子,因此可以创建一个新的。这样数据库中将有3个工具,Hammer每个工具都有一组不同的Users
  • 请注意,在我的特定情况下,Tool仅当a 至少具有一个时User,a 才会存在。

SQLalchemy是否可以通过本机自动配置数据库以保持完整性?我不想编写自己的验证器规则,因为我可能会遗漏某些东西,并最终得到破坏我的规则的数据库。

Ilj*_*ilä 5

问题是如何表达谓词“由ID标识的用户只有一个名称为NAME的工具”。用一个简单的表来表达,这当然很容易:

db.Table('user_toolname',
         db.Column('user', db.Integer, db.ForeignKey('user.id'), primary_key=True),
         db.Column('toolname', db.String, primary_key=True))
Run Code Online (Sandbox Code Playgroud)

同样非常清楚的是,仅凭这一点还不足以维持完整性,因为有关用户工具名的事实与实际工具之间没有任何联系。您的数据库可能会指出用户既有锤子又没有锤子。

最好以您user_tool_assoc_table或类似的方式来强制执行此操作,但是由于Tool.name它不是的主键的一部分,因此Tool您无法引用它。另一方面,由于您确实希望允许多个具有相同名称的工具共存,因此实际上,子集{id,name}是正确的键Tool

class Tool(db.Model):
    __tablename__ = 'tool'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name = db.Column(db.String, primary_key=True)
Run Code Online (Sandbox Code Playgroud)

id现在作为一个具有相同名称的工具之间的各种各样的“鉴别”。请注意,id在此模型中不必在全局范围内唯一,而在本地范围内name。使它仍然保持自动递增很方便,但是默认设置默认情况下autoincrement='auto'仅将单列整数主键视为具有自动递增行为,因此必须对其进行显式设置。

现在,还可以用定义user_tool_assoc_tabletool_name还具有一个附加约束,即用户只能使用一个具有给定名称的工具:

user_tool_assoc_table = db.Table(
    'user_tool',
    db.Column('user', db.Integer, db.ForeignKey('user.id')),
    db.Column('tool', db.Integer),
    db.Column('name', db.String),
    db.ForeignKeyConstraint(['tool', 'name'],
                            ['tool.id', 'tool.name']),
    db.UniqueConstraint('user', 'name'))
Run Code Online (Sandbox Code Playgroud)

使用此模型和以下设置:

john = User(name='John')
mark = User(name='Mark')
db.session.add_all([john, mark])
hammer1 = Tool(name='Hammer')
hammer2 = Tool(name='Hammer')
db.session.add_all([hammer1, hammer2])
db.session.commit()
Run Code Online (Sandbox Code Playgroud)

这将成功:

john.tools.append(hammer1)
hammer2.users.append(mark)
db.session.commit()
Run Code Online (Sandbox Code Playgroud)

由于违反了唯一约束,因此上述操作将失败:

john.tools.append(hammer2)
db.session.commit()
Run Code Online (Sandbox Code Playgroud)