用 Flask 来写个轻博客 (7) — (M)VC_models 的关系(many to many)

目录

  • 目录
  • 前文列表
  • 扩展阅读
  • 前期准备
  • 多对多
  • 使用样例
  • 一直在使用的 session

前文列表

用 Flask 来写个轻博客 (1) — 创建项目

用 Flask 来写个轻博客 (2) — Hello World!

用 Flask 来写个轻博客 (3) — (M)VC_连接 MySQL 和 SQLAlchemy

用 Flask 来写个轻博客 (4) — (M)VC_创建数据模型和表

用 Flask 来写个轻博客 (5) — (M)VC_SQLAlchemy 的 CRUD 详解

用 Flask 来写个轻博客 (6) — (M)VC_models 的关系(one to many)

扩展阅读

SQLAlchemy_定义(一对一/一对多/多对多)关系

Openstack_SQLAlchemy_一对多关系表的多表插入实现

前期准备

在实现多对多之前,我们还需要先增加一个评论(Comment) models class,而且 Comment 是 Post 的关系是 one to many。

  • 首先依旧是在 models.py 中定义模型类:
class Post(db.Model):
    """Represents Proected posts."""

    __tablename__ = ‘posts‘
    id = db.Column(db.String(45), primary_key=True)
    title = db.Column(db.String(255))
    text = db.Column(db.Text())
    publish_date = db.Column(db.DateTime)
    # Set the foreign key for Post
    user_id = db.Column(db.String(45), db.ForeignKey(‘users.id‘))
    # Establish contact with Comment‘s ForeignKey: post_id
    comments = db.relationship(
        ‘Comment‘,
        backref=‘posts‘,
        lazy=‘dynamic‘)

    def __init__(self, title):
        self.title = title

    def __repr__(self):
        return "<Model Post `{}`>".format(self.title)

class Comment(db.Model):
    """Represents Proected comments."""

    __tablename__ = ‘comments‘
    id = db.Column(db.String(45), primary_key=True)
    name = db.Column(db.String(255))
    text = db.Column(db.Text())
    date = db.Column(db.DateTime())
    post_id = db.Column(db.String(45), db.ForeignKey(‘posts.id‘))

    def __init__(self, name):
        self.name = name

    def __repr__(self):
        return ‘<Model Comment `{}`>‘.format(self.name)
  • 然后要记住在 manage.py 中返回 Comment
def make_shell_context():
    """Create a python CLI.

    return: Default import object
    type: `Dict`
    """
    return dict(app=main.app,
                db=models.db,
                User=models.User,
                Post=models.Post,
                Comment=models.Comment)
  • 最后在 manager shell 验证是否成功导入了 Comment
(env) [root@flask-dev JmilkFan-s-Blog]# python manage.py shell
>>> Comment
<class ‘models.Comment‘>

多对多

如果我们有两个 models,它们之间是相互引用的,而且彼此都可以相互引用对方的多个对象。这就是所谓 many to many 的关系类型。

多对多关系会在两个类之间增加一个关联表。 这个关联的表在 relationship() 方法中通过 secondary 参数来表示。通常的,这个表会通过 MetaData 对象来与声明基类关联, 所以这个 ForeignKey 指令会使用链接来定位到远程的表:

posts_tags = db.Table(‘posts_tags‘,
    db.Column(‘post_id‘, db.String(45), db.ForeignKey(‘posts.id‘)),
    db.Column(‘tag_id‘, db.String(45), db.ForeignKey(‘tags.id‘)))

class Post(db.Model):
    """Represents Proected posts."""

    __tablename__ = ‘posts‘
    id = db.Column(db.String(45), primary_key=True)
    title = db.Column(db.String(255))
    text = db.Column(db.Text())
    publish_date = db.Column(db.DateTime)
    # Set the foreign key for Post
    user_id = db.Column(db.String(45), db.ForeignKey(‘users.id‘))
    # Establish contact with Comment‘s ForeignKey: post_id
    comments = db.relationship(
        ‘Comment‘,
        backref=‘posts‘,
        lazy=‘dynamic‘)
    # many to many: posts <==> tags
    tags = db.relationship(
        ‘Tag‘,
        secondary=posts_tags,
        backref=db.backref(‘posts‘, lazy=‘dynamic‘))

    def __init__(self, title):
        self.title = title

    def __repr__(self):
        return "<Model Post `{}`>".format(self.title)

class Tag(db.Model):
    """Represents Proected tags."""

    __tablename__ = ‘tags‘
    id = db.Column(db.String(45), primary_key=True)
    name = db.Column(db.String(255))

    def __init__(self, name):
        self.name = name

    def __repr__(self):
        return "<Model Tag `{}`>".format(self.name)
  • many to many 的关系仍然是由 db.relationship() 来定义
  • seconddary(次级):会告知 SQLAlchemy 该 many to many 的关联保存在 posts_tags 表中
  • backref:声明表之间的关系是双向,帮助手册 help(db.backref)。需要注意的是:在 one to many 中的 backref 是一个普通的对象,而在 many to many 中的 backref 是一个 List 对象。

NOTE 1:实际上 db.Table 对象对数据库的操作比 db.Model 更底层一些。后者是基于前者来提供的一种对象化包装,表示数据库中的一条记录。 posts_tags 表对象之所以使用 db.Table 不使用 db.Model 来定义,是因为我们不需要对 posts_tags (self.name)进行直接的操作(不需要对象化),posts_tags 代表了两张表之间的关联,会由数据库自身来进行处理。

NOTE 2posts_tags 的声明定义最好在 Post 和 Tag 之前。

NOTE 3: 没添加一个 models class 都要记得在 manage.py 中导入并返回,方便之后的调试,这里就不作重复了。

  • 同步数据库
(env) [root@flask-dev JmilkFan-s-Blog]# python manage.py shell
>>> db.create_all()

使用样例

>>> posts = db.session.query(Post).all()
>>> posts
[<Model Post `Second Post`>, <Model Post `First Post`>]
>>> post_one = posts[1]
>>> post_two = posts[0]

# 实例化 3 个 Tag 的对象
>>> from uuid import uuid4
>>> tag_one = Tag(‘JmilkFan‘)
>>> tag_one.id = str(uuid4())
>>> tag_two = Tag(‘FanGuiju‘)
>>> tag_two.id = str(uuid4())
>>> tag_three = Tag(‘Flask‘)
>>> tag_three.id = str(uuid4())

# 将 Tag 的实例化对象赋值给 Post 实例化对象的 tags 属性
# 即指定 Tag 和 Post 之间的关联状态
# post_one 对应一个 tag
# post_two 对应三个 tags
# tag_one/tag_three 对应一个 post
# tag_two 对象两个 posts
>>> post_one.tags
[]
>>> post_one.tags = [tag_two]
>>> post_two.tags = [tag_one, tag_two, tag_three]

NOTE: 此时的数据库中还是只有原来就已经存在的两条 posts 记录,但是还没有 tags 记录。这是因为在刚刚实例化的 Tag 对象还没有被提交,所以不会被写入到数据库中。

mysql> select * from posts;
+--------------------------------------+-------------+------+--------------+--------------------------------------+
| id                                   | title       | text | publish_date | user_id                              |
+--------------------------------------+-------------+------+--------------+--------------------------------------+
| 0722c107-296f-4ac5-8133-48f3470d85ca | Second Post | NULL | NULL         | ad7fd192-89d8-4b53-af96-fceb1f91070f |
| 140078fe-c53b-4226-ad47-33734793e47e | First Post  | NULL | NULL         | ad7fd192-89d8-4b53-af96-fceb1f91070f |
+--------------------------------------+-------------+------+--------------+--------------------------------------+
2 rows in set (0.00 sec)

mysql> select * from tags;
Empty set (0.00 sec)

NOTE:再次提交 post_one/post_two 对象。post_one/post_two 对象相应的记录本就已经存在于数据库中了为什么要重新提交呢?

这是因为 post_one/post_two 都被指定了新的关联属性 tags,所以提交 post_one/post_two 不仅仅是更新 posts 的引用,更重要的是将新创建的 3 个 tags 对象写入到数据库中,同时也是将 posts 和 tags 的映射关系写入到 posts_tags 表中。

>>> db.session.add(post_one)
>>> db.session.add(post_two)
>>> db.session.commit()

再次查看数据库:

mysql> select * from tags;
+--------------------------------------+----------+
| id                                   | name     |
+--------------------------------------+----------+
| 22c46fa9-6899-4851-b95b-cc6267b68c7c | FanGuiju |
| 2a3f5c3f-1e1b-4d4a-89c6-62ea74fed75e | JmilkFan |
| 76b38f31-6a23-4acd-b252-fc580e46d186 | Flask    |
+--------------------------------------+----------+
3 rows in set (0.00 sec)

mysql> select * from posts_tags;
+--------------------------------------+--------------------------------------+
| post_id                              | tag_id                               |
+--------------------------------------+--------------------------------------+
| 140078fe-c53b-4226-ad47-33734793e47e | 22c46fa9-6899-4851-b95b-cc6267b68c7c |
| 0722c107-296f-4ac5-8133-48f3470d85ca | 76b38f31-6a23-4acd-b252-fc580e46d186 |
| 0722c107-296f-4ac5-8133-48f3470d85ca | 2a3f5c3f-1e1b-4d4a-89c6-62ea74fed75e |
| 0722c107-296f-4ac5-8133-48f3470d85ca | 22c46fa9-6899-4851-b95b-cc6267b68c7c |
+--------------------------------------+--------------------------------------+
4 rows in set (0.00 sec)

NOTE:在上面说过了 many to many 的 backref 是一个 List 对象,所以我们还可以反过来为 tags 添加一个 posts 对象(引用)。

>>> tag_one.posts.all()
[<Model Post `Second Post`>]
>>> tag_one.posts.append(post_one)
>>> tag_one.posts.all()
[<Model Post `Second Post`>, <Model Post `First Post`>]
>>> post_one.tags
[<Model Tag `FanGuiju`>, <Model Tag `JmilkFan`>]
# 因为修改了 tag_one 的 posts 属性(添加了 post_one 的引用),所以需要重新提交 tag_one 才会被写入到数据库。
>>> db.session.add(tag_one)
>>> db.session.commit()

再次查看数据库的记录:

mysql> select * from tags;
+--------------------------------------+----------+
| id                                   | name     |
+--------------------------------------+----------+
| 22c46fa9-6899-4851-b95b-cc6267b68c7c | FanGuiju |
| 2a3f5c3f-1e1b-4d4a-89c6-62ea74fed75e | JmilkFan |
| 76b38f31-6a23-4acd-b252-fc580e46d186 | Flask    |
+--------------------------------------+----------+
3 rows in set (0.00 sec)

mysql> select * from posts;
+--------------------------------------+-------------+------+--------------+--------------------------------------+
| id                                   | title       | text | publish_date | user_id                              |
+--------------------------------------+-------------+------+--------------+--------------------------------------+
| 0722c107-296f-4ac5-8133-48f3470d85ca | Second Post | NULL | NULL         | ad7fd192-89d8-4b53-af96-fceb1f91070f |
| 140078fe-c53b-4226-ad47-33734793e47e | First Post  | NULL | NULL         | ad7fd192-89d8-4b53-af96-fceb1f91070f |
+--------------------------------------+-------------+------+--------------+--------------------------------------+
2 rows in set (0.00 sec)

mysql> select * from posts_tags;
+--------------------------------------+--------------------------------------+
| post_id                              | tag_id                               |
+--------------------------------------+--------------------------------------+
| 140078fe-c53b-4226-ad47-33734793e47e | 22c46fa9-6899-4851-b95b-cc6267b68c7c |
| 0722c107-296f-4ac5-8133-48f3470d85ca | 76b38f31-6a23-4acd-b252-fc580e46d186 |
| 0722c107-296f-4ac5-8133-48f3470d85ca | 2a3f5c3f-1e1b-4d4a-89c6-62ea74fed75e |
| 0722c107-296f-4ac5-8133-48f3470d85ca | 22c46fa9-6899-4851-b95b-cc6267b68c7c |
| 140078fe-c53b-4226-ad47-33734793e47e | 2a3f5c3f-1e1b-4d4a-89c6-62ea74fed75e |
+--------------------------------------+--------------------------------------+
5 rows in set (0.00 sec)

NOTE: 从 posts_tags 的记录中可以看出 posts 和 tags 之间的多对多关系。

获取文章 First Post 下有哪些标签:

>>> db.session.query(Post).filter_by(title=‘First Post‘).first().tags
[<Model Tag `JmilkFan`>, <Model Tag `FanGuiju`>]

获取标签 JmilkFan 下有哪些文章

>>> db.session.query(Tag).filter_by(name=‘JmilkFan‘).first().posts.all()
[<Model Post `First Post`>, <Model Post `Second Post`>]

NOTE:再次说明一下,在定义 models 间关系时使用的 backref 参数,指定了加载关联对象的方式(这里使用了动态方式),所以加载 Tag 的关联对象 Post 时,返回的是 sqlalchemy.orm.dynamic.AppenderBaseQuery object 而不是全部的关联对象。

那么为什么反之却是直接返回全部的关联对象呢?

这是因为我们是在 Post 中使用了 backref 对象,所以对于两者的关系而言,backref 指的是 Tag。

>>> db.session.query(Post).filter_by(title=‘First Post‘).first().tags
[<Model Tag `JmilkFan`>, <Model Tag `FanGuiju`>]
>>> db.session.query(Tag).filter_by(name=‘JmilkFan‘).first().posts
<sqlalchemy.orm.dynamic.AppenderBaseQuery object at 0x38ff850>

一直在使用的 session

session 是连接到数据库的一个桥梁,实际上 session 具有更多的功能,例如:事务

事务:是对数据库进行操作即集合,在我们 commit 的时候,实际事务帮我们实现了一系列有效的数据库操作。

例如:刚刚我们在 commit 一个 post_one/post_two 前,明明没有 commit tag_one/tag_two/tag_three,为什么数据库中还会写入这三条记录呢?这些都是由事务去帮我们进行的隐式的数据库操作。如果没有事务我们就需要按部就班一步步的完成对数据库的写入,这样的效率是非常低的。除此之外 SQLAlchemy 的 session 还会提供很多有用的功能,感兴趣的话可以继续挖掘,这里就不多做介绍了。

时间: 2024-12-29 23:13:57

用 Flask 来写个轻博客 (7) — (M)VC_models 的关系(many to many)的相关文章

用 Flask 来写个轻博客 (6) — (M)VC_models 的关系(one to many)

目录 目录 前文列表 扩展阅读 前言 一对多 再一次 sync db How to use 前文列表 用 Flask 来写个轻博客 (1) - 创建项目 用 Flask 来写个轻博客 (2) - Hello World! 用 Flask 来写个轻博客 (3) - (M)VC_连接 MySQL 和 SQLAlchemy 用 Flask 来写个轻博客 (4) - (M)VC_创建数据模型和表 用 Flask 来写个轻博客 (5) - (M)VC_SQLAlchemy 的 CRUD 详解 扩展阅读 S

用 Flask 来写个轻博客 (27) — 使用 Flask-Cache 实现网页缓存加速

目录 目录 前文列表 扩展阅读 Flask-Cache 应用 Flask-Cache 实现视图函数缓存 缓存无参数的普通函数 缓存带参数的普通函数 缓存无动态参数的视图函数 缓存带动态参数的视图函数 前文列表 用 Flask 来写个轻博客 (1) - 创建项目 用 Flask 来写个轻博客 (2) - Hello World! 用 Flask 来写个轻博客 (3) - (M)VC_连接 MySQL 和 SQLAlchemy 用 Flask 来写个轻博客 (4) - (M)VC_创建数据模型和表

用 Flask 来写个轻博客 (8) — (M)VC_Alembic 管理数据库结构的升级和降级

目录 目录 前文列表 扩展阅读 Alembic 查看指令 manager db 的可用选项 初始化 DB Migrate 开始第一次跟踪 将记录文件应用到数据库中实时升级数据库结构 回滚到某一个记录环境中 前文列表 用 Flask 来写个轻博客 (1) - 创建项目 用 Flask 来写个轻博客 (2) - Hello World! 用 Flask 来写个轻博客 (3) - (M)VC_连接 MySQL 和 SQLAlchemy 用 Flask 来写个轻博客 (4) - (M)VC_创建数据模型

HTML+JS 写一个 个人博客

HTML+JS 写一个 个人博客(初学版) HTML+JS 写一个 个人博客(初学版) 自己琢磨了两三天,算是把HTML入门了吧,然后就想着用这点知识写一个个人主页,然后一下午的时间写完了. 先不看代码,说说思路: 第一步:就是构思,一个网页的展示怎么才好看?!!站在一个老男人的角度,那远远是不能的,,, 1.标题 2.菜单栏(加个超链接) 3.展示页面(图片轮播) 第二步:小标题展示页面. 第三步:代码实现.(说了一堆废话)  代码 Jscript实现图片轮播.(很简单,一看就懂) <scri

一个前端写的php博客系统--支持markdown哦

部署的地址 我的博客地址:http://www.weber.pub/ 起因 最近买了个新的域名,同时在阿里云申请了免费的虚拟主机...借此机会就想搞个自己的博客站点,虽然之前也使用了hexo + github . WordPress 做过,但是我的阿里云虚拟主机实在是不知道怎么去搞hexo (也许根本就搞不起来,除非自己花钱买主机,哈哈)...WordPress总是感觉好笨重,运行起来一股的人到中年,气喘吁吁啊...百度之后,typeecho成了首选,但是markdown竟然有问题...一口老血

Flask+mongodb 实现简易个人博客

最近学习完了<flask-web开发>,实现了一个简易的个人博客网站,由flask+mongodb+bootstrap做成, 这个软件是在阅读<Flask-Web开发>后写的一个小型web程序,不过我没有使用书中作者使用的SQLALchemy.而使用了mongodb,如果你也在学习这本书,我也推荐你使用与作者不同的数据库,因为使用了不同的数据库,意味着你需要对作者在书中的代码作出修改.这个过程可以帮助你熟悉整个软件,让整个学习变得更加有效. 现在网页做的还不够美观,但是基本的功能发

python 生成18年写过的博客词云

文章链接:https://mp.weixin.qq.com/s/NmJjTEADV6zKdT--2DXq9Q 回看18年,最有成就的就是有了自己的 博客网站,坚持记录,写文章,累计写了36篇了,从一开始的难以下手,到现在成为一种习惯,虽然每次写都会一字一句斟酌,但是每次看到产出,内心还是开心的,享受这样的过程. 这篇文章就是用python 生成自己写的博客词云,平常写的博客都是markdown 格式的,直接把文件传到后台,前端用js去解析文件显示的,所以我这里处理数据就不需要去爬虫网站,直接读文

最近写了一个博客程序: QuickBlog PHP 开源的一文多发系统

开始 在开始之前也用到了一些其他类似的系统,区别大概就是非开源的商业化产品不安全,无法进行自我数据存储管理.开源的产品部署起来总有一些问题或者有很多自己想实现的点没有完成. 犹豫了一段时间之后,断断续续写起来当前的这个系统, 主要目的也比较简单,可以在一个地方进行博客/文章撰写,然后可以自动发布到多个平台,通过不同的平台获得提升文章展现量.最后,如果还可以做成独立博客,给搜索引擎蜘蛛抓取,让其他平台进而给自己网站导流,因为你可以很轻易给自己网站增加装饰,甚至增加微薄的广告收入,那就比较完美了.

ASP.NET写的一个博客系统

由于域名闲置,正好也有服务器空间,短期内开发了一个博客系统. 大家都来谈  http://www.djdlt.com 目前是开放注册,免费发布.(限于空间有限,图片还是尽量少传些) 网站架构: ASP.NET SQLSERVER2008 支持多屏幕响应.如.平板.手机.自适应所有平台. 网站能干什么: 发布你的想法 参与表达你对某件事的看法 将来要怎样: 对发布信息的人提供打赏功能,实现知识就是财富. 注册的发步者实现SNS化管理,增加关注.粉丝.好友等实现交流. 感谢: 感谢博客园提供推广平台