目录

前文列表

用 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 中定义模型类:
  1. class Post(db.Model):
  2. """Represents Proected posts."""
  3. __tablename__ = 'posts'
  4. id = db.Column(db.String(45), primary_key=True)
  5. title = db.Column(db.String(255))
  6. text = db.Column(db.Text())
  7. publish_date = db.Column(db.DateTime)
  8. # Set the foreign key for Post
  9. user_id = db.Column(db.String(45), db.ForeignKey('users.id'))
  10. # Establish contact with Comment's ForeignKey: post_id
  11. comments = db.relationship(
  12. 'Comment',
  13. backref='posts',
  14. lazy='dynamic')
  15. def __init__(self, title):
  16. self.title = title
  17. def __repr__(self):
  18. return "<Model Post `{}`>".format(self.title)
  19. class Comment(db.Model):
  20. """Represents Proected comments."""
  21. __tablename__ = 'comments'
  22. id = db.Column(db.String(45), primary_key=True)
  23. name = db.Column(db.String(255))
  24. text = db.Column(db.Text())
  25. date = db.Column(db.DateTime())
  26. post_id = db.Column(db.String(45), db.ForeignKey('posts.id'))
  27. def __init__(self, name):
  28. self.name = name
  29. def __repr__(self):
  30. return '<Model Comment `{}`>'.format(self.name)
  • 然后要记住在 manage.py 中返回 Comment
  1. def make_shell_context():
  2. """Create a python CLI.
  3. return: Default import object
  4. type: `Dict`
  5. """
  6. return dict(app=main.app,
  7. db=models.db,
  8. User=models.User,
  9. Post=models.Post,
  10. Comment=models.Comment)
  • 最后在 manager shell 验证是否成功导入了 Comment
  1. (env) [root@flask-dev JmilkFan-s-Blog]# python manage.py shell
  2. >>> Comment
  3. <class 'models.Comment'>

多对多

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

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

  1. posts_tags = db.Table('posts_tags',
  2. db.Column('post_id', db.String(45), db.ForeignKey('posts.id')),
  3. db.Column('tag_id', db.String(45), db.ForeignKey('tags.id')))
  4. class Post(db.Model):
  5. """Represents Proected posts."""
  6. __tablename__ = 'posts'
  7. id = db.Column(db.String(45), primary_key=True)
  8. title = db.Column(db.String(255))
  9. text = db.Column(db.Text())
  10. publish_date = db.Column(db.DateTime)
  11. # Set the foreign key for Post
  12. user_id = db.Column(db.String(45), db.ForeignKey('users.id'))
  13. # Establish contact with Comment's ForeignKey: post_id
  14. comments = db.relationship(
  15. 'Comment',
  16. backref='posts',
  17. lazy='dynamic')
  18. # many to many: posts <==> tags
  19. tags = db.relationship(
  20. 'Tag',
  21. secondary=posts_tags,
  22. backref=db.backref('posts', lazy='dynamic'))
  23. def __init__(self, title):
  24. self.title = title
  25. def __repr__(self):
  26. return "<Model Post `{}`>".format(self.title)
  27. class Tag(db.Model):
  28. """Represents Proected tags."""
  29. __tablename__ = 'tags'
  30. id = db.Column(db.String(45), primary_key=True)
  31. name = db.Column(db.String(255))
  32. def __init__(self, name):
  33. self.name = name
  34. def __repr__(self):
  35. 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 中导入并返回,方便之后的调试,这里就不作重复了。

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

使用样例

  1. >>> posts = db.session.query(Post).all()
  2. >>> posts
  3. [<Model Post `Second Post`>, <Model Post `First Post`>]
  4. >>> post_one = posts[1]
  5. >>> post_two = posts[0]
  6. # 实例化 3 个 Tag 的对象
  7. >>> from uuid import uuid4
  8. >>> tag_one = Tag('JmilkFan')
  9. >>> tag_one.id = str(uuid4())
  10. >>> tag_two = Tag('FanGuiju')
  11. >>> tag_two.id = str(uuid4())
  12. >>> tag_three = Tag('Flask')
  13. >>> tag_three.id = str(uuid4())
  14. # 将 Tag 的实例化对象赋值给 Post 实例化对象的 tags 属性
  15. # 即指定 Tag 和 Post 之间的关联状态
  16. # post_one 对应一个 tag
  17. # post_two 对应三个 tags
  18. # tag_one/tag_three 对应一个 post
  19. # tag_two 对象两个 posts
  20. >>> post_one.tags
  21. []
  22. >>> post_one.tags = [tag_two]
  23. >>> post_two.tags = [tag_one, tag_two, tag_three]

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

  1. mysql> select * from posts;
  2. +--------------------------------------+-------------+------+--------------+--------------------------------------+
  3. | id | title | text | publish_date | user_id |
  4. +--------------------------------------+-------------+------+--------------+--------------------------------------+
  5. | 0722c107-296f-4ac5-8133-48f3470d85ca | Second Post | NULL | NULL | ad7fd192-89d8-4b53-af96-fceb1f91070f |
  6. | 140078fe-c53b-4226-ad47-33734793e47e | First Post | NULL | NULL | ad7fd192-89d8-4b53-af96-fceb1f91070f |
  7. +--------------------------------------+-------------+------+--------------+--------------------------------------+
  8. 2 rows in set (0.00 sec)
  9. mysql> select * from tags;
  10. 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 表中。

  1. >>> db.session.add(post_one)
  2. >>> db.session.add(post_two)
  3. >>> db.session.commit()

再次查看数据库:

  1. mysql> select * from tags;
  2. +--------------------------------------+----------+
  3. | id | name |
  4. +--------------------------------------+----------+
  5. | 22c46fa9-6899-4851-b95b-cc6267b68c7c | FanGuiju |
  6. | 2a3f5c3f-1e1b-4d4a-89c6-62ea74fed75e | JmilkFan |
  7. | 76b38f31-6a23-4acd-b252-fc580e46d186 | Flask |
  8. +--------------------------------------+----------+
  9. 3 rows in set (0.00 sec)
  10. mysql> select * from posts_tags;
  11. +--------------------------------------+--------------------------------------+
  12. | post_id | tag_id |
  13. +--------------------------------------+--------------------------------------+
  14. | 140078fe-c53b-4226-ad47-33734793e47e | 22c46fa9-6899-4851-b95b-cc6267b68c7c |
  15. | 0722c107-296f-4ac5-8133-48f3470d85ca | 76b38f31-6a23-4acd-b252-fc580e46d186 |
  16. | 0722c107-296f-4ac5-8133-48f3470d85ca | 2a3f5c3f-1e1b-4d4a-89c6-62ea74fed75e |
  17. | 0722c107-296f-4ac5-8133-48f3470d85ca | 22c46fa9-6899-4851-b95b-cc6267b68c7c |
  18. +--------------------------------------+--------------------------------------+
  19. 4 rows in set (0.00 sec)

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

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

再次查看数据库的记录:

  1. mysql> select * from tags;
  2. +--------------------------------------+----------+
  3. | id | name |
  4. +--------------------------------------+----------+
  5. | 22c46fa9-6899-4851-b95b-cc6267b68c7c | FanGuiju |
  6. | 2a3f5c3f-1e1b-4d4a-89c6-62ea74fed75e | JmilkFan |
  7. | 76b38f31-6a23-4acd-b252-fc580e46d186 | Flask |
  8. +--------------------------------------+----------+
  9. 3 rows in set (0.00 sec)
  10. mysql> select * from posts;
  11. +--------------------------------------+-------------+------+--------------+--------------------------------------+
  12. | id | title | text | publish_date | user_id |
  13. +--------------------------------------+-------------+------+--------------+--------------------------------------+
  14. | 0722c107-296f-4ac5-8133-48f3470d85ca | Second Post | NULL | NULL | ad7fd192-89d8-4b53-af96-fceb1f91070f |
  15. | 140078fe-c53b-4226-ad47-33734793e47e | First Post | NULL | NULL | ad7fd192-89d8-4b53-af96-fceb1f91070f |
  16. +--------------------------------------+-------------+------+--------------+--------------------------------------+
  17. 2 rows in set (0.00 sec)
  18. mysql> select * from posts_tags;
  19. +--------------------------------------+--------------------------------------+
  20. | post_id | tag_id |
  21. +--------------------------------------+--------------------------------------+
  22. | 140078fe-c53b-4226-ad47-33734793e47e | 22c46fa9-6899-4851-b95b-cc6267b68c7c |
  23. | 0722c107-296f-4ac5-8133-48f3470d85ca | 76b38f31-6a23-4acd-b252-fc580e46d186 |
  24. | 0722c107-296f-4ac5-8133-48f3470d85ca | 2a3f5c3f-1e1b-4d4a-89c6-62ea74fed75e |
  25. | 0722c107-296f-4ac5-8133-48f3470d85ca | 22c46fa9-6899-4851-b95b-cc6267b68c7c |
  26. | 140078fe-c53b-4226-ad47-33734793e47e | 2a3f5c3f-1e1b-4d4a-89c6-62ea74fed75e |
  27. +--------------------------------------+--------------------------------------+
  28. 5 rows in set (0.00 sec)

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

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

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

获取标签 JmilkFan 下有哪些文章

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

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

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

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

  1. >>> db.session.query(Post).filter_by(title='First Post').first().tags
  2. [<Model Tag `JmilkFan`>, <Model Tag `FanGuiju`>]
  3. >>> db.session.query(Tag).filter_by(name='JmilkFan').first().posts
  4. <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 还会提供很多有用的功能,感兴趣的话可以继续挖掘,这里就不多做介绍了。

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

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

    Blog 项目源码:https://github.com/JmilkFan/JmilkFan-s-Blog 目录 目录 前文列表 扩展阅读 前言 一对多 再一次 sync db How to use ...

  2. 用 Flask 来写个轻博客

    用 Flask 来写个轻博客 用 Flask 来写个轻博客 (1) — 创建项目 用 Flask 来写个轻博客 (2) — Hello World! 用 Flask 来写个轻博客 (3) — (M)V ...

  3. 用 Flask 来写个轻博客 (37) — 在 Github 上为第一阶段的版本打 Tag

    Blog 项目源码:https://github.com/JmilkFan/JmilkFan-s-Blog 目录 目录 前文列表 第一阶段结语 打 Tag 前文列表 用 Flask 来写个轻博客 (1 ...

  4. 用 Flask 来写个轻博客 (36) — 使用 Flask-RESTful 来构建 RESTful API 之五

    目录 目录 前文列表 PUT 请求 DELETE 请求 测试 对一条已经存在的 posts 记录进行 update 操作 删除一条记录 前文列表 用 Flask 来写个轻博客 (1) - 创建项目 用 ...

  5. 用 Flask 来写个轻博客 (35) — 使用 Flask-RESTful 来构建 RESTful API 之四

    Blog 项目源码:https://github.com/JmilkFan/JmilkFan-s-Blog 目录 目录 前文列表 POST 请求 身份认证 测试 前文列表 用 Flask 来写个轻博客 ...

  6. 用 Flask 来写个轻博客 (34) — 使用 Flask-RESTful 来构建 RESTful API 之三

    目录 目录 前文列表 应用请求中的参数实现 API 分页 测试 前文列表 用 Flask 来写个轻博客 (1) - 创建项目 用 Flask 来写个轻博客 (2) - Hello World! 用 F ...

  7. 用 Flask 来写个轻博客 (33) — 使用 Flask-RESTful 来构建 RESTful API 之二

    Blog 项目源码:https://github.com/JmilkFan/JmilkFan-s-Blog 目录 目录 前文列表 扩展阅读 构建 RESTful Flask API 定义资源路由 格式 ...

  8. 用 Flask 来写个轻博客 (32) — 使用 Flask-RESTful 来构建 RESTful API 之一

    目录 目录 前文列表 扩展阅读 RESTful API REST 原则 无状态原则 面向资源 RESTful API 的优势 REST 约束 前文列表 用 Flask 来写个轻博客 (1) - 创建项 ...

  9. 用 Flask 来写个轻博客 (31) — 使用 Flask-Admin 实现 FileSystem 管理

    Blog 项目源码:https://github.com/JmilkFan/JmilkFan-s-Blog 目录 目录 前文列表 扩展阅读 编写 FileSystem Admin 页面 Flask-A ...

随机推荐

  1. JavaScript-Tool-截取头像:ShearPhoto

    ylbtech-JavaScript-Tool-截取头像:ShearPhoto ShearPhoto 2.0 发布,支持HTML5本地截取头像,支持美图秀秀特效,支持几十M数码相片压缩截取 1.返回顶 ...

  2. Php 单元测试 phpunit && codecept

    Php 单元测试 phpunit && codecept phpunit: Windows版本 整体上说,在 Windows 下安装 PHAR 和手工在 Windows 下安装 Com ...

  3. docker 实战

    创建镜像 docker pull ubuntu 创建容器 docker run -it -name web ubuntu /bin/bash 更新软件源信息 apt-get update 安装ssh  ...

  4. Spark Streaming + Kafka 整合向导之createDirectStream

    启动zk: zkServer.sh start 启动kafka:kafka-server-start.sh $KAFKA_HOME/config/server.properties 创建一个topic ...

  5. Shell脚本中判断输入变量或者参数是否为空的方法

    shell判断一个变量是否为空方法总结 https://www.jb51.net/article/154835.htm 1.判断变量 复制代码代码如下: read -p "input a w ...

  6. [fw]用Kprobes调试(debug)内核

    Kprobes是一种运行时动态调试内核的机制, 你可以用它设置断点并收集调试信息, 甚至改变内核行为. Kprobes分三种, 普通kprobes以及基于普通kprobes的jprobes和kretp ...

  7. JavaScript 盖尔-沙普利算法

    最近在学 JavaScript , 为了尽快熟悉语法,决定移植以前写的盖尔-沙普利算法. c# 下的代码:https://www.cnblogs.com/aitong/p/10973774.html ...

  8. 【目录】sql server 架构篇系列

    随笔分类 - sql server 架构篇系列 sql server 高可用镜像 摘要: 一.什么是数据库镜像 基本软件的高可用性解决方案 快速的故障转移恢复(3秒转移),低硬件成本 基于数据库级别的 ...

  9. .net core 部署到IIS 以及上 HTTP Error 502.5 - ANCM Out-Of-Process Startup Failure

    安装AspNetCoreModule托管模块后执行 1.net stop was /y 2.net start w3svc 测试可以,但是需要装对应的托管模块的版本. 1. .NET Core与Win ...

  10. C++ I/O库练习

    编写函数,以读模式打开一个文件,将其内容读入到一个string的vector中,将每一行作为一个独立的元素存于vector中,并输出. 思路:1.以读的模式打开文件“目录.txt”: 2.先创建str ...