Flask学习记录


Flask是一个使用Python编写的轻量级web框架,相比其他框架如Django更加简单易学。本文将实现一个简单示例,完成以下功能:从数据库中读取用户信息,在浏览器中分页展示,添加分页导航栏,并且实现根据用户名搜索。用到的主要知识点:数据库操作、表单、Jinja2模板。

学习环境搭建

操作系统

CentOS Linux release 7.4.1708 (Core)

执行yum update -y然后重启系统,确保软件包处于最新状态。

Python版本 2.7.5

安装Python虚拟环境

利用virtualenv可以实现一个隔离的Python环境,在这个虚拟环境中安装、删除Python的pip包而不会影响环境外的其他进程。虚拟环境还会让系统移植更容易,只要把虚拟环境的文件拷到另一个系统中然后恢复虚拟环境,就可以保证跟之前的Python包环境一模一样。

安装virtualenv:

[root@localhost ~]# pip install virtualenv

Collecting virtualenv

Downloading virtualenv-15.1.0-py2.py3-none-any.whl (1.8MB)

100% |████████████████████████████████| 1.8MB 78kB/s

Installing collected packages: virtualenv

Successfully installed virtualenv-15.1.0

创建一个名为env的虚拟环境:

[root@localhost ~]# virtualenv env

New python executable in /root/env/bin/python2

Also creating executable in /root/env/bin/python

Installing setuptools, pip, wheel...done.

进入虚拟环境env:

[root@localhost demo]# source env/bin/activate

(env) [root@localhost demo]#

前面的(env)标识了当前shell所在的虚拟环境,把env目录拷到其他系统就可以进行环境移植。

安装数据库

安装数据和相关开发包:

[root@localhost demo]# yum install mariadb mariadb-server python-devel mariadb-devel -y

安装完后,执行mysql_secure_installation,设置数据库root密码,因为是测试环境,我们为了方便设置为“test”,生产环境的话一定要设置复杂密码。

简单示例实现

项目结构

我们创建一个demo目录存放项目文件,用tree命令查看这个目录的结构如下:

(env) [root@localhost ~]# tree -I 'env' demo

demo

├── app                #程序包目录

│   ├── __init__.py

│   ├── main

│   │   ├── forms.py    #定义表单类

│   │   ├── __init__.py

│   │   ├── views.py    #定义视图函数,实际处理http请求

│   ├── models.py      #定义数据模型

│   ├── static           #存放静态文件

│   └── templates       #存放模板

│       ├── _macro.html

│       ├── userlist.html

├── config.py           #项目配置文件        

├── manage.py         #项目启动脚本

└── requirements.txt     #项目依赖的所有pip包

各文件的主要作用已经在上面注释。Flask并没有限定项目的结构必须如此,但是通常这是比较合理的结构,也可以根据自己的项目特点进行微调。下面分文件详细介绍如何实现本文开头提到的各个功能。

requirements.txt

这个文件保存了项目依赖的所有包及其版本,方便其他人移植你的Flask项目。文件是由命令pip freeze > requirements.txt生成的,在要运行该项目的系统中执行pip install -r requirements.txt就可以安装完所有依赖包,很方便。这个示例的requirements.txt内容如下:

alembic==0.9.6

click==6.7

Flask==0.12.2

Flask-Migrate==2.1.1

Flask-Script==2.0.6

Flask-SQLAlchemy==2.3.2

Flask-WTF==0.14.2

ForgeryPy==0.1

itsdangerous==0.24

Jinja2==2.10

Mako==1.0.7

MarkupSafe==1.0

MySQL-python==1.2.5

python-dateutil==2.6.1

python-editor==1.0.3

six==1.11.0

SQLAlchemy==1.2.0

Werkzeug==0.14.1

WTForms==2.1

config.py

项目的配置文件,集中存放配置参数,内容如下:

class Config:

    SQLALCHEMY_DATABASE_URI = "mysql://root:test@localhost/demo"

    SQLALCHEMY_COMMIT_ON_TEARDOWN = True

    SECRET_KEY = 'hard to guess string'

 

    @staticmethod

    def init_app(app):

        pass

文件中配置了数据库的访问方式,还有SECRET_KEY,这是一个密钥,用于保护表单免受跨站请求伪造(CSRF)的攻击。

models.py

编写业务逻辑前要先设计好数据模型,我们的示例的数据很简单,一张表——用户信息,包括两个字段:用户名和用户邮箱地址。代码如下:

from . import db

 

 

class User(db.Model):

    __tablename__ = 'users'

    id = db.Column(db.Integer, primary_key=True)

    username = db.Column(db.String(64), unique=True, index=True)

    email = db.Column(db.String(64), unique=True, index=True)

 

    def __repr__(self):

        return '<User %r>' % self.username

 

    @staticmethod

    def generate_fake(count=100):

        from sqlalchemy.exc import IntegrityError

        from random import seed

        import forgery_py

        seed()

        for i in range(count):

            u = User(username=forgery_py.internet.user_name(True),

                    email=forgery_py.internet.email_address())

            db.session.add(u)

            try:

                db.session.commit()

            except IntegrityError:

                db.session.rollback()

Flask-SQLalchemy是Flask的数据库扩展,通过对象关系映射ORM,把对数据库的操作转换为对Python对象的操作。Class User在数据库中对应的就是一张表,表名在__tablename__中设置,两个属性username和email对应表字段。代码中还用到了一个很有意思的模块forgery_py,它用于随机生成符合要求的测试数据,函数generate_fake()随机生成数量等于count条的数据并写入数据库。生成数据的具体操作方法会在最后执行程序的时候讲。

app/__init__.py

程序包的构建文件,在这文件里定义创建程序实例的函数create_app,也叫工厂函数。工厂函数完成3个主要工作:

1.      从config.py中导入程序配置;

2.      大多数Flask的扩展需要在这个函数里初始化,例如我们的示例用到的Flask数据库扩展Flask-Sqlalchemy;

3.      还有一个工作是给程序实例注册蓝本(blueprint),蓝本用于定义URL到视图函数的路由。

代码如下:

from flask import Flask

from flask_sqlalchemy import SQLAlchemy

from config import Config

 

db = SQLAlchemy()

 

 

def create_app(config_name):

    app = Flask(__name__)

    app.config.from_object(Config)

    Config.init_app(app)

 

    db.init_app(app)

 

    from .main import main as main_blueprint

    app.register_blueprint(main_blueprint)

 

return app

manage.py

manage.py是整个程序的启动脚本,从上面的app/__init__.py导入工厂函数create_app(),然后创建程序实例,初始化Flask的扩展Flask-Script和Flask-Migrate,代码如下:

#!/usr/bin/env python

#conding: utf-8

 

import os

 

from flask.ext.script import Manager, Shell

from flask.ext.migrate import Migrate, MigrateCommand

 

from app import create_app, db

from app.models import *

 

app = create_app('default')

manager = Manager(app)

migrate = Migrate(app, db)

 

def make_shell_context():

    return dict(app=app, db=db, User=User)

manager.add_command("shell", Shell(make_context=make_shell_context))

manager.add_command('db', MigrateCommand)

 

 

 

if __name__ == '__main__':

manager.run()

forms.py

表单的功能是接收用户提供的数据。用户将数据填到表单的各字段中,通过Post方法提交给服务端。在我们的示例中,表单用于接收用户输入的用户名,用于后续的搜索功能。代码如下:

from flask_wtf import FlaskForm

from wtforms import StringField, SubmitField

from wtforms.validators import DataRequired

 

class Form(FlaskForm):

    name = StringField('Username', validators=[DataRequired()])

submit = SubmitField('Search')

flask.wtf是Flask框架处理表单的扩展,我们自己定义的表单Form继承该模块中的FlaskForm类。Form很简单,一共2个字段,StringField提供给用户填写要搜索的用户名,检测函数DataRequired()确保数据不为空;SubmitField是一个提交按钮,用户点击按钮后,就会把表单数据提交给服务端。

views.py

该文件中定义路由和视图函数,视图函数是实际处理http请求的函数。我们的示例只有一个视图函数,代码如下:

from flask import render_template, url_for, request, session

from . import main

from .. import db

from ..models import User

from .forms import Form

 

@main.route('/',methods=['GET','POST'])

def index():

    page = request.args.get('page',1,type=int)

    pagination = User.query.order_by(User.id).paginate(

            page,per_page=10,error_out=False)

    form = Form()

    users = pagination.items

    if form.validate_on_submit():

        session['username'] = form.name.data

        users = User.query.filter_by(username=session.get('username')).all()

return render_template('userlist.html',users=users,pagination=pagination,form=form)

main是一个蓝本,修饰器@main.route定义的路由的含义是,访问“/”的的GET或POST方法都由index()函数处理。开头提到了我们要实现用户信息的分页展示,所以index()先从全局变量request中获取了页数,默认显示第1页。

然后分页查询数据库,每页10个数据;

然后初始化搜索表单,If判断把视图函数的执行结果分成了表单有数据和没数据两种情况:如果表单有数据,则把表单数据存到会话session中,并且查询数据库,把符合条件的用户传递给模板;如果表单没有数据,就把所有用户传递给模板。模板根据收到的用户信息生成html页面。

视图函数最核心的任务就是返回一个html页面给客户端,render_template()通过渲染模板生成一个html页面,所谓渲染就是根据传入的参数去修改模板中对应的可变部分。模板将在下一部分详细介绍。

templates

Flask集成了Jinja2模板引擎,templates这个目录存放所有jinja2模板。模板中有一些可以被替换的部分,渲染流程也支持通过条件判断或循环控制。

_macro.html中定义了一个宏pagination_widget,宏类似于Python中的函数,这个宏用于生成分页导航栏,它接收的参数是数据库分页查找返回的对象pagination和视图函数名endpoint。pagination对象支持一系列方法和属性:

l  方法has_prev()和has_next()分别检测当前页之前和之后还有没有页,返回布尔值;

l  方法prev()和next()分别返回上一页和下一页的分页对象

l  属性page返回当前页码;

l  方法iter_pages是一个迭代器,根据参数返回一组页码;

_macro.html代码如下:

{% macro pagination_widget(pagination, endpoint) %}

<style>

li{ list-style:none;}

</style>

<ul class="pagination">

    <tr>

    <td{% if not pagination.has_prev %} class="disabled"{% endif %}>

        <a href="{% if pagination.has_prev %}{{ url_for(endpoint, page=pagination.prev_num, **kwargs) }}{% else %}#{% endif %}">

            &laquo;

        </a>

    </td>

    {% for p in pagination.iter_pages(right_current=2) %}

        {% if p %}

            {% if p == pagination.page %}

            <td class="active">

                <a href="{{ url_for(endpoint, page = p, **kwargs) }}">{{ p }}</a>

            </td>

            {% else %}

            <td>

                <a href="{{ url_for(endpoint, page = p, **kwargs) }}">{{ p }}</a>

            </td>

            {% endif %}

        {% else %}

        <td class="disabled"><a href="#">&hellip;</a></td>

        {% endif %}

    {% endfor %}

    <td{% if not pagination.has_next %} class="disabled"{% endif %}>

        <a href="{% if pagination.has_next %}{{ url_for(endpoint, page=pagination.next_num, **kwargs) }}{% else %}#{% endif %}">

            &raquo;

        </a>

    </td>

    </tr>

</ul>

{% endmacro %}

其核心内容是中间的for循环,根据iter_pages()方法返回的页码,生成指向该页码的URL的链接。因此分页导航栏本质是由若干链接组成的,只不过这些特殊链接都指向与自己名字相关的URL。执行效果会在本文最后一部分看到。

 

userlist.html是视图函数index()真正渲染的文件,它通过一个列表来展示视图函数传给它的参数users中包含的用户信息。然后它引用了上面定义的宏,生成了分页导航栏。在最后是搜索表单。

{% import "_macro.html" as macro %}

<html>

<style>

li{ list-style:none;}

</style>

<body>

<h4>用户列表:</h4>

<ul>

{% for user in users%}

<li>{{ user.username }}: {{ user.email }}</li>

{% endfor %}

</ul>

<div>

    {{ macro.pagination_widget(pagination, '.index')}}

</div>

<form method="POST">

    {{ form.hidden_tag() }}

    {{ form.name.label }} {{ form.name() }}

    {{ form.submit() }}

</form>

</body>

</html>

执行效果

生成测试数据

(env) [root@localhost demo]# python manage.py shell

manage.py:6: ExtDeprecationWarning: Importing flask.ext.script is deprecated, use flask_script instead.

  from flask.ext.script import Manager, Shell

manage.py:7: ExtDeprecationWarning: Importing flask.ext.migrate is deprecated, use flask_migrate instead.

  from flask.ext.migrate import Migrate, MigrateCommand

/root/demo/env/lib/python2.7/site-packages/flask_sqlalchemy/__init__.py:794: FSADeprecationWarning: SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and will be disabled by default in the future.  Set it to True or False to suppress this warning.

  'SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and '

 

>>> db.create_all()

>>> User.generate_fake(100)

db.create_all()创建所有表,User.generate_fake(100)生成表中的数据。

启动程序

Flask框架内置了一个用于测试的web服务器,启动服务:

(env) [root@localhost demo]# python manage.py runserver -h 0.0.0.0 -p 5000

manage.py:6: ExtDeprecationWarning: Importing flask.ext.script is deprecated, use flask_script instead.

  from flask.ext.script import Manager, Shell

manage.py:7: ExtDeprecationWarning: Importing flask.ext.migrate is deprecated, use flask_migrate instead.

  from flask.ext.migrate import Migrate, MigrateCommand

/root/demo/env/lib/python2.7/site-packages/flask_sqlalchemy/__init__.py:794: FSADeprecationWarning: SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and will be disabled by default in the future.  Set it to True or False to suppress this warning.

  'SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and '

 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)

访问页面

打开一个浏览器,输入URL http://x.x.x.x:5000/,x.x.x.x替换为你的测试机的ip,可以看到以下页面:

image.png

数据库中一共存了100条用户记录,分为了10页展示,点击下面的分页导航可以跳转页面,下面是第9页的内容:

image.png

然后测试搜索功能,在表单中输入“amy63”,点击按钮,结果如下:

image.png


猜你喜欢

转载自blog.51cto.com/3646344/2120170
今日推荐