Study_microblog笔记Part 4--数据库

大多数应用都需要持久化存储数据,并高效地执行的增删查改的操作,数据库为此而生。本应用使用轻量级SQLite数据库,以文件的形式存储。
Flask框架允许通过插件的形式自由选择数据库,如mysql,sqlite,nosql等等,这里介绍SQLALchemy,是Flask的扩展,通过ORM映射管理数据库。

一、虚拟环境下安装SQLALchemy

(microblog) D:\pythonProgram\PycharmProjects\microblog>pip install flask-sqlalchemy
Collecting flask-sqlalchemy
  Using cached Flask_SQLAlchemy-2.5.1-py2.py3-none-any.whl (17 kB)
Collecting SQLAlchemy>=0.8.0
  Downloading SQLAlchemy-1.4.17-cp38-cp38-win_amd64.whl (1.5 MB)
     |████████████████████████████████| 1.5 MB 24 kB/s
Requirement already satisfied: Flask>=0.10 in d:\pycharmprojects\flask\microblog\lib\site-packages (from flask-sqlalchemy) (2.0.1)
Requirement already satisfied: Jinja2>=3.0 in d:\pycharmprojects\flask\microblog\lib\site-packages (from Flask>=0.10->flask-sqlalchemy) (3.0.1)
Requirement already satisfied: Werkzeug>=2.0 in d:\pycharmprojects\flask\microblog\lib\site-packages (from Flask>=0.10->flask-sqlalchemy) (2.0.1)
Requirement already satisfied: click>=7.1.2 in d:\pycharmprojects\flask\microblog\lib\site-packages (from Flask>=0.10->flask-sqlalchemy) (8.0.1)
Requirement already satisfied: itsdangerous>=2.0 in d:\pycharmprojects\flask\microblog\lib\site-packages (from Flask>=0.10->flask-sqlalchemy) (2.0.1)
Requirement already satisfied: colorama in d:\pycharmprojects\flask\microblog\lib\site-packages (from click>=7.1.2->Flask>=0.10->flask-sqlalchemy) (0.4.4)
Requirement already satisfied: MarkupSafe>=2.0 in d:\pycharmprojects\flask\microblog\lib\site-packages (from Jinja2>=3.0->Flask>=0.10->flask-sqlalchemy) (2.0.1)
Collecting greenlet!=0.4.17
  Downloading greenlet-1.1.0-cp38-cp38-win_amd64.whl (96 kB)
     |████████████████████████████████| 96 kB 66 kB/s
Installing collected packages: greenlet, SQLAlchemy, flask-sqlalchemy
Successfully installed SQLAlchemy-1.4.17 flask-sqlalchemy-2.5.1 greenlet-1.1.0

在配置文件中添加配置项

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

class Config(object):
    # ...
    SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \
        'sqlite:///' + os.path.join(basedir, 'app.db')
    SQLALCHEMY_TRACK_MODIFICATIONS = False

Flask-SQLAlchemy插件从SQLALCHEMY_DATABASE_URI配置变量中获取应用的数据库的位置。 首先从环境变量获取配置变量,未获取到就使用默认值。 本处从DATABASE_URL环境变量中获取数据库URL,如果没有定义,将其配置为basedir变量表示的应用顶级目录下的一个名为app.db的文件路径。
SQLALCHEMY_TRACK_MODIFICATIONS配置项用于设置数据发生变更之后是否发送信号给应用,如不需要这项功能,将其设置为False。

数据库在应用的表现形式是一个数据库实例,将会在应用实例化之后进行实例化和注册操作。app/init.py文件变更如下:

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

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

from app import routes, models

底部导入了一个名为models的模块,这个模块将会用来定义数据库结构。

二、数据库模型

定义数据库中一张表及其字段的类,通常叫做数据模型。ORM(SQLAlchemy)会将类的实例关联到数据库表中的数据行,并翻译相关操作。
利用 WWW SQL Designer工具,我画了一张图来设计用户表的各个字段。
在这里插入图片描述
username,email和password_hash字段被定义为字符串(数据库术语中的VARCHAR),并指定其最大长度,以便数据库可以优化空间使用率。 username和email字段的用途不言而喻,password_hash字段值得提一下。 我想确保我正在构建的应用采用安全最佳实践,因此我不会将用户密码明文存储在数据库中。 明文存储密码的问题是,如果数据库被攻破,攻击者就会获得密码,这对用户隐私来说可能是毁灭性的。 如果使用哈希密码,这就大大提高了安全性。
用户表构思完毕之后,用代码实现,并存储到新建的模块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_hash = db.Column(db.String(128))

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

这个类将表的字段定义为类属性,字段被创建为db.Column类的实例,它传入字段类型以及其他可选参数,例如,可选参数中允许指示哪些字段是唯一的并且是可索引的,这对高效的数据检索十分重要。
该类的__repr__方法用于在调试时打印用户实例。在下面的Python交互式会话中你可以看到__repr__()方法的运行情况:

>>> from app.models import User
>>> u = User(username='susan', email='susan@example.com')
>>> u
<User susan>

三、数据库迁移

应用Flask-migrate扩展,这个插件是Alembic的一个Flask封装,是SQLAlchemy的一个数据库迁移框架。安装Flask-Migrate和安装你见过的其他插件的方式一样:

(microblog) D:\pythonProgram\PycharmProjects\microblog>pip install flask-migrate
Collecting flask-migrate
  Downloading Flask_Migrate-3.0.1-py2.py3-none-any.whl (12 kB)
Collecting alembic>=0.7
  Downloading alembic-1.6.5-py2.py3-none-any.whl (164 kB)
     |████████████████████████████████| 164 kB 327 kB/s
Requirement already satisfied: Flask-SQLAlchemy>=1.0 in d:\pycharmprojects\flask\microblog\lib\site-packages (from flask-migrate) (2.5.1)
Requirement already satisfied: Flask>=0.9 in d:\pycharmprojects\flask\microblog\lib\site-packages (from flask-migrate) (2.0.1)
Collecting python-editor>=0.3
  Using cached python_editor-1.0.4-py3-none-any.whl (4.9 kB)
Collecting Mako
  Using cached Mako-1.1.4-py2.py3-none-any.whl (75 kB)
Collecting python-dateutil
  Using cached python_dateutil-2.8.1-py2.py3-none-any.whl (227 kB)
Requirement already satisfied: SQLAlchemy>=1.3.0 in d:\pycharmprojects\flask\microblog\lib\site-packages (from alembic>=0.7->flask-migrate) (1.4.17)
Requirement already satisfied: click>=7.1.2 in d:\pycharmprojects\flask\microblog\lib\site-packages (from Flask>=0.9->flask-migrate) (8.0.1)
Requirement already satisfied: Werkzeug>=2.0 in d:\pycharmprojects\flask\microblog\lib\site-packages (from Flask>=0.9->flask-migrate) (2.0.1)
Requirement already satisfied: Jinja2>=3.0 in d:\pycharmprojects\flask\microblog\lib\site-packages (from Flask>=0.9->flask-migrate) (3.0.1)
Requirement already satisfied: itsdangerous>=2.0 in d:\pycharmprojects\flask\microblog\lib\site-packages (from Flask>=0.9->flask-migrate) (2.0.1)
Requirement already satisfied: colorama in d:\pycharmprojects\flask\microblog\lib\site-packages (from click>=7.1.2->Flask>=0.9->flask-migrate) (0.4.4)
Requirement already satisfied: MarkupSafe>=2.0 in d:\pycharmprojects\flask\microblog\lib\site-packages (from Jinja2>=3.0->Flask>=0.9->flask-migrate) (2.0.1)
Requirement already satisfied: greenlet!=0.4.17 in d:\pycharmprojects\flask\microblog\lib\site-packages (from SQLAlchemy>=1.3.0->alembic>=0.7->flask-migrate) (1.1.
0)
Collecting six>=1.5
  Downloading six-1.16.0-py2.py3-none-any.whl (11 kB)
Installing collected packages: six, python-editor, python-dateutil, Mako, alembic, flask-migrate
Successfully installed Mako-1.1.4 alembic-1.6.5 flask-migrate-3.0.1 python-dateutil-2.8.1 python-editor-1.0.4 six-1.16.0

数据库迁移引擎在应用的表现形式和数据库一样是一个数据库实例,在应用实例化之后进行实例化和注册操作。app/init.py文件变更如下:

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

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

from app import routes, models

创建数据库迁移存储, Flask-Migrate添加了flask db子命令来管理与数据库迁移相关的所有事情。 那么让我们通过运行flask db init来创建microblog的迁移存储库:

(microblog) D:\pythonProgram\PycharmProjects\microblog>flask db init
Creating directory D:\pythonProgram\PycharmProjects\microblog\migrations ...  done
Creating directory D:\pythonProgram\PycharmProjects\microblog\migrations\versions ...  done
Generating D:\pythonProgram\PycharmProjects\microblog\migrations\alembic.ini ...  done
Generating D:\pythonProgram\PycharmProjects\microblog\migrations\env.py ...  done
Generating D:\pythonProgram\PycharmProjects\microblog\migrations\README ...  done
Generating D:\pythonProgram\PycharmProjects\microblog\migrations\script.py.mako ...  done
Please edit configuration/connection/logging settings in 'D:\\pythonProgram\\PycharmProjects\\microblog\\migrations\\alembic.ini' before proceeding.

运行迁移初始化命令之后,你会发现一个名为migrations的新目录。该目录中包含一个名为versions的子目录以及若干文件。从现在起,这些文件就是你项目的一部分了,应该添加到代码版本管理中去。

第一次数据库迁移

包含映射到User数据库模型的用户表的迁移存储库生成后,是时候创建第一次数据库迁移了。 有两种方法来创建数据库迁移:手动或自动。 要自动生成迁移,Alembic会将数据库模型定义的数据库模式与数据库中当前使用的实际数据库模式进行比较。 然后,使用必要的更改来填充迁移脚本,以使数据库模式与应用程序模型匹配。 当前情况是,由于之前没有数据库,自动迁移将把整个User模型添加到迁移脚本中。 flask db migrate子命令生成这些自动迁移:

(microblog) D:\pythonProgram\PycharmProjects\microblog>flask db migrate -m "users table"
INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.autogenerate.compare] Detected added table 'user'
INFO  [alembic.autogenerate.compare] Detected added index 'ix_user_email' on '['email']'
INFO  [alembic.autogenerate.compare] Detected added index 'ix_user_username' on '['username']'
Generating D:\pythonProgram\PycharmProjects\microblog\migrations\versions\a1667b833050_users_table.py ...  done

flask db migrate命令不会对数据库进行任何更改,只会生成迁移脚本。 要将更改应用到数据库,必须使用flask db upgrade命令。

(microblog) D:\pythonProgram\PycharmProjects\microblog>flask db upgrade
INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.runtime.migration] Running upgrade  -> a1667b833050, users table

因为本应用使用SQLite,所以upgrade命令检测到数据库不存在时,会创建它(在这个命令完成之后,你会注意到一个名为app.db的文件,即SQLite数据库)。 在使用类似MySQL和PostgreSQL的数据库服务时,必须在运行upgrade之前在数据库服务器上创建数据库。

四、数据库关系

用户发表动态的情况, 用户将在user表中有一个记录,并且这条用户动态将在post表中有一个记录。 标记谁写了一个给定的动态的最有效的方法是链接两个相关的记录。
一旦建立了用户和动态之间的关系,数据库就可以在查询中展示它。最小的例子就是当你看一条用户动态的时候需要知道是谁写的。一个更复杂的查询是, 如果你好奇一个用户时,你可能想知道这个用户写的所有动态。 Flask-SQLAlchemy有助于实现这两种查询。
让我们扩展数据库来存储用户动态,以查看实际中的关系。 这是一个新表post的设计:
在这里插入图片描述
post表将具有必须的id、用户动态的body和timestamp字段。 除了这些预期的字段之外,还添加了一个user_id字段,将该用户动态链接到其作者。 你已经看到所有用户都有一个唯一的id主键, 将用户动态链接到其作者的方法是添加对用户id的引用,这正是user_id字段所在的位置。 这个user_id字段被称为外键。 上面的数据库图显示了外键作为该字段和它引用的表的id字段之间的链接。 这种关系被称为一对多,因为“一个”用户写了“多”条动态。
修改后的app/models.py如下:

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_hash = 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)

新的“Post”类表示用户发表的动态。 timestamp字段将被编入索引,如果你想按时间顺序检索用户动态,这将非常有用。 我还为其添加了一个default参数,并传入了datetime.utcnow函数。 当你将一个函数作为默认值传入后,SQLAlchemy会将该字段设置为调用该函数的值(请注意,在utcnow之后我没有包含(),所以我传递函数本身,而不是调用它的结果)。 通常,在服务应用中使用UTC日期和时间是推荐做法。 这可以确保你使用统一的时间戳,无论用户位于何处,这些时间戳会在显示时转换为用户的当地时间。
user_id字段被初始化为user.id的外键,这意味着它引用了来自用户表的id值。本处的user是数据库表的名称,Flask-SQLAlchemy自动设置类名为小写来作为对应表的名称。 User类有一个新的posts字段,用db.relationship初始化。这不是实际的数据库字段,而是用户和其动态之间关系的高级视图,因此它不在数据库图表中。对于一对多关系,db.relationship字段通常在“一”的这边定义,并用作访问“多”的便捷方式。因此,如果我有一个用户实例u,表达式u.posts将运行一个数据库查询,返回该用户发表过的所有动态。 db.relationship的第一个参数表示代表关系“多”的类。 backref参数定义了代表“多”的类的实例反向调用“一”的时候的属性名称。这将会为用户动态添加一个属性post.author,调用它将返回给该用户动态的用户实例。 lazy参数定义了这种关系调用的数据库查询是如何执行的,这个我会在后面讨论。不要觉得这些细节没什么意思,本章的结尾将会给出对应的例子。
一旦我变更了应用模型,就需要生成一个新的数据库迁移:

(microblog) D:\pythonProgram\PycharmProjects\microblog>flask db migrate -m "posts tables"
INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.autogenerate.compare] Detected added table 'post'
INFO  [alembic.autogenerate.compare] Detected added index 'ix_post_timestamp' on '['timestamp']'
Generating D:\pythonProgram\PycharmProjects\microblog\migrations\versions\39222d6aeb97_posts_tables.py ...  done

应用到数据库

(microblog) D:\pythonProgram\PycharmProjects\microblog>flask db upgrade
INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.runtime.migration] Running upgrade a1667b833050 -> 39222d6aeb97, posts tables

如果你对项目使用了版本控制,记得将新的迁移脚本添加进去并提交。

五、运用数据库模型及相互关系

经历了一个漫长的过程来定义数据库,我却还没向你展示它们如何使用。 由于应用还没有任何数据库逻辑,所以让我们在Python解释器中来使用以便熟悉它。
进入Python交互式环境后,导入数据库实例和模型:

>>> from app import db
>>> from app.models import User, Post

开始阶段,创建一个新用户:

>>> u = User(username='john', email='john@example.com')
>>> db.session.add(u)
>>> db.session.commit()

添加另一个用户:

>>> u = User(username='susan', email='susan@example.com')
>>> db.session.add(u)
>>> db.session.commit()

这样处理时,会提示sqlite错误。在config中更改如下:

class Config(object): 
    #------#
    SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \
        'sqlite:///'+os.path.join(basedir, 'app.db') + '?check_same_thread=False'
    SQLALCHEMY_TRACK_MODIFICATIONS = False
   #------#

数据库执行返回所有用户的查询:

User.query.order_by(User.username.desc()).all()
[<User susan>, <User hanson>, <User charlie>]
users=User.query.all()

添加一条用户动态:

u=User.query.get(2)
p=Post(body='my first post!',author=u)
db.session.add(p)
db.session.commit()

为了完成演示,让我们看看另外的数据库查询案例:

u=User.query.all()
u
[<User susan>, <User charlie>, <User hanson>]
users=User.query.all()
users
[<User susan>, <User charlie>, <User hanson>]
u=User.query.get(4)
u
<User hanson>
posts=u.posts.all()
posts
[<Post This post from No.4>, <Post The second  from No.4>, <Post The third  from No.4>, <Post The third  from No.4>, <Post The third  from No.3>]
u=User.query.get(2)
u
<User susan>
u.posts.all()
[<Post my first post!>]
posts=Post.query.all()
posts
[<Post my first post!>, <Post This is another post!>, <Post This post from No.4>, <Post The second  from No.4>, <Post The third  from No.4>, <Post The third  from No.4>, <Post The third  from No.3>]

学完本节内容,我们需要清除这些测试用户和用户动态,以便保持数据整洁和为下一章做好准备:

users=User.query.all()
posts=Post.query.all()
for user in users:
...     db.session.delete(user)
...     
for post in posts:
...     db.session.delete(post)
...     
db.session.commit()

六、Shell上下文

还记得上一节的启动Python解释器之后你做过什么吗?第一件事是运行两条导入语句:

>>> from app import db
>>> from app.models import User, Post

开发应用时,你经常会在Python shell中测试,所以每次重复上面的导入都会变得枯燥乏味。 flask shell命令是flask命令集中的另一个非常有用的工具。 这个命令的目的是在应用的上下文中启动一个Python解释器。 这意味着什么? 看下面的例子:

(venv) $ python
>>> app
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'app' is not defined
>>>

(venv) $ flask shell
>>> app
<Flask 'app'>

flask shell的绝妙之处不在于它预先导入了app,而是你可以配置一个“shell上下文”,也就是可以预先导入一份对象列表。
在microblog.py中实现一个函数,它通过添加数据库实例和模型来创建了一个shell上下文环境:

from app import app, db
from app.models import User, Post

@app.shell_context_processor
def make_shell_context():
    return {'db': db, 'User': User, 'Post': Post}

在添加shell上下文处理器函数后,你无需导入就可以使用数据库实例:

(microblog) D:\pythonProgram\PycharmProjects\microblog>flask shell
Python 3.8.5 (tags/v3.8.5:580fbb0, Jul 20 2020, 15:57:54) [MSC v.1924 64 bit (AMD64)] on win32
App: app [production]
Instance: D:\pythonProgram\PycharmProjects\microblog\instance
>>> db
<SQLAlchemy engine=sqlite:///D:\pythonProgram\PycharmProjects\microblog\app.db?check_same_thread=False>
>>> User
<class 'app.models.User'>
>>> Post
<class 'app.models.Post'>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值