美文网首页python & flask
《Flask Web Development》第5章 数据库

《Flask Web Development》第5章 数据库

作者: tangyefei | 来源:发表于2014-12-26 15:10 被阅读1761次

    SQL Databases

    特点是什么?(得空补充)

    NoSQL Databases

    特点是什么?(得空补充)

    SQL or NoSQL?

    相比各自的优势是什么?(得空补充)

    Python Database Frameworks

    • 你可以使用哪些数据库
      不管是否开源的数据库,Flask都有对应数据库引擎的package比如:MySQL、 Postgres、 SQLite、 Redis、MongoDB 或 CouchDB。

    • 你还可以使用什么
      如果这些还不够,还有一些数据库抽象层package比如SQLAlchemy、 MongoEngine能让你在更高的层次操作数据(以对象形式)而非以table、document、query languages的形式。

    • 选择数据库有哪些参考依据

      • 易用性:SQL Vs NoSQL, 后者当然完胜了
      • 效率:后者在对象到数据库模型转化中会有一定开销,但这可以忽略不计,反过来,后者对生成率的促进远超过造成的开销。
      • 可移植性:尽管很多数据库抽象层只提供对一种数据库的支持,但是有的更高层次的数据库抽象层几乎支持所有的数据库,比如:SQLAlchemy ORM。
      • Flask集成:如果能以Flask的extension的形式存在意味着可以省去很多手写的代码。
    • 本书选择的数据库
      综上,本书选择Flask-SQLAlchemy(作为SQLAlchemy的扩展)作为数据库工具。

    使用Flask-SQLAlchemy进行数据库管理

    Flask-SQLAlchemy是一个使用了SQLAlchemy的扩展。 SQLAlchemy 是一个强大的关系型数据库框架,它能够支持多种数据库并提供了高层的ORM和底层的原生数据库操作。

    • 安装
    (venv) $ pip install flask-sqlalchemy
    
    • 数据库URL
      在Flask-SQLAlchemy中,数据库被表示为一个URL,如下所示:

      MySQL mysql://username:password@hostname/database
      Postgres
      postgresql://username:password@hostname/database
      SQLite (Unix) sqlite:////absolute/path/to/database
      SQLite (Windows) sqlite:///c:/absolute/path/to/database

      hostname对应一台主机,username和password好理解,对于sqlite数据库是没有用户名密码的,所以它只是目录下的一个文件而已。

    • 配置
      数据库URL被在配置在: SQLALCHEMY_DATABASE_URI,还有另一个重要的属性被配置在SQLALCHEMY_COMMIT_ON_TEARDOWN( 用来在每次请求结束时候提交数据库改动,通常设置为True)。

    • 实例
      如下是一个配置SQLite数据库的例子, db实例化了一个SQLAlchemy对象并提供了所有Flask-SQLAlchemy具备的功能:

    import os
    #..
    from flask.ext.sqlalchemy import SQLAlchemy
    basedir = os.path.abspath(os.path.dirname(__file__))
    app = Flask(__name__)
    app.config['SQLALCHEMY_DATABASE_URI'] =\
        'sqlite:///' + os.path.join(basedir, 'data.sqlite')
    app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True
    db = SQLAlchemy(app)
    

    Model定义

    class Role(db.Model):
        __tablename__ = 'roles'
        id = db.Column(db.Integer, primary_key=True) 
        name = db.Column(db.String(64), unique=True)
    
        def __repr__(self):
            return '<Role %r>' % self.name
    
    class User(db.Model):
        __tablename__ = 'users'
        id = db.Column(db.Integer, primary_key=True)
        username = db.Column(db.String(64), unique=True, index=True)
        
        def __repr__(self):
            return '<User %r>' % self.username
    

    类变量 __tablename__ 定义了表名(尽管默认会有名字,但是是非复数形式的不太好),所有的属性都定义为db.Column的实例对象,db.Column的第一个参数是类别第二个参数是可选配置参数(所有table都要有primary key),__repr__()方法是为了方便调试和测试用。

    Relationships

    如下示例展示了一个一对多的关系(对于配置的理解不深先照样写吧!),该书附录了一张表格列出了db.relationship常用的配置参数说明,需要时参考:

    class Role(db.Model): 
        # ...
        users = db.relationship('User', backref='role')
    
    class User(db.Model):
        # ...
        role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
    

    Database操作

    学习这些数据库操作的的最好办法是在Python Shell中,如下几个部分将开始学习最常见的数据库操作(本章前面的构建model的代码应用到hello.py中):

    # shell方式需要安装flask-script,create_all会创建所有的model
    (venv) $ python hello.py shell 
    >>> from hello import db
    >>> db.create_all()
    
    #重新设计表结构以后需要drop掉之前的表
    >>> db.drop_all()
    >>> db.create_all()
    
    >>> from hello import Role, User
    >>> admin_role = Role(name='Admin')
    >>> mod_role = Role(name='Moderator')
    >>> user_role = Role(name='User')
    >>> user_john = User(username='john', role=admin_role) 
    >>> user_susan = User(username='susan', role=user_role) 
    >>> user_david = User(username='david', role=user_role)
    
    # 因为没有commit所有的对象都还没有id
    >>> print(admin_role.id) None
    >>> print(mod_role.id) None
    >>> print(user_role.id) None
    
    # 用db.session管理对象的持续化
    >>> db.session.add(admin_role)
    >>> db.session.add(mod_role)
    >>> db.session.add(user_role)
    >>> db.session.add(user_john)
    >>> db.session.add(user_susan)
    >>> db.session.add(user_david)
    
    >>> db.session.commit()
    
    # 数据已经提交带数据库
    >>> print(admin_role.id) 1
    >>> print(mod_role.id) 2
    >>> print(user_role.id) 3
    
    # 修改属性
     >>> admin_role.name = 'Administrator'
    >>> db.session.add(admin_role)
    >>> db.session.commit()
    
    # 删除
    >>> db.session.delete(mod_role)
    >>> db.session.commit()
    
    # 查询
     >>> Role.query.all()
    [<Role u'Administrator'>, <Role u'User'>]
    >>> User.query.all()
    [<User u'john'>, <User u'susan'>, <User u'david'>]
    
    # 过滤器
     >>> User.query.filter_by(role=user_role).all()
    [<User u'susan'>, <User u'david'>]
    
    # 查询方法和过滤器是多种多样的,该书列出了两个表格可供查询,参考page61.
    
    # 还可以获取到原生的查询语句
    >>> str(User.query.filter_by(role=user_role))
    'SELECT users.id AS users_id, users.username AS users_username,
    users.role_id AS users_role_id FROM users WHERE :param_1 = users.role_id'
    
    # 注意,关闭了shell窗口以后,意味着你要重新import db、Role,重新构建之前定义过的对象,如下:
    >>> from hello import db
    >>> from hello import Role
    >>> user_role = Role.query.filter_by(name='User').first()
    
    # 关联查询
     >>> users = user_role.users
    >>> users
    [<User u'susan'>, <User u'david'>]
    >>> users[0].role
    <Role u'User'>
    
    # 如上查询,如果想要应用filter是不行的。因为user_role.usres已经调用了all()方法,要使用filter需要在py中进行修改:
    
    class Role(db.Model): 
    # ...
    users = db.relationship('User', backref='role', lazy='dynamic') 
    # ...
    
    >>> user_role.users.order_by(User.username).all()
    [<User u'david'>, <User u'susan'>]
    >>> user_role.users.count()
    2
    

    Python Shell集成Model

    如果每次打开Shell都要手动导入未免太繁琐了,Flask-Script提供了配置可以自动导入对象:

    from flask.ext.script import Shell
    def make_shell_context():
        return dict(app=app, db=db, User=User, Role=Role)
    
    manager.add_command("shell", Shell(make_context=make_shell_context))
    

    在View Functions中操作数据库

    将数据库操作应用到View Function中,如下是一个例子:

    index.py

    @app.route('/', methods=['GET', 'POST'])
    def index():
        form = NameForm()
        if form.validate_on_submit():
            user = User.query.filter_by(username=form.name.data).first()
            if user is None:
                user = User(username = form.name.data)
                db.session.add(user)
                session['known'] = False
            else:
                session['known'] = True
    
            session['name'] = form.name.data
            form.name.data = ''
    
            return redirect(url_for('index'))
        return render_template('index.html',
            form = form, name = session.get('name'), known = session.get('known', False))
    

    templates/index.html

    {% extends "commonBase.html" %}
    {% import "bootstrap/wtf.html" as wtf %}
    
    {% block title %}Flasky{% endblock %}
    
    {% block page_content %}
        <div class="page-header">
            <h1>Hello, {% if name %}{{ name }}{% else %}Stranger{% endif %}!</h1>
            {% if not known %}
                <p>Pleased to meet you!</p>
            {% else %}
                <p>Happy to see you again!</p>
            {% endif %}
        </div>
        {{ wtf.quick_form(form) }}
    {% endblock %}
    

    使用Flask-Migrate来做数据库的Migrations

    开发进行到一定阶段,你会发现model的结构需要发生改变,Flask-SQLAlchemy从Model来构建数据库表结构只会发生在以前表不存在的时候,当然你也可以不管数据丢失先删除数据库。

    更好的做法是使用数据库迁移框架,就像代码能够进行版本控制一样,一个数据库迁移框架能够跟踪数据库的变化,并且渐进的应用数据库的改变。

    SQLAlchemy的开发者写了一个名叫Alembic的框架,但是我们并不打算直接使用它,而是使用Flask-Migrate extension扩展来和Flask-Script集成,全部通过命令行来达到目的。

    Creating a Migration Repository
    (venv) $ pip install flask-migrate
    

    如下展示而来该扩展的配置方式:

    from flask.ext.migrate import Migrate, MigrateCommand 
    # ...
    migrate = Migrate(app, db)
    manager.add_command('db', MigrateCommand)
    

    使用db的子命令来构建一个资源库:

      (venv) $ python index.py db init
    
    Creating a Migration Script

    Alembic migrations能够有手动和自动两种模式可用。

    手动的migration要创建空的工具方法upgrade()和downgrade(),自动migration会自动查找当前数据库和model definitions的不同之处来完成upgrade()和downgrade()。

    一个自动 migration 的例子:

    (venv) $ python index.py db migrate -m "initial migration"
    
    Upgrading the Database

    一旦migration完成,你就可以通过db upgrade 来更新数据库了,你可以把data.sqlite删除以后再执行命令。

    (venv) $ python hello.py db upgrade
    

    贯穿全书都会围绕migration来推进,本书只是简单介绍,后面有更详细的内容。

    相关文章

      网友评论

      • artisan_7f01:在学习狗书第5章执行到db.session.add(user_john)这句时报错了,代码是从作者的github上拉取的,sqlalchemy.exc.InvalidRequestError: Object '<User at 0x1041bfdd8>' is already attached to session '1' (this is '4') 这个坑怎么填
        artisan_7f01:@tangyefei 找到原因了,是安装的交互式解释器bpython本身有bug造成的,github上这个工具此问题的issue都一年多了也没修复,开源软件有时候真坑爹啊(写不挣钱的东西估计也没有修复的动力),在这上花了好长时间。
        tangyefei:@artisan_7f01 看错误信息应该是之前有add过同一个user,所以会报错,可以考虑移除掉再add?
      • depers:大神,有个问题想要请教你,我在命令行中输入了这行代码python manager.py db migrate -m 'initial migration'。当时出现了这样的错误,这怎么回事,求解答,谢谢^_^
        (virtualenv) E:\Dome\Django\item\test_1>python manager.py db migrate -m 'initial migration'
        usage: manager.py [-?] {shell,deploy,db,dev,runserver,test} ...
        manager.py: error: too many arguments
      • tangyefei:@欧悦茶具 无意被关闭了,现在已打开。

      本文标题:《Flask Web Development》第5章 数据库

      本文链接:https://www.haomeiwen.com/subject/brmatttx.html