zoukankan      html  css  js  c++  java
  • Python框架 Flask 项目实战教程

    本文目的是为了完成一个项目用到的flask基本知识,例子会逐渐加深。最好对着源码,一步一步走。
    下载源码,运行
    pip install -r requirements.txt 建立环境
    python db_create.py 创建自己数据库
    python db_migrate 迁移数据库
    ————————————————————————————–
    flask 不仅简介小巧,同时运用的时候十分灵活。下面简单介绍一下如何编写一个 flask项目。

    涉及调用开发服务器,数据库连接以及 ORM 映射,还有数据库的迁移,模板使用。后期再完善会话,管理后台,缓存等。

    一 、安装环境
    我们使用 flask web框架,并用 sqlalchemy来做数据库映射,并使用 migrate做数据迁移。

    $ pip install flask
    $ pip install SQLAlchemy==0.7.9
    $ pip install flask-sqlalchemy
    $ pip install flask-migrate
    $ pip install sqlalchemy-migrate

    二、建立项目
    flask 没有 django 那样原生的 manage管理工具(flask-admin可以实现,日后再说)。因此我们需要手动建立目录。新建一个 myproject目录,在里面建 app tmp两个文件夹,然后在 app文件夹里面建立 static, templates 两个文件夹,用来存储静态文件和模板。最后目录结构如下:

    ├── app
    │ ├── static
    │ ├── templates
    └── tmp

    三、 初始化文件

    建立一些文件,在 app文件里建立 __init__.py models.py views.py 然后在与app 同目录下建立 config.py 和 run.py 此时的目录结构如下:

    (env)admin@admindeMacBook-Air:~/project/python/flask/project$ tree
    
    ├── app
    │ ├── static
    │ ├── templates
    │ ├── __init__.py
    │ ├── models.py
    │ ├── views.py
    ├── config.py
    ├── run.py
    └── tmp

    四 、开始项目

    1 hello wrod
    打开 (/app/__init.py) 文件,写入

    # -*- coding: utf-8 -*-
    from flask import Flask # 引入 flask
    app = Flask(__name__) # 实例化一个flask 对象
    import views # 导入 views 模块
    # from app import views

    注意,我们的 app 文件夹其实是一个python包,from app import views 这句话也就是从 包app里面导入 views模块,所以写成注释的那句话也没错,其他代码实践喜欢写成后面那种

    现在我们来开始写我们的视图,打开 (/app/views.py)

    # -*- coding: utf-8 -*-
    from app import app
    from models import User, Post, ROLE_USER, ROLE_ADMIN
    @app.route(‘/’)
    def index():
        return ‘hello world, hello flask’

    下一步我们将要启动我们的开发服务器,打开 (/run.py)

    # -*- coding: utf-8 -*-
    from app import app
    if __name__ == ‘__main__’:
        app.debug = True # 设置调试模式,生产模式的时候要关掉debug
        app.run() # 启动服务器

    这段代码需要注意第一句 from app import app。也许会有疑问,我们的 app 包里,貌似没有 app.py 这样的模块。其实这是 flask 方式和python的导入方式。from app 指导入 app 包里的 __iniy__.py 所以这句话的含义是导入 __.init__.py 里面的 app实例。

    打开shell 运行

    $ python run.py
    (env)admin@admindeMacBook-Air:~/project/python/flask/project$ python run.py
    * Running on http://127.0.0.1:5000/ * Restarting with reloader

    用浏览器打开 http://127.0.0.1:5000/ 将会看到一个输入 hello world 的页面

    2 连接数据库
    下面开始连接数据库引擎 先要做一个简单的配置 打开 (/config.py)

    # -*- coding: utf-8 -*-
    
    import os
    basedir = os.path.abspath(os.path.dirname(__file__))
    SQLALCHEMY_DATABASE_URI = ‘sqlite:///%s’ % os.path.join(basedir, ‘app.db’)
    SQLALCHEMY_MIGRATE_REPO = os.path.join(basedir, ‘db_repository’)
    CSRF_ENABLED = True
    SECRET_KEY = ‘you-will-never-guess’

    SQLALCHEMY_DATABASE_URI是the Flask-SQLAlchemy必需的扩展。这是我们的数据库文件的路径。

    SQLALCHEMY_MIGRATE_REPO 是用来存储SQLAlchemy-migrate数据库文件的文件夹。

    然后打开 (/app/__init__.py)

    # -*- coding: utf-8 -*-
    import os
    from flask import Flask
    from flask.ext.sqlalchemy import SQLAlchemy
    from config import basedir
    
    app = Flask(__name__)
    app.config.from_object(‘config’) # 载入配置文件
    db = SQLAlchemy(app) # 初始化 db 对象

    # from app import views, models # 引用视图和模型

      import views, models

     

    打开 (/app/models.py)

    # -*- coding: utf-8 -*-
    
    from app import db
    ROLE_USER = 0
    ROLE_ADMIN = 1
    
    class User(db.Model): id = db.Column(db.Integer, primary_key=True) nickname=db.Column(db.String(60), index=True, unique=True) email = db.Column(db.String(120), index=True, unique=True) role = db.Column(db.SmallInteger, default=ROLE_USER)
    def
    __repr__(self): return ‘<User %r>’ % self.nickname

    我们的模型建立了一个 user类,正好是 数据库里面的 user 表,表有四个字段

    现在我们的数据库配置已经好了,可是还没有建立数据库。新建一个文件 (/db_create.py)

    # -*- coding: utf-8 -*-
    
    from migrate.versioning import api
    from config import SQLALCHEMY_DATABASE_URI
    from config import SQLALCHEMY_MIGRATE_REPO
    from app import db
    import os.path
    
    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))

    这个脚本是完全通用的,所有的应用路径名都是从配置文件读取的。当你用在自己的项目时,你可以把脚本拷贝到你的项目目录下就能正常使用了。
    打开shell 运行

    $ python db_create.py


    运行这条命令之后,你就创建了一个新的app.db文件。这是个支持迁移的空sqlite数据库,同时也会生成一个带有几个文件的db_repository目录,这是SQLAlchemy-migrate存储数据库文件的地方,注意如果数据库已存在它就不会再重新生成了。这将帮助我们在丢失了现有的数据库后,再次自动创建出来。
    运行
    slqite3 app.db

    >>> .tables

    >>> user

    或者使用 sqlite 图形化客户端,没有需要安装 (windows 下直接下载安装包即可)

    $ sudo apt-get install sqlitebrowser
    3 数据库迁移
    每当我们更改了 models 。等价于更改了数据库的表结构,这个时候,需要数据库同步。新建 (/db_migrate.py)

    # -*- coding: utf-8 -*-
    
    import imp
    from migrate.versioning import api
    from app import db
    from config import SQLALCHEMY_DATABASE_URI
    from config import SQLALCHEMY_MIGRATE_REPO
    
    migration = SQLALCHEMY_MIGRATE_REPO + '/versions/%03d_migration.py' % (api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO) + 1)
    tmp_module = imp.new_module('old_model')
    old_model = api.create_model(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO)
    
    exec old_model in 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)
    print 'New migration saved as' + migration print 'Current database version:' + str(api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO))

     这个脚本看起来很复杂,SQLAlchemy-migrate通过对比数据库的结构(从app.db文件读取)和models结构(从app/models.py文件读取)的方式来创建迁移任务,两者之间的差异将作为一个迁移脚本记录在迁移库中,迁移脚本知道如何应用或者撤销一次迁移,所以它可以方便的升级或者降级一个数据库的格式。

    开始数据库迁移

    $ python db_mirgate.py

    New migration saved as db_repository/versions/001_migration.py Current database version: 1
    相应的,我们继续创建数据库 升级和回退的脚本
    (/db_upgrade.py)

    # -*- coding: utf-8 -*-
    
    from migrate.versioning import api
    from config import SQLALCHEMY_DATABASE_URI
    from config import SQLALCHEMY_MIGRATE_REPO
    api.upgrade(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO)
    print 'Current database version:' + str(api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO))

    (/db_downgrade.py)

    # -*- coding: utf-8 -*-
    

    from migrate.versioning import api from config import SQLALCHEMY_DATABASE_URI from config import SQLALCHEMY_MIGRATE_REPO v = api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO) api.downgrade(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO, v – 1) print 'Current database version:' + str(api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO))

    4 操作数据库
    接下来,我们定义一个新的models class 并做第二次迁移归并
    打开(/app/models.py)

    # -*- coding: utf-8 -*-
    
    from app import db
    ROLE_USER
    = 0 ROLE_ADMIN = 1 class User(db.Model): id = db.Column(db.Integer, primary_key=True) nickname=db.Column(db.String(60), index=True, unique=True) email = db.Column(db.String(120), index=True, unique=True) role = db.Column(db.SmallInteger, default=ROLE_USER) posts = db.relationship(‘Post’, db.backref = ‘author’, db.lazyload = ‘dynamic’) def __repr__(self): return ‘<User %r>’ % self.nickname class Post(db.Model): id = db.Column(db.Integer, primary_key=True) body = db.Column(db.String(140)) timestamp = db.Column(db.DateTime) user_id = db.Column(db.Integer, db.ForeignKey(‘user.id’)) def __repr__(self): return ‘<Post %r>’ % (self.body)

    这里我们进行了两个class 的关联。

    $ python db_mirgrate.py

    New migration saved as db_repository/versions/002_migration.py Current database version: 2

    打开 (/app/views.py)

    # -*- coding: utf-8 -*-
    
    from flask import render_template, flash, redirect, session, url_for, request, g
    from app import app, db
    from models import User, Post, ROLE_USER, ROLE_ADMIN
    
    @app.route(‘/’)
    def index():
        return render_template(‘index.html’)
    
    @app.route(‘/adduser/<nickname>/<email>’)
    def adduser(nickname, email):
        u = User(nickname=nickname, email=email)
        try:
            db.session.add(u)
            db.session.commit()
            return ‘add successful’
        except Exception, e:
            return ‘something go wrong’
    
    @app.route(‘/getuser/<nickname>’)
    def getuser(nickname):
        user = User.query.filter_by(nickname=nickname).first()
            return render_template(‘user.html’, user=user)
    
    @app.errorhandler(404)
    def internal_error(error):
        return render_template(‘404.html’), 404
    
    @app.errorhandler(500)
    def internal_error(error):
        db.session.rollback()
        return render_template(‘500.html’), 500

    这次我们使用了模板, 新建(/app/templates/user.html)

    <html>
    <head>
    <title>user</title>
    </head>
    <body>
    <h1>user</h1>
    <ul>
    <li>user: {{ user.nickname }}</li>
    <li>email: {{ user.email }}</li>
    </ul>
    </body>
    </html>

    最后运行

    $ python run.py
    用浏览器访问 http://127.0.0.1:5000/adduser/username/useremail
    最后还可以用 sqlite 客户端打开查看我们的数据库变换。关于更多的数据库操作方法,请阅读 sqlalchemy文档。
    最后我们的代码目录结构如下:

    (env)admin@admindeMacBook-Air:~/project/python/flask/project$ tree
    .
    ├── app
    │ ├── __init__.py
    │ ├── models.py
    │ ├── static
    │ ├── templates
    │ │ ├── 404.html
    │ │ ├── 500.html
    │ │ ├── index.html
    │ │ └── user.html
    │ ├── views.py
    ├── app.db
    ├── config.py
    ├── db_create.py
    ├── db_downgrade.py
    ├── db_migrate.py
    ├── db_repository
    │ ├── __init__.py
    │ ├── manage.py
    │ ├── migrate.cfg
    │ ├── README
    │ └── versions
    │ ├── 001_migration.py
    │ ├── 002_migration.py
    │ ├── __init__.py
    ├── db_upgrade.py
    ├── requirements.txt
    ├── run.py
    ├── tmp

    5 表单
    接下来,我们将要接触表单方面的内容。flask原生提供的表单处理,也比较简单。当然,有了轮子,我们就直接用好了。需要安装一个
    Flask-WTF==0.9.4。
    安装完之后,打开 (/app/forms.py)

    # -*- coding: utf-8 -*-
    
    import re
    from flask_wtf import Form
    from flask_wtf.html5 import EmailField
    from wtforms import TextField, PasswordField, BooleanField
    from wtforms.validators import DataRequired, ValidationError
    from models import User
    from hashlib import md5
    
    class RegisterFrom(Form):
        nickname = TextField(‘nickname’, validators=[DataRequired()])
        email = EmailField(’email’, validators=[DataRequired()])
        password = PasswordField(‘password’, validators=[DataRequired()])
        conform = PasswordField(‘conform’, validators=[DataRequired()])
    
        def validate_nickname(self, field):
            nickname = field.data.strip()
    if len(nickname) < 3 or len(nickname) > 20:
        raise ValidationError(‘nickname must be 3 letter at least’)
            elif not re.search(r’^w+$’, nickname):
                raise ValidationError(‘User names can contain only alphanumeric characters and underscores.’)
    else:
    # 验证是否已经注册
                u = User.query.filter_by(nickname=nickname).first()
                if u:
    raise ValidationError(‘The nickname already exists’)
    
        def validate_email(self, field):
            email = field.data.strip()
            email = User.query.filter_by(email=email).first()
            if email:
                raise ValidationError(‘The email already exists’)
    
        def validate_password(self, field):
            password = field.data.strip()
            if len(password) < 6:
                raise ValidationError(‘password must be 3 letter at least’)
    
        def validate_conform(self, field):
            conform = field.data.strip()
            if self.data[‘password’] != conform:
                raise ValidationError(‘the password and conform are different’)

    上述代码定义了一个 RegisterFrom class,正好是一个表单,class的字段映射为表单的域。其中 wtforms 中有 TextField, PasswordField, BooleanField这些表单类型,flask_wtf.html5 中 EmailField 则是 html5 规范的。
    表单有 form.validate_on_submit() 方法,这个方法执行之前会验证表单域。验证方法和django类似,都是 validate_field 的方法。其中一个参数 field正好是表单的value值 需要注意验证两次密码是否相同的时候,需要用到 self.data 。这是一个字典,包含了表单域的name和value

    6 用户注册
    打开(/app/views.py)添加注册方法。

    @app.route(‘/account/signup’, methods=[‘GET’, ‘POST’])
    def signup():
        form = RegisterFrom()
    if request.method == ‘POST’:
    if form.validate_on_submit():
                psdmd5 = md5(form.data[‘password’])
                password = psdmd5.hexdigest()
                u = User(nickname=form.data[‘nickname’], email=form.data[’email’], password=password)
    try:
                db.session.add(u)
                db.session.commit()
                flash(‘signup successful’)
    except Exception, e: return ‘something goes wrong’ return redirect(url_for(‘signin’)) return render_template(‘signup.html’, form=form)

    模版 新建 (/app/tempaltes/signup.html)

    <!– extend base layout –>
    {% extends “base.html” %}
    
    {% block content %}
    <form method=”POST” action=”>
    {{ form.hidden_tag() }}
    <p>{{ form.nickname.label }} {{ form.nickname(size=20) }}</p>
    <p>{{ form.email.label }} {{ form.email(size=20) }}</p>
    <p>{{ form.password.label }} {{ form.password(size=20) }}</p>
    <p>{{ form.conform.label }} {{ form.conform(size=20) }}</p>
    <input type=”submit” value=”Sign Up”>
    <input type=”reset” value=”Reset”>
    </form>
    
    {{ form.errors }}
    {% endblock %}

    其中,主要是用到 form 对象的一些属性。form.nickname.label 是指表单类定义的名字,form.nickname 会转变成 表单域的 html 代码 即 <input type=”text” id=”nickname” name=”nickaname” value=””/>

    7 用户登录
    先添加一个用户登录的表单,打开 (/app/forms.py)

    class LoginForm(Form):
        nickname = TextField(‘nickname’, validators=[DataRequired()])
        password = PasswordField(‘password’, validators=[DataRequired()])
        remember_me = BooleanField(‘remember_me’, default=False)
    
        def validate_nickname(self, field):
            nickname = field.data.strip()
            if len(nickname) < 3 or len(nickname) > 20:
                raise ValidationError(‘nickname must be 3 letter at least’)
            elif not re.search(r’^w+$’, nickname):
                raise ValidationError(‘User names can contain only alphanumeric characters and underscores.’)
            else:
                return nickname

    用户登录,最简单的方法就是 验证用户名,如果通过,则添加 session,登出的时候,清除session即可,模版里面可以直接使用 request.session用来判断用户登录状态
    打开(/app/views.py) 添加登录登出方法

    @app.route(‘/account/signin’, methods=[‘GET’, ‘POST’])
    def signin():
        form = LoginForm()
    if request.method == ‘POST’:
    if form.validate_on_submit():
                nickname = form.data[‘nickname’]
                psdmd5 = md5(form.data[‘password’])
                password = psdmd5.hexdigest()
                u = User.query.filter_by(nickname=nickname, password=password).first()
    if u:
                    session[‘signin’] = True
                    flash(‘signin successful’)
                    return redirect(url_for(‘index’))
    else:
                    flash(u’用户名或者密码错误’)
                    return render_template(‘signin.html’, form=form)
    
    @app.route(‘/account/signout’)
    def signout():
        session.pop(‘signin’, None)
        flash(‘signout successful’)
        return redirect(‘index’)

    注意,我们使用 flask 的时候,用了中文,就必须是 unicode
    除了原生的登录方式,我们还可以用flask-login。安装flask-login
    此时需要初始化一个 login_manager 对象,打开 (/app/__init__.py)

    # -*- coding: utf-8 -*-
    
    import os
    from flask import Flask
    from flask.ext.sqlalchemy import SQLAlchemy
    from flask.ext.login import LoginManager
    from config import basedir
    
    app = Flask(__name__)
    
    app.config.from_object(‘config’) db
    = SQLAlchemy(app) # 初始化数据库管理对象 login_manager = LoginManager() # 初始化登录管理对象 login_manager.init_app(app) login_manager.login_view = “signin” # 登录跳转视图 login_manager.login_message = u”Bonvolu ensaluti por uzi tio paĝo.” # 登录跳转视图前的输出消息 # from app import views, models import views, models

    然后打开 (/app/views.py)
    添加下面方法, 注释掉之前的 登入登出方法

    from flask.ext.login import login_user, logout_user, current_user, login_required
    from app import app, db, login_manager
    
    @login_manager.user_loader
    def load_user(userid):
    return User.query.get(userid)
    
    @app.route(‘/account/signin’, methods=[‘GET’, ‘POST’])
    def signin():
        form = LoginForm()
        if request.method == ‘POST’:
            if form.validate_on_submit():
                nickname = form.data[‘nickname’]
                psdmd5 = md5(form.data[‘password’])
                password = psdmd5.hexdigest()
                remember_me = form.data[‘remember_me’]
                user = User.query.filter_by(nickname=nickname, password=password).first()
    if user:
                    login_user(user, remember = remember_me)
                    flash(‘signin successful’)
                    return redirect(request.args.get(“next”) or url_for(“index”))
                else:
                    flash(u’用户名或者密码错误’)
    return render_template(‘signin.html’, form=form)
    
    @app.route(‘/account/signout’)
    
    @login_required
    def signout():
    logout_user() flash(‘signout successful’)
    return redirect(‘index’)

    模版也会响应的改,具体看源码吧。
    base.html

    <html>
    <head>
    <meta http-equiv=”Content-Type” content=”text/html; charset=utf-8″ />
    {% if title %}
    <title>{{title}} – microblog</title>
    {% else %}
    <title>microblog</title>
    {% endif %}
    </head>
    <body>
    <div class="metanav">
    
    <div>
    <a href=”{{ url_for(‘index’) }}”>Home</a>
    {% if current_user.is_authenticated() %}
    <a href=”{{ url_for(‘signout’) }}”>signout</a>
    {% else %}
    <a href=”{{ url_for(‘signin’) }}”>signin</a>
    {% endif %}
    </div>
    
    {% for message in get_flashed_messages() %}
    <div class="flash">{{ message }}</div>
    {% endfor %}
    {% block content %}{% endblock %}
    </body>
    </html>

    代码下载: https://github.com/rsj217/flask-extend/tree/master/project

  • 相关阅读:
    Eclipse / android studio 添加第三方jar包 步骤
    Android checkbox 自定义点击效果
    Android 程序打包和安装过程
    Android 基础
    (转)Genymotion安装virtual device的“unable to create virtual device, Server returned Http status code 0”的解决方法
    (转)eclipse 导入Android 项目 步骤
    微信开放平台注册 步骤
    Android Studio 初级安装
    数组
    作用域问题代码
  • 原文地址:https://www.cnblogs.com/jsben/p/4909964.html
Copyright © 2011-2022 走看看