[译]Flask Framework Cookbook-第三章 Flask中的数据模型

本文介绍了如何使用Flask框架连接并操作SQL及NoSQL数据库,包括定义模型、查询数据等核心功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这一章将会覆盖任何应用中最重要的部分:和数据库的交互。本章中将介绍如何用Flask连接数据库系统,定义模型,查询数据。

本章将包含下面小节:

  • 创建一个SQLAlchemy DB实例
  • 创建一个基本的商品模型
  • 创建一个关系类别模型
  • 使用Alembic和Flask-Migrate实现数据库迁移(migration)
  • 用Redis建立模型数据索引
  • 使用非关系型数据库MongoDB

介绍

Flask被设计的足够灵活来支持任何数据库。最简单的方式是直接使用sqlite3,sqlite3它提供了DB-API2.0接口,不提供ORM。sqlite3使用SQL语句和数据库对话。这种方法不推荐用来构建大型应用,因为最终维护应用会变成一个噩梦。同样,用这种方法实际上是不存在模型的,所有的事情在视图函数中进行,比如在视图函数中编写查询语句去和数据库交互。

本章我们将使用广泛使用的SQLAlchemy为Flask应用创建一个ORM层。同时学习如何编写一个使用NoSQL数据库的Flask应用。

提示

ORM的指的是对象关系映射(Object Relation Mapping/Modeling),抽象的表明了我们的应用数据如何存储,如何处理。强大的ORM使得设计和查询业务逻辑非常简单和简洁。

创建一个SQLAlchemy DB实例

SQLAlchemy是一个Python SQL工具集,它提供了一个ORM,可以灵活高效的处理SQL,并且通过它能够感受到Python的面向对象特性。

准备

Flask-SQLAlchemy是一个扩展,为Flask提供了SQLAlchemy接口。
安装Flask-SQLAlchemy:

$ pip install flask-sqlalchemy

使用Flask-SQLAlchemy首先要做的是设置应用配置参数,告诉SQLAlchemy数据库的位置:

app.config['SQLALCHENY_DATABASE_URI'] = os.environ('DATABASE_URI')

SQLALCHEMY_DATABASE_URI是数据库协议的组合,需要认证,需要数据库的名字。用SQLite举例,它看起来像这样:

sqlite:////tmp/test.db

用PostgreSQL举例,看起来像这样:

postgresql://yourusername:yourpassword@localhost/yournewdb.

这个扩展提供了一个叫做Model的类,它用来为我们的应用定义模型。了解更多数据库URLS参见
http://docs.sqlalchemy.org/en/rel_0_9/core/engines.html#database-urls

提示

除了SQLite,都需要安装独立的数据库。比如,如果需要使用PostgreSQL,需要安装psycopg2.

怎么做

用一个小应用进行演示。下面的小节也一直会使用这个应用。这里,我们将会看到如何创建一个db实例,和一些基本的DB命令。文件结构看起来像这样:

flask_catalog/
    - run.py
    my_app/
        - __init__.py

首先从flask_app/run.py开始,这已经在书里见到很多次了:

from my_app import app
app.run(debug=True)

然后是应用配置文件,flask_app/my_app/__init__.py:

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.sqlite'
db = SQLAlchemy(app)
译者注

原书为from flask.ext.sqlalchemy import SQLAlchemy,现已不建议这么使用。
原书为sqlite:////tmp/test.db,改为sqlite:////tmp/test.sqlite。

我们配置SQLALCHEMY_DATABASE_URI为一个特定的路径。然后我们创建了一个SQLAlchemy对象叫做db。从名字可以看出,这个对象将会处理所有和ORM相关的活动。之前提到过,db对象有一个名为Model的类,它提供了在Flask创建模型的基础。任何类都可以继承Model去创建模型,模型将作为数据库表。

现在如果在浏览器打开http://127.0.0.1:5000,我们看不到任何东西。因为应用里就没有东西。

更多

有时你可能需要一个单独的SQLAlchemy db实例可以被多个应用使用或者动态的创建应用。在这些情况下,我们不会讲一个db实例绑定在一个单独的应用上。这里我们必须和应用上下文一起工作以达到预期的结果。
这种情况下,使用SQLAlchemy注册方式将有所不同:

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

def create_app():
    app = Flask(__name__)
    db.init_app(app)
    return app
提示

上面的方法也可以用来初始化其他Flask扩展,而且这在实际应用中是很通常的做法。

现在,所有使用全局db实例的操作都需要一个Flask上下文了:

Flask application context
>>> from my_app import create_app
>>> app = create_app()
>>> app.test_request_context().push()
>>> # Do whatever needs to be done
>>> app.test_request_context().pop()
Or we can use context manager
with app():
    # We have flask application context now till we are inside the with block
其他
  • 下面章节将扩展当前的应用为一个完整的应用,以帮助我们更好的理解ORM

创建一个基本的商品模型

这一小节,我们将创建一个应用帮助我们在网站目录中显示商品。它也可以用来向目录中添加商品也可以根据需要删除他们。从前面章节可以看到,也可以使用非持久化的存储,但是现在我们将数据存储在数据库里做持久化存储。

怎么做

文件夹目录看起来像这样:

flask_catalog/
    - run.py
    my_app/
        – __init__.py
        catalog/
            - __init__.py
            - views.py
            - models.py

首先,修改应用配置文件,flask_catalog/my_app/__init__.py

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.sqlite'
db = SQLAlchemy(app)

from my_app.catalog.views import catalog
app.register_blueprint(catalog)

db.create_all()

最后一句db.create_all(),告诉应用在特定的数据库里创建所有的表。所以,当应用运行的时候,如果表不存在的话,所有的表将会创建。现在是时候去在flask_catalog/my_app/catalog/models.py中创建模型了:

from my_app import db

class Product(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(255))
    price = db.Column(db.Float)

    def __init__(self, name, price):
        self.name = name
        self.price = price

    def __repr__(self):
        return '<Product %d>' % self.id 

这个文件中,我们创建了一个叫做Product的模型,它有三个字段,id,name,price。id是一个自增长的字段,它会存储记录的ID并且做为主键。name是一个字符串类型的字段,price是浮点类型的。

现在为视图添加一个新的文件,flask_catalog/my_app/catalog/views.py。在这个文件里我们有许多视图方法来处理商品模型和应用:

from flask import request, jsonify, Blueprint
from my_app import app, db
from my_app.catalog.models import Product

catalog = Blueprint('catalog', __name__)

@catalog.route('/')
@catalog.route('/home')
def home():
    return "Welocme to the Catolog Home."

这个方法处理了主页看起来像什么样子。大多数情况下会在应用里使用模板进行渲染。我们稍后会继续讨论这个问题,现在看下下面的代码:

@catalog.route('/product/<id>')
def product(id):
    product = Product.query.get_or_404(id)
    return 'Product - %s, $%s' % (product.name, product.price)

这个方法控制了当用户用商品特定ID进行搜索时的输出。我们用ID过滤商品,当商品被找到的时候返回它的信息;如果没有找到,产生一个404错误。看下面的代码:

@catalog.route('/products')
def products():
    products = Product.query.all()
    res = {}
    for product in products:
        res[product.id] = {
            'name': product.name,
            'price': str(product.price)
    }
    return jsonify(res)

这个方法以JSON格式返回了所有商品信息。看下面代码:

@catalog.route('/product-create', methods=['POST',])
def create_product():
    name = request.form.get('name')
    price = request.form.get('price')
    product = Product(name, price)
    db.session.add(product)
    db.session.commit()
    return "Product created"

这个方法控制了数据库中商品的创建。我们首先从request请求中获取信息,然后用这些信息创建一个Product实例。然后将这个Product实例添加到数据库会话中(session),然后提交保存这条记录到数据库。

原理

首先,数据库是空的没有任何商品。这可以通过在浏览器打开http://127.0.0.1:5000/products进行确认。页面上仅仅会显示一个{}。
现在,要去创建一个商品。为此我们需要发送一个POST请求,POST请求可以很容易的通过使用Python request库实现:

>>> import requests
>>> requests.post('http://127.0.0.1:5000/product-create', data={'name': 'iPhone 5S', 'price': '549.0'})

想要确认商品是否在数据库里了,可以在浏览器里再一次输入http://127.0.0.1:5000/products。这次,它会以JSON的形式输出商品信息。

其他
  • 在下一小节,创建一个关系类别模型,中将会演示表之间的关系

创建一个关系类别模型

前一小节,我们创建了一个简单的商品模型。但是,在实际情况下,应用会更加复杂,各个表之间有各种各样的关系(relationships)。这些关系可以是一对一的,一对多的,多对一的,或者是多对多的。这一小节,我们将用例子去理解他们中的一些。

怎么做

假设我们每个商品类别可以有多个商品,但是每个商品至少有一个类别。让我们修改之前的一些代码,同时对模型和视图做出修改。在模型中我们增加了一个Category模型,在视图中,我们增加了新的方法去处理类别相关的调用。

首先修改models.py,增加Category模型,并且修改Product模型:

from my_app import db

class Product(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(255))
    price = db.Column(db.Float)
    category_id = db.Column(db.Integer, db.ForeignKey('category.id'))
    category = db.relationship('Category', backref=db.backref('products', lazy='dynamic'))

    def __init__(self, name, price, category):
        self.name = name
        self.price = price
        self.category = category

    def __repr__(self):
        return '<Product %d>' % self.id

在前面的Product模型中,注意新增加的两个字段category_id和category。category_id是Category模型的外键,category代表关系表。从他们的定义可以看出一个是关系,另一个使用这个关系在数据库中存储外键值。这是一个从商品到类别的简单多对一的关系。同时,注意category字段中的backref参数;这个参数允许我们可以在视图里编写category.prodycts从Category模型获取商品。从另一端看这一个一对多的关系。考虑下面代码:

calss Category(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(100))

    def __init__(self, name):
        self.name = name

    def __repr__(self):
        return '<Category %d>' % self.id

前面的代码是Category模型,它仅仅只有一个name字段。
现在修改views.py来适应模型的改变:

from my_app.catalog.models import Product, Category

@catalog.route('/products')
def products():
    products = Product.query.all()
    res = {}
    for product in products:
        res[product.id] = {
            'name': product.name,
            'price': product.price,
            'category': product.category.name
        }
    return jsonify(res)

这里,我们只做了一个修改,在返回商品JSON信息的时候添加了category信息。看下面的代码:

@catalog.route('/product-create', methods=['POST',])
def create_product():
    name = request.form.get('name')
    price = request.form.get('price')
    categ_name = request.form.get('category')
    category = Category.query.filter_by(name=categ_name).first()
    if not category:
        category = Category(categ_name)
    product = Product(name, price, category)
    db.session.add(product)
    db.session.commit()
    return "Product created."

看一下是如何在创建商品之前查找类别的。首先,使用请求中的类别名在已经存在的类别里进行搜索。如果找到了,就使用它进行商品的创建。否则,就创建一个新的类别。看下面的代码:

@catalog.route('/category-create', methods=['POST',])
def create_category():
    name = request.form.get('name')
    category = Category(name)
    db.session.add(category)
    db.session.commit()
    return 'Category created.'

前面的是一个非常简单的使用请求里的name来创建类别的方法。看下面的代码:

@catalog.route('/categories')
def categories():
    categories = Category.query.all()
    res = {}
    for category in categoires:
        res[category.id] = {
            'name': category.name
        }
        for product in category.products:

            res[category.id]['products'] = {
                'id': product.id,
                'name': product.name,
                'price': product.price
            }
    return jsonify(res)

上面代码有点复杂。首先从数据库里获取到所有的类别信息,然后遍历每个类别,获取所有的商品信息,然后用JSON的形式返回。

译者注

上面代码是存在问题的,它的原意是想列出每个类别下所有的商品,但是结果只能列出一个。可以改为:

@catalog.route('/categories')
def categories():
    categories = Category.query.all()
    res = {}
    for category in categories:
        res[category.id] = {
            'name': category.name
        }
        res[category.id]['products'] = []
        for product in category.products:
            p = {
                'id': product.id,
                'name': product.name,
                'price': product.price
            }
            res[category.id]['products'].append(p)
    return jsonify(res)

使用Alembic和Flask-Migrate进行数据库迁移(migration)

现在我们想要在Product模型中添加一个新的叫做company的字段。一种方式是去通过使用db.drop_all()和db.create_all()删除数据库然后新建一个。但是,这种方法不能用于生产中。我们希望迁移我们的数据库到最新的模型,并保持所有数据完整。
为此,我们使用Alembic,这是一个基于Python的工具来管理数据库迁移和使用SQLAlchemy作为底层引擎。Alembic在很大程度上提供了自动迁移,但有一些限制(当然,我们不能期望任何工具是完美的)。我们使用一个叫做Flask-Migrate的扩展来简化迁移的过程。

准备

先安装Flask-Migrate

$ pip install Flask-Migrate

这个将会安装Flask-Script和Alembic还有其他一些依赖。Flask-Script使得Flask-Migrate提供一些简单使用的命令行参数,这些参数对用户而言是一个高级别的抽象,并且隐藏了所有复杂的特性(如果需要的话事实上也不是很难的去自定义)。

怎么做

为了能够迁移,需要稍微去修改一下app定义,my_app/__init__.py看起来像这样:

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_script import Manager
from flask_migrate import Migrate, MigrateCommand

app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = 'sqlite:////tmp/test.sqlite'
db = SQLAlchemy(app)
migrate = Migrate(app, db)

manager = Manager(app)
manager.add_command('db', MigrateCommand)

# import my_app.catalog.views
from my_app.catalog.views import catalog
app.register_blueprint(catalog)

db.create_all()

同时,需要对run.py做些小改动:

from my_app import manager
manager.run()

run.py的改动是因为我们需要使用Flask script manager的方式去启动应用。script manager同样提供了额外的命令行参数。在这个例子中我们将使用db做为命令行参数。
如果我们把run.py当做脚本运行时,给它传递–help参数,终端这时候将会展示所有的选项,看起来像下面这样:

现在,运行这个应用,可以使用:

$ python run.py runserver

初始化迁移,需要运行init命令:

$ python run.py db init

之后当我们对模型做了更改,需要运行migrate命令:

$ python run.py db migrate

为了将更改反映到数据库上,需要运行upgrade命令:

$ python run.py db upgrade
原理

现在,修改商品模型,添加一个新的字段company:

class Product(db.Model):
    # ...
    # Same product model as last recipe
    # ...
    company = db.Column(db.String(100))

migrate的结果看起来像这样:

$ python run.py db migrate
INFO [alembic.migration] Context impl SQLiteImpl.
INFO [alembic.migration] Will assume non-transactional DDL.
INFO [alembic.autogenerate.compare] Detected added column 
    'product.company' Generating <path/to/application>/
    flask_catalog/migrations/versions/2c08f71f9253_.py ... done

前面的代码,我们看到Alembic将新的模型和数据库进行比较,然后检测到product新增了company一列(由Product模型创建)。

相似的,upgrade的的输出将看起来像这样:

$ python run.py db upgrade
INFO [alembic.migration] Context impl SQLiteImpl.
INFO [alembic.migration] Will assume non-transactional DDL.
INFO [alembic.migration] Running upgrade None -> 2c08f71f9253, empty message    

这里,Alembic用之前检测到的迁移来升级数据库。可以看到输出中有一个16进制数。这代表了执行迁移的版本。
Alembic内部使用它来追踪数据库表的更改。

用Redis建立模型数据索引

也许有些特性要去实现,但是不想对他们做持久化存储。所以,我们可以将他们存储在内存里保持一段时间,然后隐藏他们,举个例子,在网站上向访问者展示访问过的商品列表。

准备

我们将使用Redis来做到这些,使用下面命令安装Redis:

$ pip install redis

确保你的Redis服务器在运行,以便链接。安装和运行Redis服务器,参见:http://redis.io/topics/quickstart

然后我们需要和Redis连接。在my_app/__init__.py中添加下面代码可以做到这些:

from redis import Redis
redis = Redis()

我们可以在应用中需要用到Redis的地方构造redis,比如定义app的地方,或者在视图文件里。最好是在应用
文件中做,因为这样连接将在整个应用中保持打开,仅仅通过导入redis就能够在任何需要的地方使用,。

怎么做

我们将在Redis中设置一个集合来存储最近浏览过的商品。当我们浏览商品的时候会填充它。该记录将在10分钟后过期。对views.py做如下修改:

from my_app import redis

@catalog.route('/product/<id>')
def product(id):
    product = Product.query.get_or_404(id)
    product_key = 'product-%s' % product.id
    redis.set(product_key, product.name)
    redis.expire(product_key, 600)
    return 'Product - %s, $%s' % (product.name, product.price)
提示

好的习惯是从配置文件获取expire时间,600。可以在my_app/__init__.py中设置该值,然后从这里获取。

在前面的方法中,注意redis对象的set()和expire()方法。首先,我们在Redis中使用product_key设置商品的ID。然后,我们设置过期时间为600秒。

现在我们将查找缓存中还存活的键。然后获取和这些键相匹配的商品,之后返回他们:

@catalog.route('/recent-products')
def recent_products():
    keys_alive = redis.keys('product-*')
    products = [redis.get(k).decode("utf-8") for k in keys_alive]
    return jsonify({'products': products})
译者注

我运行代码的时候,因为redis.get(k)获取到的字符串是unicode类型的,如果没有decode(“utf-8”),jsonify解析会出错,所以这里加上解码。

原理

当用户访问一个商品的时候就会有一条记录被存储,这条记录将保持600秒。下面的10分钟这个商品将被列在最近商品中,除非再一次被访问,然后再一次设置为10分钟。

使用非关系型数据库MongoDB

有时,我们正在构建的应用程序中使用的数据可能根本不是结构化的,也可以是半结构化的,也可以是其模式随时间变化的数据。在这种情况下,我们将避免使用RDBMS,因为它增加了痛苦,并且难以理解和维护。这时应该使用NoSQL数据库。
同时,在当前流行的开发环境下,由于开发速度快,不一定能够第一次设计出完美的结构(scheam)。NoSQL提供了修改结构的灵活性,而不需要太多麻烦。
在生产环境中,数据库通常在短时间内增长到一个巨大的规模。这极大地影响了整个系统的性能。垂直和水平缩放技术(Vertical-and horizontal-scaling)也是可用的,但是非常昂贵。这些情况下,可以考虑使用NoSQL数据库,因为它就是为了这个目的而被设计的。NoSQL数据库能够在大型集群上运行,并处理大量生成的高速数据,这使得它们在使用传统RDBMS处理伸缩性(scaling)问题时是一个不错的选择。
这里将使用MongoDB来演示Flask如何集成NoSQL。

准备

Flask有许多MongoDB的扩展。我们将使用Flask-MongoEngine,因为它提供了一个非常好的抽象,让我们很容易理解。通过下面命令安装它:

$ pip install flask-mongoengine

记住去开启MongoDB服务器,以便连接。更多安装和运行MongoDB的信息,可以参见http://docs.mongodb.org/manual/installation/

怎么做

下面使用MongoDB重写我们的应用。首先修改my_app/__init__.py:

from flask import Flask
from flask_mongoengine import MongoEngine
from redis import Redis

app = Flask(__name__)
app.config['MONGODB_SETTINGS']  = {'DB': 'my_catalog'}
app.debug = True
db = MongoEngine(app)

redis = Redis()

from my_app.catalog.views import catalog
app.register_blueprint(catalog)
提示

现在我们使用MONGODB_SETTINGS的配置而不是通常SQLAlchemy-centric的配置。这里,我们只需指定数据库的名字就可以使用。首先,我们需要在MongoDB中创建数据库,使用下面命令:

>>> mongo
MongoDB shell version: 2.6.4
> use my_catalog
switched to db my_catalog

接下来,我们将使用MongoDB字段创建一个Product模型。修改flask_catalog/my_app/catalog/models.py:

import datetime
from my_app import db

class Product(db.Document):
    created_at = db.DateTimeField(default=datetime.datetime.now, required=True)
    key = db.StringField(max_length=255, required=True)
    name = db.StringField(max_length=255, required=True)
    price = db.DecimalField()

    def __repr__(self):
        return '<Product %r>' % self.id
其他

注意创建模型的MongoDB字段和前面小节使用的SQLAlchemy是相似的。这里取消了ID字段,我们设置了created_at,这个字段将会存储记录创建的时间戳。

接下来是视图文件,flask_catalog/my_app/catalog/views.py:

from decimal import Decimal
from flask import request, Blueprint, jsonify
from my_app.catalog.models import Product

catalog = Blueprint('catalog', __name__)

@catalog.route('/')
@catalog.route('/home')
def home():
    return "Welcome to the Catalog Home."

@catalog.route('/product/<key>')
def product(key):
    product = Product.objects.get_or_404(key=key)
    return 'Product - %s, $%s' % (product.name, product.price)

@catalog.route('/products')
def products():
    products = Product.objects.all()
    res = {}
    for product in products:
        res[product.key] = {
            'name': product.name,
            'price': str(product.price)
        }
    return jsonify(res)

@catalog.route('/product-create', methods=['POST',])
def create_product():
    name = request.form.get('name')
    key = request.form.get('key')
    price = request.form.get('price')
    product = Product(
        name=name,
        key=key,
        price=Decimal(price)
    )
    product.save()
    return 'Product created.'

你会发现这非常类似于基于SQLAlchemy模型创建的视图。仅仅存在一些差异,而且是很容易理解的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值