classBlog(Base): __tablename__ = 'blog' id = Column(Integer, primary_key=True) name = Column(String(64)) tags = relationship( 'Tag', secondary=association, backref=backref('blogs', lazy='dynamic'), lazy='dynamic')
classTag(Base): __tablename__ = 'tag' id = Column(Integer, primary_key=True) name = Column(String(64))
一个经常被拿出来作为演示的 Many-To-Many 模型。
先填充一些数据:
1 2 3 4 5
In [1]: blog = Blog(name='first') In [2]: blog.tags.append(Tag(name='t1')) In [3]: blog.tags.append(Tag(name='t2')) In [4]: session.add(blog) In [5]: session.commit()
接下来就可以获取这些对象的所有信息了:
1 2 3 4 5 6 7 8
In [4]: blog.tags.all() Out[4]: [<Tag at 0x1fdbab6f198>, <Tag at 0x1fdbab6f208>]
In [5]: blog.tags.all()[0].name Out[5]: 't1'
In [6]: [t.name for t in blog.tags] Out[6]: ['t1', 't2']
上面的操作有点复杂。对我们而言,Tag 对象只有 name 字段是有用的,为了获取 name 字段,我们要写很多额外的代码把 name 字段从 Tag 对象中剥离出来。association_proxy 就可以用来简化这个操作。
现在修改一下上面的 Blog 映射:
1 2 3 4 5 6 7 8 9
from sqlalchemy.ext.associationproxy import association_proxy
上面的例子里把 association 表作为一个普通的 Table 对象,是因为 association 中不需要保存额外信息,只需要作为 Blog 和 Tag 的中转。现在有了新的需求,我们需要知道每篇博客的标签是在什么时候加上的,这就需要在 association 表中增加一个额外的字段用来表示创建时间,同时为了获取这个时间,还要把 association 改造成一个真正的映射:
blog = relationship('Blog', backref=backref('blog_tags', lazy='dynamic'), lazy='joined') tag = relationship('Tag', backref=backref('tag_blogs', lazy='dynamic'), lazy='joined')
classTag(Base): __tablename__ = 'tag' id = Column(Integer, primary_key=True) name = Column(String(64))
classBlog(Base): __tablename__ = 'blog' id = Column(Integer, primary_key=True) name = Column(String(64))
这里实际上是把 Many-To-Many 拆成了两个 One-To-Many。
然后构造一些数据:
1 2 3 4 5 6 7
In [1]: blog = Blog(name='first') ...: tags = [Tag(name='t1'), Tag(name='t2')] ...: for tag in tags: ...: session.add(Association(blog=blog, tag=tag)) ...: session.add(blog) ...: session.add_all(tags) ...: session.commit()
现在就可以获取 Tag 和被添加的时间了:
1 2 3 4 5
In [2]: blog.blog_tags[0].tag.name Out[2]: 't1'
In [3]: blog.blog_tags[0].created_at Out[3]: datetime.datetime(2018, 3, 18, 16, 4, 17)
可以看到,给 Blog 增加标签要经过 Association 这个中间对象。虽然表结构的确如此,但是我们仍然希望 Association 表是透明的,仅当需要获取其中的创建时间时才明确获取 Association 对象。只需要在 Blog 中声明一个关联代理: