【Flask/跟着学习】Flask大型教程项目#03:数据库

14 篇文章 3 订阅

跟着学习:http://www.pythondoc.com/flask-mega-tutorial/database.html
回顾上一章:https://blog.csdn.net/weixin_41263513/article/details/85007362

本章参考到的各种内容,感谢大大们的收集总结

常用的SQLalchemy 字段类型
SQLAlchemy查询过滤器以及查询执行方法

本章内容

  • Flask 中的数据库
  • 迁移
  • 配置
  • 数据库模型
  • 创建数据库
  • 迁移数据库
  • 数据库升级和回退
  • 数据库关系

这一章节值得注意的是,创建数据库,迁移数据库,数据库升级和回退小节所涉及的代码不需要特别弄懂,相当于我们自己写的api,在需要的时候复制去用就可以了(我看的也不是很懂hh)

Flask 中的数据库

我们将使用 Flask-SQLAlchemy 扩展来管理我们应用程序的数据。这个扩展封装了 SQLAlchemy 项目,这是一个 对象关系映射器 或者 ORM。

ORMs 允许数据库应用程序与对象一起工作,而不是表以及 SQL。执行在对象的操作会被 ORM 翻译成数据库命令。这就意味着我们将不需要学习 SQL,我们将让 Flask-SQLAlchemy 代替 SQL。

迁移

我见过的大多数数据库教程会涉及到创建和使用一个数据库,但没有充分讲述随着应用程序扩大更新数据库的问题。通常情况下,每次你需要进行更新,你最终不得不删除旧的数据库和创建一个新的数据库,并且失去了所有的数据。如果数据不能容易地被重新创建,你可能会被迫自己编写导出和导入脚本。

幸运地,我们还有一个更好的选择。

我们将使用 SQLAlchemy-migrate 来跟踪数据库的更新。它只是在开始建立数据库的时候多花费些工作,这只是很小的代价,以后就再不用担心人工数据迁移了。

配置

针对我们小型的应用,我们将采用 sqlite 数据库。sqlite 数据库是小型应用的最方便的选择,每一个数据库都是存储在单个文件里。

应用使用的数据库URL必须保存到Flask配置对象的SQLALCHEMY_DATABASE_URI键中。Flask-SQLAlchemy文档还建议把SQLALCHEMY_TRACK_MODIFICATIONS键设为False,以便在不需要跟踪对象变化时降低内存消耗,其他配置选项的作用参阅Flask-SQLAlchemy的文档

我们有许多新的配置项需要添加到配置文件中
文件:/config.py

import os
basedir = os.path.abspath(os.path.dirname(__file__))

class Config(object):
    SECRET_KEY = os.environ.get('SECRET_KEY') or 'you-will-never-guess'
    SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'app.db')
    SQLALCHEMY_TRACK_MODIFICATIONS = False

最后,当我们初始化应用程序的时候,我们也必须初始化数据库。这是我们更新后的初始化文件
文件:/app/–init–.py

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from config import Config

app = Flask(__name__)
app.config.from_object(Config)
db = SQLAlchemy(app)

from app import routes, models

注意我们在初始化脚本中的两个改变。创建了一个 db 对象,这是我们的数据库,通过db可以获得Flask-SQLAlchemy提供的所有功能,接着导入一个新的模块,叫做 models。接下来我们将编写这个模块

数据库模型

我们存储在数据库中数据将会以类的集合来表示,我们称之为数据库模型。ORM 层需要做的翻译就是将从这些类创建的对象映射到适合的数据库表的行。

让我们创建一个表示用户的模型。使用 WWW SQL Designer 工具,我制作如下的图来表示我们用户的表
在这里插入图片描述
id 字段通常会在所有模型中,并且用于作为主键。在数据库的每一个用户会被赋予一个不同的 id 值,存储在这个字段中。幸好这是自动完成的,我们仅仅需要的是提供 id 这个字段。

username 以及 email 字段是被定义成字符串,并且指定了最大的长度以便数据库可以优化空间占用。

字段是被作为 db.Column 类的实例创建的,db.Column 把字段的类型作为参数,并且还有一些其它可选的参数,比如表明字段是否唯一。

–repr-- 方法告诉 Python 如何打印这个类的对象。我们将用它来调试。

常用的SQLalchemy 字段类型

文件:/app/models.py

from app import db

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(64), index=True, unique=True)
    email = db.Column(db.String(120), index=True, unique=True)
    password = db.Column(db.String(128))

    def __repr__(self):
        return '<User {}>'.format(self.username)

创建数据库

配置以及模型都已经到位了,是时候准备创建数据库文件。SQLAlchemy-migrate 包自带命令行和 APIs,这些 APIs 以一种将来允许容易升级的方式来创建数据库。我发现命令行使用起来比较别扭,因此我们自己编写一些 Python 脚本来调用迁移的 APIs。

SQLALCHEMY_DATABASE_URI 是 Flask-SQLAlchemy 扩展需要的。这是我们数据库文件的路径。
SQLALCHEMY_MIGRATE_REPO 是文件夹,我们将会把 SQLAlchemy-migrate 数据文件存储在这里。
文件:/db_create.py

from migrate.versioning import api
from app import db
import os.path

basedir = os.path.abspath(os.path.dirname(__file__))
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'app.db')
SQLALCHEMY_MIGRATE_REPO = os.path.join(basedir, 'migrations')

db.create_all()
if not os.path.exists(SQLALCHEMY_MIGRATE_REPO):
    api.create(SQLALCHEMY_MIGRATE_REPO, 'database repository')
    api.version_control(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO)
else:
    api.version_control(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO, api.version(SQLALCHEMY_MIGRATE_REPO))

在运行上述命令之后你会发现一个新的 app.db 文件。这是一个空的 sqlite 数据库,创建一开始就支持迁移。同样你还将有一个 db_repository 文件夹,里面还有一些文件,这是 SQLAlchemy-migrate 存储它的数据文件的地方。请注意,我们不会再生的存储库,如果它已经存在。这将使我们重新创建数据库,同时保留现有的存储库,如果我们需要。
运行前,后:
在这里插入图片描述
在这里插入图片描述

迁移数据库

现在,我们已经定义了我们的模型,我们可以将其合并到我们的数据库中。我们会把应用程序数据库的结构任何的改变看做成一次迁移,因此这是我们第一次迁移,我们将从一个空数据库迁移到一个能存储用户的数据库上。

为了实现迁移,我们需要编写一小段 Python 代码
文件:/db_migrate.py

import imp
from migrate.versioning import api
from app import db
import os

basedir = os.path.abspath(os.path.dirname(__file__))
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'app.db')
SQLALCHEMY_MIGRATE_REPO = os.path.join(basedir, 'migrations')
v = api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO)

migration = SQLALCHEMY_MIGRATE_REPO + ('/versions/%03d_migration.py' % (v+1))
tmp_module = imp.new_module('old_model')
old_model = api.create_model(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO)
exec(old_model, tmp_module.__dict__)
script = api.make_update_script_for_model(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO, tmp_module.meta, db.metadata)
open(migration, "wt").write(script)
api.upgrade(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO)
v = api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO)
print('New migration saved as ' + migration)
print('Current database version: ' + str(v))

SQLAlchemy-migrate 迁移的方式就是比较数据库(在本例中从 app.db 中获取)与我们模型的结构(从文件 app/models.py 获取)。两者间的不同将会被记录成一个迁移脚本存放在迁移仓库中。迁移脚本知道如何去迁移或撤销它,所以它始终是可能用于升级或降级一个数据库。

然而在使用上面的脚本自动地完成迁移的时候也不是没有问题的,我见过有时候它很难识别新老格式的变化。为了让 SQLAlchemy-migrate 容易地识别出变化,我绝不会重命名存在的字段,我仅限于增加或者删除模型或者字段,或者改变已存在字段的类型。当然我一直会检查生成的迁移脚本,确保它是正确。

毋庸置疑你不应该在没有备份下去尝试迁移数据库。当然也不能在生产环境下直接运行迁移脚本,必须在开发环境下确保迁移运转正常。

因此让我们继续进行,记录下迁移,我运行了两次,以下是我第二次运行之后的结果:
在这里插入图片描述

数据库升级和回退

到现在你可能想知道为什么完成记录数据库迁移的这项令人麻烦的事情是这么重要。

假设你有一个应用程序在开发机器上,同时有一个拷贝部署在到线上的生产机器上。在下一个版本中,你的数据模型有一个变化,比如新增了一个表。如果没有迁移脚本,你可能必须要琢磨着如何修改数据库格式在开发和生产机器上,这会花费很大的工作。

如果有数据库迁移的支持,当你准备发布新版的时候,你只需要录制一个新的迁移,拷贝迁移脚本到生产服务器上接着运行脚本,所有事情就完成了。数据库升级也只需要一点 Python 脚本
文件:/db_upgrade.py

from migrate.versioning import api
import os

basedir = os.path.abspath(os.path.dirname(__file__))
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'app.db')
SQLALCHEMY_MIGRATE_REPO = os.path.join(basedir, 'migrations')
api.upgrade(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO)
v = api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO)
print('Current database version: ' + str(v))

当你运行上述脚本的时候,数据库将会升级到最新版本。
通常情况下,没有必要把数据库降低到旧版本,所以略过

数据库关系

关系型数据可以很好的存储数据项之间的关系。考虑一个用户写了一篇 blog 的例子。在 users 表中有一条用户的数据,在 posts 表中有一条 blog 数据。记录是谁写了这篇 blog 的最有效的方式就是连接这两条相关的数据项。

一旦在用户和文章(post)的联系被建立,有两种类型的查询是我们可能需要使用的。最常用的查询就是查询 blog 的作者。复杂一点的查询就是一个用户的所有的 blog。Flask-SQLAlchemy 将会帮助我们完成这两种查询。

让我们扩展数据库以便存储 blog。为此我们回到数据库设计工具并且创建一个 posts 表。
在这里插入图片描述
我们的 posts 表中有必须得 id 字段,以及 blog 的 body 以及一个 timestamp。这里没有多少新东西。只是对 user_id 字段需要解释下。

我们说过想要连接用户和他们写的 blog。方式就是通过在 posts 增加一个字段,这个字段包含了编写 blog 的用户的 id。这个 id 称为一个外键。我们的数据库设计工具把外键显示成一个连线,这根连线连接于 users 表中的 id 与 posts 表中的 user_id。这种关系称为一对多,一个用户编写多篇 blog。

我们添加了一个 Post 类,这是用来表示用户编写的 blog。在 Post 类中的 user_id 字段初始化成外键,因此 Flask-SQLAlchemy 知道这个字段是连接到用户上。

值得注意的是我们已经在 User 类中添加一个新的字段称为 posts,它是被构建成一个 db.relationship 字段。这并不是一个实际的数据库字段,因此是不会出现在上面的图中。对于一个一对多的关系,db.relationship 字段通常是定义在“一”这一边。在这种关系下,我们得到一个 user.posts 成员,它给出一个用户所有的 blog。不用担心很多细节不知道什么意思,以后我们会不断地看到例子。

from datetime import datetime
from app import db

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(64), index=True, unique=True)
    email = db.Column(db.String(120), index=True, unique=True)
    password = db.Column(db.String(128))
    posts = db.relationship('Post', backref='author', lazy='dynamic')

    def __repr__(self):
        return '<User {}>'.format(self.username)

class Post(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    body = db.Column(db.String(140))
    timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'))

    def __repr__(self):
        return '<Post {}>'.format(self.body)

这章节花了很大的笔墨在数据库上,给我们的反馈并不是很多,但却是必要的知识,最后贴以下常用的SQLAlchemy查询过滤器以及查询执行方法,虽然以上的创建数据库,迁移数据库,数据库升级和回退内容不需要我们弄懂,但也必须要会用!

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值