Blog 项目源码:https://github.com/JmilkFan/JmilkFan-s-Blog

目录

前文列表

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

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

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

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

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

扩展阅读

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

前言

models 中的关系能够映射成为关系型数据库表中的关系,models 中可以相互建立引用,使得相关联的数据能够很容易的一次性的从数据库中取出。

一对多

  • 首先继续在 models.py 中建立一个 Post models 来表示 Blog 中的文章。而且一个用户 User 可以拥有多篇文章 Post,他们之间的关系是一对多。

表示一对多的关系时,在子表类 Post 中需要通过 foreign key (外键)引用父表类 User。

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')) def __init__(self, title):
self.title = title def __repr__(self):
return "<Model Post `{}`>".format(self.title)

其中 user_id 字段是 posts 表的外键,代表了数据库中的一种约束规则 —— 外键约束。这种规则强制规定了字段 user_id 的值必须同时存在于 User.id 列中。用来保证每一篇 post 都能对应找到一个 user,而且一个 user 能够对应多篇 posts。

NOTE: 如果你没有在父表类指定 __tablename__ 属性,那么这一条语句我们应该这么写:

user_id = db.Column(db.String(45), db.ForeignKey('User.id'))

但是一般不建议写成这样,因为在 SQLAlchemy 初始化期间, User 对象可能还没有被创建出来,所以同时也建议在定义 models class 的时候应该指定 __tablename__ 属性。

  • 然后我们还需要在父表类 User 中定义出这种 one to many 的关系:
class User(db.Model):
"""Represents Proected users.""" # Set the name for table
__tablename__ = 'users'
id = db.Column(db.String(45), primary_key=True)
username = db.Column(db.String(255))
password = db.Column(db.String(255))
# Establish contact with Post's ForeignKey: user_id
posts = db.relationship(
'Post',
backref='users',
lazy='dynamic') def __init__(self, id, username, password):
self.id = id
self.username = username
self.password = password def __repr__(self):
"""Define the string format for instance of User."""
return "<Model User `{}`>".format(self.username)
  • db.relationsformat(self.username)hip: 会在 SQLAlchemy 中创建一个虚拟的列,该列会与 Post.user_id (db.ForeignKey) 建立联系。这一切都交由 SQLAlchemy 自身管理。

  • backref:用于指定表之间的双向关系,如果在一对多的关系中建立双向的关系,这样的话在对方看来这就是一个多对一的关系。

  • lazy:指定 SQLAlchemy 加载关联对象的方式。

    • lazy=subquery: 会在加载 Post 对象后,将与 Post 相关联的对象全部加载,这样就可以减少 Query 的动作,也就是减少了对 DB 的 I/O 操作。但可能会返回大量不被使用的数据,会影响效率。
    • lazy=dynamic: 只有被使用时,对象才会被加载,并且返回式会进行过滤,如果现在或将来需要返回的数据量很大,建议使用这种方式。Post 就属于这种对象。

再一次 sync db

每一次新增了 models class,都需要导入到 manage.py 中,在通过 manager shell 来同步数据库。

# import Flask Script object
from flask.ext.script import Manager, Server
import main
import models # Init manager object via app object
manager = Manager(main.app) # Create some new commands
manager.add_command("server", Server()) @manager.shell
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) if __name__ == '__main__':
manager.run()

NOTE: 因为前面我们对原有的 users 表结构做了修改,所以我们需要将 users 表删除,再重新同步数据库。但需要注意,这是一种非常不推荐的方法,以后我们会介绍一种更加科学的增量同步数据库的方法。

mysql> DROP TABLE users;
  • 重新同步数据库
(env) [root@flask-dev JmilkFan-s-Blog]# python manage.py shell
>>> db.create_all()
>>> Post
<class 'models.Post'>
mysql> show tables;
+------------------+
| Tables_in_myblog |
+------------------+
| posts |
| users |
+------------------+
2 rows in set (0.00 sec) mysql> desc posts
;
+--------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------------+--------------+------+-----+---------+-------+
| id | varchar(45) | NO | PRI | NULL | |
| title | varchar(255) | YES | | NULL | |
| text | text | YES | | NULL | |
| publish_date | datetime | YES | | NULL | |
| user_id | varchar(45) | YES | MUL | NULL | |
+--------------+--------------+------+-----+---------+-------+
5 rows in set (0.00 sec)

How to use

>>> from uuid import uuid4
# 实例化一个 User 的对象
>>> user = User(id=str(uuid4()), username='jmilkfan', password='fanguiju')
# 写入一条 users 记录
>>> db.session.add(user)
>>> db.session.commit() >>> user.posts
<sqlalchemy.orm.dynamic.AppenderBaseQuery object at 0x22bc410>

NOTE: 因为 user 的关联对象的加载方式为动态方式,所以 user.posts 会返回一个 Query 对象,需要调用 filter()/all()/first() 来获取实际需要被使用到的对象。

反之,如果是子查询方式的话,就是直接将关联对象全部返回

# 现在因为还没有添加 posts 的记录所以为空
>>> user.posts.all()
[] # 实例化一个 Post 的对象
>>> post_one = Post('First Post')
# 主键值是非空的,必须指定一个,否则会报错
>>> post_one.id = str(uuid4())
# 指定该 post 是属于哪一个 user 的
>>> post_one.user_id = user.id
>>> db.session.add(post_one)
>>> db.session.commit() >>> user.posts.all()
[<Model Post `First Post`>]

NOTE: 必须在 commit 了 post_one 对象之后,user 才能够通过关系来获取关联对象 posts。

上面一个例子是为 user 添加一个 post,那么反过来能不能为一个 post 指定一个 user 呢?

如果我们有使用到 backref 参数的话,那答案就是肯定的,这也是该参数所谓 双向 的含义。

# 获取一个已经存在数据库中的记录 user
>>> user = db.session.query(User).first()
>>> user.id
u'ad7fd192-89d8-4b53-af96-fceb1f91070f' # 实例化一个 Post 的对象 post_second
>>> post_second = Post('Second Post')
# 必须为其设置主键值
>>> post_second.id = str(uuid4())
# 现在该 post_second 对象是没有关联到任何 user 的
>>> post_second.users
# 为 post_second 指定一个 user 对象
>>> post_second.users = user

NOTE:# 另一种建立联系的写法为 user.posts.append(post_second)

因为 user.posts 本质上是一个列表,只要该列表中存在 post 那么 post 就是属于这个 user 的

# 将 post_second 写入数据库
>>> db.session.add(post_second)
>>> db.session.commit()
# 写入完成之后,user 才能够通过关系来访问到属于其下的 posts
>>> user.posts.all()
[<Model Post `Second Post`>, <Model Post `First Post`>]

ERROR LOG

InvalidRequestError: This Session's transaction has been rolled back due to a previous exception during flush. To begin a new transaction with this Session, first issue Session.rollback(). Original exception was: (pymysql.err.InternalError) (1364, u"Field 'id' doesn't have a default value") [SQL: u'INSERT INTO posts (title, text, publish_date, user_id) VALUES (%(title)s, %(text)s, %(publish_date)s, %(user_id)s)'] [parameters: {'text': None, 'title': 'First Post', 'publish_date': None, 'user_id': '9ecae9b3-f4d2-4c8e-b033-616bb1642842'}]

TS: 因为 commit 一个指定了 user_id 的 port 对象时,实际上数据库中还不存在被关联的 user 对象,导致报错。

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

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

    目录 目录 前文列表 扩展阅读 前期准备 多对多 使用样例 一直在使用的 session 前文列表 用 Flask 来写个轻博客 (1) - 创建项目 用 Flask 来写个轻博客 (2) - Hel ...

  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. 慕课-tooltip提示框总结

    在慕课上学Waynej老师的tooltip浮动提示框,老师每次讲课都会强调搬砖的流程,这点在上了老师的几节课后宝宝终于体会到了: 分析→设计→编码→优化 分析:分析该功能到底是怎样的,其实就是需求分析 ...

  2. 用 Flask 来写个轻博客 (18) — 使用工厂模式来生成应用对象

    Blog 项目源码:https://github.com/JmilkFan/JmilkFan-s-Blog 目录 目录 前文列表 工厂模式 使用工厂方法 Factory Method 创建 app 对 ...

  3. 如何消去delphi Stringgrid重绘时产生重影

    procedure TForm1.Stringgrid1DrawCell(Sender: TObject; ACol, ARow: Integer; Rect: TRect; State: TGrid ...

  4. Maven初了解

    这周开始,我正式上手了接口测试.我们接口测试使用的是Maven做项目管理,用Junit做测试框架.所以我稍微了解了一下Maven. 那么什么是Maven呢? Maven是基于项目对象模型(POM pr ...

  5. 斯坦福【概率与统计】课程笔记(四):EDA | 茎叶图

    茎叶图的只做方法如下: 将每个数字分成茎和叶 对所有茎排序,并纵向从小到大放置好 对相同茎下的叶归到一起并排序,垂直于茎的排列方向放置好 举个例子:我们有一份奥斯卡影后的年龄集合: 34 34 27 ...

  6. QTP加载第三方DLL(C#)实现清除IE缓存(转)

    由于QTP的默认编程语言是VBS, 而VBS是一种相对来说功能比较局限的脚本语言,因此我们在编写自动化测试脚本时会有很多功能无法很好的实现. 相对来说c#是一种高级编程语言, 可以实现大多数windo ...

  7. 绿盟扫出来个http host 漏洞

    这个漏洞搞了大半天,想过从后台拦截,也想过从前台拦截,都是无从下手!网上也找了很多资料,有点乱,后来自己结合网上的办法,搞出如下解决办法:在tomcat server.xml里配置host 因为外网是 ...

  8. python基础--快速排序

    1.快速排序 快速排序是一种交换排序. 快速排序由C. A. R. Hoare在1962年提出. 它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分:分割点左边都是比它小的数,右边都是比它大 ...

  9. android pull 解析器解析xml文档

    person.xml <?xml version="1.0" encoding="UTF-8"?> <persons> <pers ...

  10. c++网络库之 poco

    java 不好吗?java面向对象很好啊. poco 做的像 java 用起来更面向对象,这是优势.开发速度提升很多.boost 那种是给大牛看的.我觉得 poco 用起来方便,不清楚的地方随时看源码 ...