sqlalchemy 创建关系时出现错误:映射器上已存在该名称的属性
我有两个非常简单的模型。在我的 Post
模型中,应该有两个与 User
表的关系。一个是帖子拥有者,另一个是最后编辑这个帖子的用户。它们的值可以不同,但都是指向同一个 User
表。
我的模型设置如下:
class Post(Base):
last_editor_id = Column(BigInteger, ForeignKey('users.id'), nullable=True)
last_editor = relationship('User', backref='posts', foreign_keys=[last_editor_id])
owner_id = Column(BigInteger, ForeignKey('users.id'), nullable=False, index=True)
owner = relationship('User', backref='posts', foreign_keys=[owner_id])
class User(Base):
'''This represents a user on the site'''
__tablename__ = 'users'
id = Column(BigInteger, primary_key=True, unique=True)
name = Column(BigInteger, nullable=False)
但是,当我尝试创建这些模型时,我遇到了以下错误:
sqlalchemy.exc.ArgumentError: 创建关系 'Post.owner' 的反向引用 'posts' 时出错:该名称的属性在映射器 'Mapper|User|users' 上已存在。
我该如何修正这个问题,以便在 Post
模型中保留两个外键呢?
3 个回答
你可以修正这个错误,并在Post
模型中同时保持两个外键,方法是使用独特的backref
名称
正确的代码应该是这样的
class Post(Base):
last_editor_id = Column(BigInteger, ForeignKey('users.id'), nullable=True)
last_editor = relationship('User', backref='edited_posts', foreign_keys=[last_editor_id])
owner_id = Column(BigInteger, ForeignKey('users.id'), nullable=False, index=True)
owner = relationship('User', backref='owned_posts', foreign_keys=[owner_id])
class User(Base):
'''This represents a user on the site'''
__tablename__ = 'users'
id = Column(BigInteger, primary_key=True, unique=True)
name = Column(BigInteger, nullable=False)
为什么
backref
会自动在用户那边创建一个关系,但这种方式不够明确,可能在复杂的情况下让人感到困惑。
我把backref
的名称改成了'edited_posts'
,用于last_editor
的关系;把'owned_posts'
,用于拥有者的关系。
现在,当你访问某个用户的帖子时,可以用user.edited_posts
来获取这个用户编辑过的帖子,用user.owned_posts
来获取这个用户拥有的帖子。
总的来说,这是一个关于反向引用命名的问题。
因为1对多的关系有时候会让人感到困惑,所以我总是把关系属性放在单数那一边,以避免混淆。
这样,反向引用的名称总是单数的,而关系属性总是在外键所指向的类中。
接下来是我对修复代码的建议:
class Post(Base):
last_editor_id = Column(BigInteger, ForeignKey('users.id'), nullable=True)
owner_id = Column(BigInteger, ForeignKey('users.id'), nullable=False, index=True)
class User(Base):
'''This represents a user on the site'''
__tablename__ = 'users'
id = Column(BigInteger, primary_key=True, unique=True)
name = Column(BigInteger, nullable=False)
owned_posts = relationship('Post', backref='owner')
edited_posts = relationship('Post', backref='last_editor')
现在你可以通过 User.owned_posts
获取一个 User
拥有的所有帖子,也可以通过 Post.owner
获取一个帖子的所有者。最后编辑的属性也是一样的。
如果想了解更多信息,可以查看 文档,了解如何设置关系。
这个错误告诉你,你在反向引用中多次使用了post
这个名字。你只需要给反向引用起个独特的名字就可以了。下面是一个完整的例子——我在Post类中添加了一个主键id,并且加了一些__repr__
方法,这样我们就能看到更易读的输出。
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, BigInteger, ForeignKey, Integer
from sqlalchemy.orm import relationship, sessionmaker
Base = declarative_base()
engine = create_engine('sqlite://') ## In Memory.
Session = sessionmaker()
Session.configure(bind=engine)
session = Session()
class Post(Base):
__tablename__ = 'post'
id = Column(Integer, primary_key=True)
last_editor_id = Column(BigInteger, ForeignKey('users.id'), nullable=True)
last_editor = relationship('User', backref='editor_posts', foreign_keys=[last_editor_id])
owner_id = Column(BigInteger, ForeignKey('users.id'), nullable=False, index=True)
owner = relationship('User', backref='owner_posts', foreign_keys=[owner_id])
def __repr__(self):
return '<Post: {}>'.format(self.id)
class User(Base):
'''This represents a user on the site'''
__tablename__ = 'users'
id = Column(BigInteger, primary_key=True, unique=True)
name = Column(BigInteger, nullable=False)
def __repr__(self):
return '<User: {}>'.format(self.name)
Base.metadata.create_all(engine)
bob = User(name='Bob', id=1)
alice = User(name='Alice', id=2)
post = Post(owner=alice, last_editor=bob, id=1)
session.add(post)
session.commit()
bob = session.query(User).get(1)
print(bob)
# <User: Bob>
print(bob.editor_posts)
# [<Post: 1>]
print(bob.owner_posts)
# []
post = session.query(Post).get(1)
print(post.owner)
# <User: Alice>
print(post.last_editor)
# <User: Bob>
现在,当你查询一个用户时,可以通过这个对象来访问user.owner_posts
或者user.editor_posts
。