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. ceph安装问题

    ceph-deploy安装 Yum priorities pluginLoaded plugins: fastestmirror Loaded plugins: fastestmirror, prio ...

  2. (子文章)Spring Boot搭建两个微服务模块

    目录 1. 创建工程和user-service模块 1.1 创建空工程 1.2 在空工程里新建Module 2. 配置文件 2.1 pom.xml 2.2 application.yml 3. 代码 ...

  3. 爬虫(二)—— 请求库(二)selenium请求库

    目录 selenium请求库 一.什么是selenium 二.环境搭建 三.使用selenium模块 1.使用chrome并设置为无GUI模式 2.使用chrome有GUI模式 3.显示等待与隐式等待 ...

  4. 创建网关项目(Spring Cloud Gateway)

    创建网关项目 加入网关后微服务的架构图 创建项目 POM文件 <properties> <java.version>1.8</java.version> <s ...

  5. Neo4j百万级数据导入只需30s

    先上图:425万nodes.180万relationships只用了30s 243ms 项目需要生成关系图,开始考虑的是用Neo4j官网提供的REST API,从solr中查出2组数据先创建节点再创建 ...

  6. 模拟javaWeb责任链的设计

    这篇文章介绍了责任链模式的应用:本文介绍如果自己实现一个责任链 定义请求和响应信息 简单定义请求类Request(封装一个字符串) public class Request { String requ ...

  7. 四、spring的JDBC模板和事务管理

    Spring的JDBC模板 Spring是JavaEE开发的一站式框架,对各种持久化技术都提供了简单的模板 ORM持久化技术 模板类 JDBC org.springframework.jdbc.cor ...

  8. Web设计规范----控件、组件

    什么是控件?什么组件? 组件控件分类可以根据组件控件属性进行分类,也可以根据组件控件的功能进行划分.一般按功能划分,例如表单类就可以划分为:单文本输入,多文本输入.日历时间选择器.下拉选择列表.单选多 ...

  9. 关于uboot一些概念

    U-boot的环境变量值得注意的有两个: bootcmd 和bootargs. bootcmd 前面有说过bootcmd是自动启动时默认执行的一些命令,因此你可以在当前环境中定义各种不同配置,不同环境 ...

  10. go语言从例子开始之Example29.关闭通道

    关闭 一个通道意味着不能再向这个通道发送值了.这个特性可以用来给这个通道的接收方传达工作已经完成的信息. Example: package main import "fmt" // ...