Flask

2018/04/24 12:54
阅读数 39

开发环境

Windows下安装Python: http://www.cnblogs.com/0bug/p/8228378.html

virtualenv的安装:http://www.cnblogs.com/0bug/p/8598458.html

Pycharm Professional:http://www.jetbrains.com/pycharm/

创建虚拟环境

mkvirtualenv flaskenv

安装flask

pip install flask

Flask简介

flask是一款非常流行的Python Web框架,出生于2010年,作者是Armin Ronacher,本来这个项目只是作者在愚人节的一个玩笑,后来由于非常受欢迎,进而成为一个正式的项目。目前为止最新的版本是0.12.2

flask自2010年发布第一个版本以来,大受欢迎,深得开发者的喜爱,并且在多个公司已经得到了应用,flask能如此流行的原因,可以分为以下几点:

  • 微框架、简洁、只做他需要做的,给开发者提供了很大的扩展性。
  • Flask和相关的依赖(Jinja2、Werkzeug)设计得非常优秀,用起来很爽。
  • 开发效率非常高,比如使用SQLAlchemyORM操作数据库可以节省开发者大量书写sql的时间。
  • 社会活跃度非常高。

Flask的灵活度非常之高,他不会帮你做太多的决策,即使做已经帮你做出选择,你也能非常容易的更换成你需要的,比如:

  • 使用Flask开发数据库的时候,具体是使用SQLAlchemy还是MongoEngine或者是不用ORM而直接基于MySQL-Python这样的底层驱动进行开发都是可以的,选择权完全掌握在你自己的手中。区别于DjangoDjango内置了非常完善和丰富的功能,并且如果你想替换成你自己想要的,要么不支持,要么非常麻烦。
  • 把默认的Jinija2模板引擎替换成Mako引擎或者是其他模板引擎都是非常容易的。

第一个flask程序:

pycharm新建一个flask项目,新建项目的截图如下:

create后创建一个新项目,然后在helloworld.py文件中书写代码:

#coding: utf8    

    # 从flask框架中导入Flask类
    from flask import Flask

    # 传入__name__初始化一个Flask实例
    app = Flask(__name__)

    # app.route装饰器映射URL和执行的函数。这个设置将根URL映射到了hello_world函数上
    @app.route('/')
    def hello_world():
        return 'Hello World!'

    if __name__ == '__main__':
        # 运行本项目,host=0.0.0.0可以让其他电脑也能访问到该网站,port指定访问的端口。默认的host是127.0.0.1,port为5000
        app.run(host='0.0.0.0',port=9000)

然后点击运行,在浏览器中输入http://127.0.0.1:9000就能看到hello world了。需要说明一点的是,app.run这种方式只适合于开发,如果在生产环境中,应该使用Gunicorn或者uWSGI来启动。如果是在终端运行的,可以按ctrl+c来让服务停止。

URL组成部分详解

URL是Uniform Resource Locator的简写,统一资源定位符。

一个URL由以下及部分组成:

scheme://host:port/path/?query-string=xxx#anchor

scheme:代表的是访问的协议,一般为http或者https以及ftp等。
host:主机名,域名,比如www.baidu.com。
port:端口号。当你访问一个网站的时候,浏览器默认使用80端口。
path:查找路径。比如:www.jianshu.com/trending/now,后面的trending/now就是path。
query-string:查询字符串,比如:www.baidu.com/s?wd=python,后面的wd=python就是查询字符串。
anchor:锚点,后台一般不用管,前端用来做页面定位的。比如:https://baike.baidu.com/item/%E5%88%98%E5%BE%B7%E5%8D%8E/114923?fr=aladdin#7

注意:URL中所有字符都是ASCII字符集,如果出现非ASCII字符,比如中文,浏览器会进行编码再进行传输。

web服务器+应用服务器+web应用框架


web服务器:
负责处理http请求,响应静态文件,常见的有Apache,Nginx以及微软的IIS.

应用服务器:
负责处理逻辑的服务器。比如php、python的代码,是不能直接通过nginx这种web服务器来处理的,只能通过应用服务器来处理,常见的应用服务器有uwsgi、tomcat等。

web应用框架:
一般使用某种语言,封装了常用的web功能的框架就是web应用框架,flask、Django以及Java中的SSH(Structs2+Spring3+Hibernate3)框架都是web应用框架。

debug模式详解

为什么需要开启DEBUG模式

1. 如果开启了DEBUG模式,那么在代码中如果抛出了异常,在浏览器的页面中可以看到具体的错误信息,以及具体的错误代码位置。方便开发者调试。
2. 如果开启了DEBUG模式,那么以后在`Python`代码中修改了任何代码,只要按`ctrl+s`,`flask`就会自动的重新记载整个网站。不需要手动点击重新运行。

配置DEBUG模式的四种方式:

1. 在`app.run()`中传递一个参数`debug=True`就可以开启`DEBUG`模式
2. 给`app.deubg=True`也可以开启`debug`模式
3. 通过配置参数的形式设置DEBUG模式:`app.config.update(DEBUG=True)`
4. 通过配置文件的形式设置DEBUG模式:`app.config.from_object(config)`

例子:

from flask import Flask

app = Flask(__name__)


@app.route('/')
def hello_world():
    a = 1
    b = 0
    c = a / b
    return 'Hello World!'


if __name__ == '__main__':
    app.run(debug=True)
from flask import Flask

app = Flask(__name__)
app.debug = True

@app.route('/')
def hello_world():
    a = 1
    b = 0
    c = a / b
    return 'Hello World!'


if __name__ == '__main__':
    app.run()
from flask import Flask

app = Flask(__name__)
app.config.update(DEBUG=True)


@app.route('/')
def hello_world():
    a = 1
    b = 0
    c = a / b

    return 'Hello World!'


if __name__ == '__main__':
    app.run()
from flask import Flask
import config  # 配置文件中配置 DEBUG = True

app = Flask(__name__)

app.config.from_object(config)


@app.route('/')
def hello_world():
    a = 1
    b = 0
    c = a / b
    return 'Hello World!'


if __name__ == '__main__':
    app.run()

如过想在网页上调试应该输入PIN码

 

app.config.from_object(config)

from flask import Flask
import config

app = Flask(__name__)

app.config.from_object(config)


@app.route('/')
def hello_world():
    return 'Hello World!'


if __name__ == '__main__':
    app.run()

app.config.from_pyfile('config.py')

from flask import Flask

app = Flask(__name__)

# silent默认是False,设置为True,找不到文件也不报错
app.config.from_pyfile('config.txt', silent=True)



@app.route('/')
def hello_world():
    return 'Hello World!'


if __name__ == '__main__':
    app.run()

URL中两种方式传参

第一种方式:使用path的形式,(将参数嵌入到路径中)

from flask import Flask

app = Flask(__name__)


@app.route('/')
def hello_world():
    return 'Hello World!'


@app.route('/p/<article_id>/')
def article_list(article_id):
    return '您请求的文章是%s' % article_id


if __name__ == '__main__':
    app.run(debug=True)

对整形参数进行约束:

from flask import Flask

app = Flask(__name__)


@app.route('/')
def hello_world():
    return 'Hello World!'


@app.route('/p/<int:article_id>/')
def article_list(article_id):
    return '您请求的文章是%s' % article_id


if __name__ == '__main__':
    app.run(debug=True)

常用类型有以下几种

string:默认的数据类型,接受没有任何斜杠‘\/’的文本。

int:接受整型。

float:接受浮点类型。

path:和string的类似,但是也接受斜杠。

uuid:只接受uuid字符串。uuid可以会保护我们的网站隐私(比如用户量等信息)

from flask import Flask

app = Flask(__name__)


@app.route('/')
def hello_world():
    return 'Hello World!'


@app.route('/p/<uuid:user_id>/')
def user_detail(user_id):
    return '当前用户是%s' % user_id


if __name__ == '__main__':
    app.run(debug=True)

uuid的参数格式如下:

import uuid

print(uuid.uuid4())  # f8cfe681-319b-40d1-9ee2-0f7105acf717  

any:可以指定多种路径,这个通过一个例子进行说明:

from flask import Flask

app = Flask(__name__)


@app.route('/')
def hello_world():
    return 'Hello World!'


@app.route('/<any(blog,user):url_path>/<id>/')
def user_detail(url_path, id):
    if url_path == 'blog':
        return '博客详情%s' % id
    else:
        return '用户详情%s' % id


if __name__ == '__main__':
    app.run(debug=True)

使用查询字符串的方式?key=value

from flask import Flask
from flask import request
app = Flask(__name__)


@app.route('/')
def hello_world():
    return 'Hello World!'


@app.route('/d/')
def d():
    wd = request.args.get('wd')
    return '您通过查询字符串的方式传递的参数是%s'%wd


if __name__ == '__main__':
    app.run(debug=True)

如果你的页面想要做SEO优化,就是被搜索引擎搜索到,那么推荐使用第一种形式。如果不在乎SEO优化就可以使用第二种(查询字符串的方式)

url_for使用详解

1.url_for的基本使用

url_for第一个参数,应该是视图函数的名字的字符串。后面的参数就是传递给url。
如果传递的参数之前在url中已经定义了,那么这个参数就会被当成path的形式给
url。如果这个参数之前没有在url中定义,那么将变成查询字符串的形式放到url中。

from flask import Flask, url_for

app = Flask(__name__)


@app.route('/')
def hello_world():
    return url_for('my_list', page=1, count=2)


@app.route('/list/<page>/')
def my_list(page):
    return 'my_list %s' % page


if __name__ == '__main__':
    app.run(debug=True)

为什么需要url_for?

1. 将来如果修改了URL,但没有修改该URL对应的函数名,就不用到处去替换URL了。
2. url_for会自动的处理那些特殊的字符,不需要手动去处理。

url_for('my_list', page=1, next='/',) # /list/1/?next=%2F

自定义URL转换器

实际上只需要定义BaseConverter的一个子类,并定义规则,再添加到app.url_map.converters里面就可以使用了。

也可以重写BaseConverter的to_python(self, value)和to_url(self, value)方法。

to_python方法的作用:这个方法的返回值,将会传递到view函数中作为参数。

to_url方法的作用:这个方法的返回值,将会调用url_for函数的时候生成符合要求的URL形式。

class BaseConverter(object):

    """Base class for all converters."""
    regex = '[^/]+'
    weight = 100

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

    def to_python(self, value):
        return value

    def to_url(self, value):
        return url_quote(value, charset=self.map.charset)


class UnicodeConverter(BaseConverter):

    """This converter is the default converter and accepts any string but
    only one path segment.  Thus the string can not include a slash.

    This is the default validator.

    Example::

        Rule('/pages/<page>'),
        Rule('/<string(length=2):lang_code>')

    :param map: the :class:`Map`.
    :param minlength: the minimum length of the string.  Must be greater
                      or equal 1.
    :param maxlength: the maximum length of the string.
    :param length: the exact length of the string.
    """

    def __init__(self, map, minlength=1, maxlength=None, length=None):
        BaseConverter.__init__(self, map)
        if length is not None:
            length = '{%d}' % int(length)
        else:
            if maxlength is None:
                maxlength = ''
            else:
                maxlength = int(maxlength)
            length = '{%s,%s}' % (
                int(minlength),
                maxlength
            )
        self.regex = '[^/]' + length


class AnyConverter(BaseConverter):

    """Matches one of the items provided.  Items can either be Python
    identifiers or strings::

        Rule('/<any(about, help, imprint, class, "foo,bar"):page_name>')

    :param map: the :class:`Map`.
    :param items: this function accepts the possible items as positional
                  arguments.
    """

    def __init__(self, map, *items):
        BaseConverter.__init__(self, map)
        self.regex = '(?:%s)' % '|'.join([re.escape(x) for x in items])


class PathConverter(BaseConverter):

    """Like the default :class:`UnicodeConverter`, but it also matches
    slashes.  This is useful for wikis and similar applications::

        Rule('/<path:wikipage>')
        Rule('/<path:wikipage>/edit')

    :param map: the :class:`Map`.
    """
    regex = '[^/].*?'
    weight = 200


class NumberConverter(BaseConverter):

    """Baseclass for `IntegerConverter` and `FloatConverter`.

    :internal:
    """
    weight = 50

    def __init__(self, map, fixed_digits=0, min=None, max=None):
        BaseConverter.__init__(self, map)
        self.fixed_digits = fixed_digits
        self.min = min
        self.max = max

    def to_python(self, value):
        if (self.fixed_digits and len(value) != self.fixed_digits):
            raise ValidationError()
        value = self.num_convert(value)
        if (self.min is not None and value < self.min) or \
           (self.max is not None and value > self.max):
            raise ValidationError()
        return value

    def to_url(self, value):
        value = self.num_convert(value)
        if self.fixed_digits:
            value = ('%%0%sd' % self.fixed_digits) % value
        return str(value)


class IntegerConverter(NumberConverter):

    """This converter only accepts integer values::

        Rule('/page/<int:page>')

    This converter does not support negative values.

    :param map: the :class:`Map`.
    :param fixed_digits: the number of fixed digits in the URL.  If you set
                         this to ``4`` for example, the application will
                         only match if the url looks like ``/0001/``.  The
                         default is variable length.
    :param min: the minimal value.
    :param max: the maximal value.
    """
    regex = r'\d+'
    num_convert = int


class FloatConverter(NumberConverter):

    """This converter only accepts floating point values::

        Rule('/probability/<float:probability>')

    This converter does not support negative values.

    :param map: the :class:`Map`.
    :param min: the minimal value.
    :param max: the maximal value.
    """
    regex = r'\d+\.\d+'
    num_convert = float

    def __init__(self, map, min=None, max=None):
        NumberConverter.__init__(self, map, 0, min, max)


class UUIDConverter(BaseConverter):

    """This converter only accepts UUID strings::

        Rule('/object/<uuid:identifier>')

    .. versionadded:: 0.10

    :param map: the :class:`Map`.
    """
    regex = r'[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-' \
            r'[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}'

    def to_python(self, value):
        return uuid.UUID(value)

    def to_url(self, value):
        return str(value)


#: the default converter mapping for the map.
DEFAULT_CONVERTERS = {
    'default':          UnicodeConverter,
    'string':           UnicodeConverter,
    'any':              AnyConverter,
    'path':             PathConverter,
    'int':              IntegerConverter,
    'float':            FloatConverter,
    'uuid':             UUIDConverter,
}
部分源代码

如下例子,在一个url中,含有手机号码的变量,必须限定变量的字符串格式满足手机号码的格式,定义一个TelephoneConverter类

from flask import Flask
from werkzeug.routing import BaseConverter

app = Flask(__name__)


# 在一个url中,含有手机号码的变量,必须限定变量的字符串格式满足手机号码的格式
class TelephoneConverter(BaseConverter):
    regex = r'1[85734]\d{9}'


app.url_map.converters['tel'] = TelephoneConverter


@app.route('/')
def hello_world():
    return 'hello world'


@app.route('/telephone/<tel:my_tel>/')
def my_tel(my_tel):
    return '您的电话号码是 %s' % my_tel


if __name__ == '__main__':
    app.run(debug=True)

另一个例子,重写BaseConverter的to_python(self, value)和to_url(self, value)方法;

from flask import Flask, url_for
from werkzeug.routing import BaseConverter

app = Flask(__name__)


class ListConverter(BaseConverter):
    def to_python(self, value):
        return value.split('+')

    def to_url(self, value):
        return '+'.join(value)


app.url_map.converters['list'] = ListConverter


@app.route('/')
def hello_world():
    print(url_for('posts', boards=['a', 'b']))  # /posts/a+b/
    return 'hello world'


@app.route('/posts/<list:boards>/')
def posts(boards):
    return '您提交的模块是 %s' % boards


# 访问 http://127.0.0.1:5000/posts/a+b/
# 您提交的模块是 ['a', 'b']

if __name__ == '__main__':
    app.run(debug=True)

必会的小细节知识点

1.在局域网中让其他电脑访问我的网站

如果想在同一个局域网下的其他电脑访问自己电脑上的Flask网站,
那么可以设置`host='0.0.0.0'`才能访问得到。

2.指定端口号

Flask项目,默认使用`5000`端口。如果想更换端口,那么可以设置`port=9000`。

3. url唯一

在定义url的时候,一定要记得在最后加一个斜杠。
1. 如果不加斜杠,那么在浏览器中访问这个url的时候,如果最后加了斜杠,那么就访问不到。这样用户体验不太好。
2. 搜索引擎会将不加斜杠的和加斜杠的视为两个不同的url。而其实加和不加斜杠的都是同一个url,那么就会给搜索引擎造成一个误解。加了斜杠,就不会出现没有斜杠的情况。

@app.route('/hello/')
def hello():
    return 'hello 0bug'

访问http://127.0.0.1:5000/hello 会被重定向到http://127.0.0.1:5000/hello/

4.GET请求和POST请求

在网络请求中有许多请求方式,比如:GET、POST、DELETE、PUT请求等。那么最常用的就是`GET`和`POST`请求了。
1. `GET`请求:只会在服务器上获取资源,不会更改服务器的状态。这种请求方式推荐使用`GET`请求。
2. `POST`请求:会给服务器提交一些数据或者文件。一般POST请求是会对服务器的状态产生影响,那么这种请求推荐使用POST请求。
3. 关于参数传递:
 `GET`请求:把参数放到`url`中,通过`?xx=xxx`的形式传递的。因为会把参数放到url中,所以如果视力好,一眼就能看到你传递给服务器的参数。这样不太安全。
 `POST`请求:把参数放到`Form Data`中。会把参数放到`Form Data`中,避免了被偷瞄的风险,但是如果别人想要偷看你的密码,那么其实可以通过抓包的形式。因为POST请求可以提交一些数据给服务器,比如可以发送文件,那么这就增加了很大的风险。所以POST请求,对于那些有经验的黑客来讲,其实是更不安全的。

4. 在`Flask`中,`route`方法,默认将只能使用`GET`的方式请求这个url,如果想要设置自己的请求方式,那么应该传递一个`methods`参数。

关于响应(Response)

返回值可以是什么类型?

from flask import Flask

app = Flask(__name__)


@app.route('/')
def hello_world():
    return {'name':'0bug'}


if __name__ == '__main__':
    app.run(debug=True)

from flask import Flask

app = Flask(__name__)


@app.route('/')
def hello_world():
    return ['lcg','0bug']


if __name__ == '__main__':
    app.run(debug=True)

那么什么样的类型能被视图函数返回?

部分源码:

class Response(ResponseBase):
    """The response object that is used by default in Flask.  Works like the
    response object from Werkzeug but is set to have an HTML mimetype by
    default.  Quite often you don't have to create this object yourself because
    :meth:`~flask.Flask.make_response` will take care of that for you.

    If you want to replace the response object used you can subclass this and
    set :attr:`~flask.Flask.response_class` to your subclass.
    """
    default_mimetype = 'text/html'
Response
class Response(BaseResponse, ETagResponseMixin, ResponseStreamMixin,
               CommonResponseDescriptorsMixin,
               WWWAuthenticateMixin):

    """Full featured response object implementing the following mixins:

    - :class:`ETagResponseMixin` for etag and cache control handling
    - :class:`ResponseStreamMixin` to add support for the `stream` property
    - :class:`CommonResponseDescriptorsMixin` for various HTTP descriptors
    - :class:`WWWAuthenticateMixin` for HTTP authentication support
    """
ResponseBase
class BaseResponse(object):

    """Base response class.  The most important fact about a response object
    is that it's a regular WSGI application.  It's initialized with a couple
    of response parameters (headers, body, status code etc.) and will start a
    valid WSGI response when called with the environ and start response
    callable.

    Because it's a WSGI application itself processing usually ends before the
    actual response is sent to the server.  This helps debugging systems
    because they can catch all the exceptions before responses are started.

    Here a small example WSGI application that takes advantage of the
    response objects::

        from werkzeug.wrappers import BaseResponse as Response

        def index():
            return Response('Index page')

        def application(environ, start_response):
            path = environ.get('PATH_INFO') or '/'
            if path == '/':
                response = index()
            else:
                response = Response('Not Found', status=404)
            return response(environ, start_response)

    Like :class:`BaseRequest` which object is lacking a lot of functionality
    implemented in mixins.  This gives you a better control about the actual
    API of your response objects, so you can create subclasses and add custom
    functionality.  A full featured response object is available as
    :class:`Response` which implements a couple of useful mixins.

    To enforce a new type of already existing responses you can use the
    :meth:`force_type` method.  This is useful if you're working with different
    subclasses of response objects and you want to post process them with a
    known interface.

    Per default the response object will assume all the text data is `utf-8`
    encoded.  Please refer to `the unicode chapter <unicode.txt>`_ for more
    details about customizing the behavior.

    Response can be any kind of iterable or string.  If it's a string it's
    considered being an iterable with one item which is the string passed.
    Headers can be a list of tuples or a
    :class:`~werkzeug.datastructures.Headers` object.

    Special note for `mimetype` and `content_type`:  For most mime types
    `mimetype` and `content_type` work the same, the difference affects
    only 'text' mimetypes.  If the mimetype passed with `mimetype` is a
    mimetype starting with `text/`, the charset parameter of the response
    object is appended to it.  In contrast the `content_type` parameter is
    always added as header unmodified.

    .. versionchanged:: 0.5
       the `direct_passthrough` parameter was added.

    :param response: a string or response iterable.
    :param status: a string with a status or an integer with the status code.
    :param headers: a list of headers or a
                    :class:`~werkzeug.datastructures.Headers` object.
    :param mimetype: the mimetype for the response.  See notice above.
    :param content_type: the content type for the response.  See notice above.
    :param direct_passthrough: if set to `True` :meth:`iter_encoded` is not
                               called before iteration which makes it
                               possible to pass special iterators through
                               unchanged (see :func:`wrap_file` for more
                               details.)
    """

    #: the charset of the response.
    charset = 'utf-8'

    #: the default status if none is provided.
    default_status = 200

    #: the default mimetype if none is provided.
    default_mimetype = 'text/plain'

    #: if set to `False` accessing properties on the response object will
    #: not try to consume the response iterator and convert it into a list.
    #:
    #: .. versionadded:: 0.6.2
    #:
    #:    That attribute was previously called `implicit_seqence_conversion`.
    #:    (Notice the typo).  If you did use this feature, you have to adapt
    #:    your code to the name change.
    implicit_sequence_conversion = True

    #: Should this response object correct the location header to be RFC
    #: conformant?  This is true by default.
    #:
    #: .. versionadded:: 0.8
    autocorrect_location_header = True

    #: Should this response object automatically set the content-length
    #: header if possible?  This is true by default.
    #:
    #: .. versionadded:: 0.8
    automatically_set_content_length = True

    #: Warn if a cookie header exceeds this size. The default, 4093, should be
    #: safely `supported by most browsers <cookie_>`_. A cookie larger than
    #: this size will still be sent, but it may be ignored or handled
    #: incorrectly by some browsers. Set to 0 to disable this check.
    #:
    #: .. versionadded:: 0.13
    #:
    #: .. _`cookie`: http://browsercookielimits.squawky.net/
    max_cookie_size = 4093

    def __init__(self, response=None, status=None, headers=None,
                 mimetype=None, content_type=None, direct_passthrough=False):
        if isinstance(headers, Headers):
            self.headers = headers
        elif not headers:
            self.headers = Headers()
        else:
            self.headers = Headers(headers)

        if content_type is None:
            if mimetype is None and 'content-type' not in self.headers:
                mimetype = self.default_mimetype
            if mimetype is not None:
                mimetype = get_content_type(mimetype, self.charset)
            content_type = mimetype
        if content_type is not None:
            self.headers['Content-Type'] = content_type
        if status is None:
            status = self.default_status
        if isinstance(status, integer_types):
            self.status_code = status
        else:
            self.status = status

        self.direct_passthrough = direct_passthrough
        self._on_close = []

        # we set the response after the headers so that if a class changes
        # the charset attribute, the data is set in the correct charset.
        if response is None:
            self.response = []
        elif isinstance(response, (text_type, bytes, bytearray)):
            self.set_data(response)
        else:
            self.response = response

    def call_on_close(self, func):
        """Adds a function to the internal list of functions that should
        be called as part of closing down the response.  Since 0.7 this
        function also returns the function that was passed so that this
        can be used as a decorator.

        .. versionadded:: 0.6
        """
        self._on_close.append(func)
        return func

    def __repr__(self):
        if self.is_sequence:
            body_info = '%d bytes' % sum(map(len, self.iter_encoded()))
        else:
            body_info = 'streamed' if self.is_streamed else 'likely-streamed'
        return '<%s %s [%s]>' % (
            self.__class__.__name__,
            body_info,
            self.status
        )

    @classmethod
    def force_type(cls, response, environ=None):
        """Enforce that the WSGI response is a response object of the current
        type.  Werkzeug will use the :class:`BaseResponse` internally in many
        situations like the exceptions.  If you call :meth:`get_response` on an
        exception you will get back a regular :class:`BaseResponse` object, even
        if you are using a custom subclass.

        This method can enforce a given response type, and it will also
        convert arbitrary WSGI callables into response objects if an environ
        is provided::

            # convert a Werkzeug response object into an instance of the
            # MyResponseClass subclass.
            response = MyResponseClass.force_type(response)

            # convert any WSGI application into a response object
            response = MyResponseClass.force_type(response, environ)

        This is especially useful if you want to post-process responses in
        the main dispatcher and use functionality provided by your subclass.

        Keep in mind that this will modify response objects in place if
        possible!

        :param response: a response object or wsgi application.
        :param environ: a WSGI environment object.
        :return: a response object.
        """
        if not isinstance(response, BaseResponse):
            if environ is None:
                raise TypeError('cannot convert WSGI application into '
                                'response objects without an environ')
            response = BaseResponse(*_run_wsgi_app(response, environ))
        response.__class__ = cls
        return response

    @classmethod
    def from_app(cls, app, environ, buffered=False):
        """Create a new response object from an application output.  This
        works best if you pass it an application that returns a generator all
        the time.  Sometimes applications may use the `write()` callable
        returned by the `start_response` function.  This tries to resolve such
        edge cases automatically.  But if you don't get the expected output
        you should set `buffered` to `True` which enforces buffering.

        :param app: the WSGI application to execute.
        :param environ: the WSGI environment to execute against.
        :param buffered: set to `True` to enforce buffering.
        :return: a response object.
        """
        return cls(*_run_wsgi_app(app, environ, buffered))

    def _get_status_code(self):
        return self._status_code

    def _set_status_code(self, code):
        self._status_code = code
        try:
            self._status = '%d %s' % (code, HTTP_STATUS_CODES[code].upper())
        except KeyError:
            self._status = '%d UNKNOWN' % code
    status_code = property(_get_status_code, _set_status_code,
                           doc='The HTTP Status code as number')
    del _get_status_code, _set_status_code

    def _get_status(self):
        return self._status

    def _set_status(self, value):
        try:
            self._status = to_native(value)
        except AttributeError:
            raise TypeError('Invalid status argument')

        try:
            self._status_code = int(self._status.split(None, 1)[0])
        except ValueError:
            self._status_code = 0
            self._status = '0 %s' % self._status
        except IndexError:
            raise ValueError('Empty status argument')
    status = property(_get_status, _set_status, doc='The HTTP Status code')
    del _get_status, _set_status

    def get_data(self, as_text=False):
        """The string representation of the request body.  Whenever you call
        this property the request iterable is encoded and flattened.  This
        can lead to unwanted behavior if you stream big data.

        This behavior can be disabled by setting
        :attr:`implicit_sequence_conversion` to `False`.

        If `as_text` is set to `True` the return value will be a decoded
        unicode string.

        .. versionadded:: 0.9
        """
        self._ensure_sequence()
        rv = b''.join(self.iter_encoded())
        if as_text:
            rv = rv.decode(self.charset)
        return rv

    def set_data(self, value):
        """Sets a new string as response.  The value set must either by a
        unicode or bytestring.  If a unicode string is set it's encoded
        automatically to the charset of the response (utf-8 by default).

        .. versionadded:: 0.9
        """
        # if an unicode string is set, it's encoded directly so that we
        # can set the content length
        if isinstance(value, text_type):
            value = value.encode(self.charset)
        else:
            value = bytes(value)
        self.response = [value]
        if self.automatically_set_content_length:
            self.headers['Content-Length'] = str(len(value))

    data = property(get_data, set_data, doc='''
        A descriptor that calls :meth:`get_data` and :meth:`set_data`.  This
        should not be used and will eventually get deprecated.
        ''')

    def calculate_content_length(self):
        """Returns the content length if available or `None` otherwise."""
        try:
            self._ensure_sequence()
        except RuntimeError:
            return None
        return sum(len(x) for x in self.iter_encoded())

    def _ensure_sequence(self, mutable=False):
        """This method can be called by methods that need a sequence.  If
        `mutable` is true, it will also ensure that the response sequence
        is a standard Python list.

        .. versionadded:: 0.6
        """
        if self.is_sequence:
            # if we need a mutable object, we ensure it's a list.
            if mutable and not isinstance(self.response, list):
                self.response = list(self.response)
            return
        if self.direct_passthrough:
            raise RuntimeError('Attempted implicit sequence conversion '
                               'but the response object is in direct '
                               'passthrough mode.')
        if not self.implicit_sequence_conversion:
            raise RuntimeError('The response object required the iterable '
                               'to be a sequence, but the implicit '
                               'conversion was disabled.  Call '
                               'make_sequence() yourself.')
        self.make_sequence()

    def make_sequence(self):
        """Converts the response iterator in a list.  By default this happens
        automatically if required.  If `implicit_sequence_conversion` is
        disabled, this method is not automatically called and some properties
        might raise exceptions.  This also encodes all the items.

        .. versionadded:: 0.6
        """
        if not self.is_sequence:
            # if we consume an iterable we have to ensure that the close
            # method of the iterable is called if available when we tear
            # down the response
            close = getattr(self.response, 'close', None)
            self.response = list(self.iter_encoded())
            if close is not None:
                self.call_on_close(close)

    def iter_encoded(self):
        """Iter the response encoded with the encoding of the response.
        If the response object is invoked as WSGI application the return
        value of this method is used as application iterator unless
        :attr:`direct_passthrough` was activated.
        """
        if __debug__:
            _warn_if_string(self.response)
        # Encode in a separate function so that self.response is fetched
        # early.  This allows us to wrap the response with the return
        # value from get_app_iter or iter_encoded.
        return _iter_encoded(self.response, self.charset)

    def set_cookie(self, key, value='', max_age=None, expires=None,
                   path='/', domain=None, secure=False, httponly=False,
                   samesite=None):
        """Sets a cookie. The parameters are the same as in the cookie `Morsel`
        object in the Python standard library but it accepts unicode data, too.

        A warning is raised if the size of the cookie header exceeds
        :attr:`max_cookie_size`, but the header will still be set.

        :param key: the key (name) of the cookie to be set.
        :param value: the value of the cookie.
        :param max_age: should be a number of seconds, or `None` (default) if
                        the cookie should last only as long as the client's
                        browser session.
        :param expires: should be a `datetime` object or UNIX timestamp.
        :param path: limits the cookie to a given path, per default it will
                     span the whole domain.
        :param domain: if you want to set a cross-domain cookie.  For example,
                       ``domain=".example.com"`` will set a cookie that is
                       readable by the domain ``www.example.com``,
                       ``foo.example.com`` etc.  Otherwise, a cookie will only
                       be readable by the domain that set it.
        :param secure: If `True`, the cookie will only be available via HTTPS
        :param httponly: disallow JavaScript to access the cookie.  This is an
                         extension to the cookie standard and probably not
                         supported by all browsers.
        :param samesite: Limits the scope of the cookie such that it will only
                         be attached to requests if those requests are
                         "same-site".
        """
        self.headers.add('Set-Cookie', dump_cookie(
            key,
            value=value,
            max_age=max_age,
            expires=expires,
            path=path,
            domain=domain,
            secure=secure,
            httponly=httponly,
            charset=self.charset,
            max_size=self.max_cookie_size,
            samesite=samesite
        ))

    def delete_cookie(self, key, path='/', domain=None):
        """Delete a cookie.  Fails silently if key doesn't exist.

        :param key: the key (name) of the cookie to be deleted.
        :param path: if the cookie that should be deleted was limited to a
                     path, the path has to be defined here.
        :param domain: if the cookie that should be deleted was limited to a
                       domain, that domain has to be defined here.
        """
        self.set_cookie(key, expires=0, max_age=0, path=path, domain=domain)

    @property
    def is_streamed(self):
        """If the response is streamed (the response is not an iterable with
        a length information) this property is `True`.  In this case streamed
        means that there is no information about the number of iterations.
        This is usually `True` if a generator is passed to the response object.

        This is useful for checking before applying some sort of post
        filtering that should not take place for streamed responses.
        """
        try:
            len(self.response)
        except (TypeError, AttributeError):
            return True
        return False

    @property
    def is_sequence(self):
        """If the iterator is buffered, this property will be `True`.  A
        response object will consider an iterator to be buffered if the
        response attribute is a list or tuple.

        .. versionadded:: 0.6
        """
        return isinstance(self.response, (tuple, list))

    def close(self):
        """Close the wrapped response if possible.  You can also use the object
        in a with statement which will automatically close it.

        .. versionadded:: 0.9
           Can now be used in a with statement.
        """
        if hasattr(self.response, 'close'):
            self.response.close()
        for func in self._on_close:
            func()

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, tb):
        self.close()

    def freeze(self):
        """Call this method if you want to make your response object ready for
        being pickled.  This buffers the generator if there is one.  It will
        also set the `Content-Length` header to the length of the body.

        .. versionchanged:: 0.6
           The `Content-Length` header is now set.
        """
        # we explicitly set the length to a list of the *encoded* response
        # iterator.  Even if the implicit sequence conversion is disabled.
        self.response = list(self.iter_encoded())
        self.headers['Content-Length'] = str(sum(map(len, self.response)))

    def get_wsgi_headers(self, environ):
        """This is automatically called right before the response is started
        and returns headers modified for the given environment.  It returns a
        copy of the headers from the response with some modifications applied
        if necessary.

        For example the location header (if present) is joined with the root
        URL of the environment.  Also the content length is automatically set
        to zero here for certain status codes.

        .. versionchanged:: 0.6
           Previously that function was called `fix_headers` and modified
           the response object in place.  Also since 0.6, IRIs in location
           and content-location headers are handled properly.

           Also starting with 0.6, Werkzeug will attempt to set the content
           length if it is able to figure it out on its own.  This is the
           case if all the strings in the response iterable are already
           encoded and the iterable is buffered.

        :param environ: the WSGI environment of the request.
        :return: returns a new :class:`~werkzeug.datastructures.Headers`
                 object.
        """
        headers = Headers(self.headers)
        location = None
        content_location = None
        content_length = None
        status = self.status_code

        # iterate over the headers to find all values in one go.  Because
        # get_wsgi_headers is used each response that gives us a tiny
        # speedup.
        for key, value in headers:
            ikey = key.lower()
            if ikey == u'location':
                location = value
            elif ikey == u'content-location':
                content_location = value
            elif ikey == u'content-length':
                content_length = value

        # make sure the location header is an absolute URL
        if location is not None:
            old_location = location
            if isinstance(location, text_type):
                # Safe conversion is necessary here as we might redirect
                # to a broken URI scheme (for instance itms-services).
                location = iri_to_uri(location, safe_conversion=True)

            if self.autocorrect_location_header:
                current_url = get_current_url(environ, root_only=True)
                if isinstance(current_url, text_type):
                    current_url = iri_to_uri(current_url)
                location = url_join(current_url, location)
            if location != old_location:
                headers['Location'] = location

        # make sure the content location is a URL
        if content_location is not None and \
           isinstance(content_location, text_type):
            headers['Content-Location'] = iri_to_uri(content_location)

        if status in (304, 412):
            remove_entity_headers(headers)

        # if we can determine the content length automatically, we
        # should try to do that.  But only if this does not involve
        # flattening the iterator or encoding of unicode strings in
        # the response.  We however should not do that if we have a 304
        # response.
        if self.automatically_set_content_length and \
           self.is_sequence and content_length is None and \
           status not in (204, 304) and \
           not (100 <= status < 200):
            try:
                content_length = sum(len(to_bytes(x, 'ascii'))
                                     for x in self.response)
            except UnicodeError:
                # aha, something non-bytestringy in there, too bad, we
                # can't safely figure out the length of the response.
                pass
            else:
                headers['Content-Length'] = str(content_length)

        return headers

    def get_app_iter(self, environ):
        """Returns the application iterator for the given environ.  Depending
        on the request method and the current status code the return value
        might be an empty response rather than the one from the response.

        If the request method is `HEAD` or the status code is in a range
        where the HTTP specification requires an empty response, an empty
        iterable is returned.

        .. versionadded:: 0.6

        :param environ: the WSGI environment of the request.
        :return: a response iterable.
        """
        status = self.status_code
        if environ['REQUEST_METHOD'] == 'HEAD' or \
           100 <= status < 200 or status in (204, 304, 412):
            iterable = ()
        elif self.direct_passthrough:
            if __debug__:
                _warn_if_string(self.response)
            return self.response
        else:
            iterable = self.iter_encoded()
        return ClosingIterator(iterable, self.close)

    def get_wsgi_response(self, environ):
        """Returns the final WSGI response as tuple.  The first item in
        the tuple is the application iterator, the second the status and
        the third the list of headers.  The response returned is created
        specially for the given environment.  For example if the request
        method in the WSGI environment is ``'HEAD'`` the response will
        be empty and only the headers and status code will be present.

        .. versionadded:: 0.6

        :param environ: the WSGI environment of the request.
        :return: an ``(app_iter, status, headers)`` tuple.
        """
        headers = self.get_wsgi_headers(environ)
        app_iter = self.get_app_iter(environ)
        return app_iter, self.status, headers.to_wsgi_list()

    def __call__(self, environ, start_response):
        """Process this response as WSGI application.

        :param environ: the WSGI environment.
        :param start_response: the response callable provided by the WSGI
                               server.
        :return: an application iterator
        """
        app_iter, status, headers = self.get_wsgi_response(environ)
        start_response(status, headers)
        return app_iter
BaseResponse

视图函数的返回值会被自动转换为一个响应对象,Flask的转换逻辑如下:

  • 如果返回的是一个合法的响应对象,则直接返回。

  • 如果返回的是一个字符串,那么Flask会重新创建一个werkzeug.wrappers.Response对象,Response将该字符串作为主体,状态码为200,MIME类型为text/html,然后返回该Response对象。

  • 如果返回的是一个元组,元祖中的数据类型是(response,status,headers)。status值会覆盖默认的200状态码,headers可以是一个列表或者字典,作为额外的消息头。

  • 如果以上条件都不满足,Flask会假设返回值是一个合法的WSGIt应用程序,并通过Response.force_type(rv,request.environ)转换为一个请求对象。

以下将用例子来进行说明:

第一个例子:直接使用Response创建:

from flask import Flask, Response
# from werkzeug.wrappers import Response

app = Flask(__name__)


@app.route('/')
def hello():
    res = Response('hello 0bug',status=200,mimetype='text/html')
    return res

第二个例子:可以使用make_response函数来创建Response对象,这个方法可以设置额外的数据,比如设置cookie,header信息等:

from flask import make_response

 @app.route('/about/')
 def about():
     return make_response('about page')

第三个例子:通过返回元组的形式:

@app.errorhandler(404)
 def not_found():
     return 'not found',404

 可以返回两个也可以返回三个:

@app.route('/lcg/')
def lcg():
    return 'lcg', 666, {'tou': '0bug'}

这个响应头可以用于token认证。

第四个例子:自定义响应。自定义响应必须满足三个条件:

  • 必须继承自Response类。

  • 实现类方法force_type(cls,rv,environ=None)

  • 必须指定app.response_class为你自定义的Response

以下将用一个例子来进行讲解,Restful API都是通过JSON的形式进行传递,如果你的后台跟前台进行交互,所有的URL都是发送JSON数据,那么此时你可以自定义一个叫做JSONResponse的类来代替Flask自带的Response类来返回一个字典形式的JSON数据:

from flask import Flask, Response, jsonify

app = Flask(__name__)


class JSONResponse(Response):

    @classmethod
    def force_type(cls, response, environ=None):
        """
        这个方法只有视图函数返回非字符串、非元祖、非Response对象时才会调用
        """
        if isinstance(response, dict):
            response = jsonify(response)
        return super().force_type(response, environ)


app.response_class = JSONResponse


@app.route('/')
def hello():
    return {'name': '0bug'}


if __name__ == '__main__':
    app.run(debug=True)

注意以上例子,如果不写app.response_class = JSONResponse,将不能正确的将字典返回给客户端。因为字典不在Flask的响应类型支持范围中,那么将调用app.response_class这个属性的force_type类方法,而app.response_class的默认值为Response,因此会调用Response.force_class()这个类方法,他有一个默认的算法转换成字符串,但是这个算法不能满足我们的需求。因此,我们要设置app.response_class=JSONResponse,然后重写JSONResponse中的force_type类方法,在这个方法中将字典转换成JSON格式的字符串后再返回。

这里如果只是将response转换为json而不是response对象,那么也会报错,所有切记这里返回的是一个response对象。

import json

class JSONResponse(Response):

    @classmethod
    def force_type(cls, response, environ=None):
        if isinstance(response, dict):
            response = json.dumps(response)
        return super().force_type(response, environ)

 

模板简介

在之前的例子中,视图函数只是直接返回文本,而在实际生产环境中其实很少这样用,因为实际的页面大多是带有样式和复杂逻辑的HTML代码,这可以让浏览器渲染出非常漂亮的页面。目前市面上有非常多的模板系统,其中最知名最好用的就是Jinja2Mako,我们先来看一下这两个模板的特点和不同:

  1. Jinja2:Jinja是日本寺庙的意思,并且寺庙的英文是temple和模板的英文template的发音类似。Jinja2是默认的仿Django模板的一个模板引擎,由Flask的作者开发。它速度快,被广泛使用,并且提供了可选的沙箱模板来保证执行环境的安全,它有以下优点:

    • 让前端开发者和后端开发者工作分离。
    • 减少Flask项目代码的耦合性,页面逻辑放在模板中,业务逻辑放在视图函数中,将页面逻辑和业务逻辑解耦有利于代码的维护。
    • 提供了控制语句、继承等高级功能,减少开发的复杂度。
  2. Marko:Marko是另一个知名的模板。他从DjangoJinja2等模板借鉴了很多语法和API,他有以下优点:

    • 性能和Jinja2相近,在这里可以看到。
    • 有大型网站在使用,有成功的案例。Reddit和豆瓣都在使用。
    • 有知名的web框架支持。PylonsPyramid这两个web框架内置模板就是Mako
    • 支持在模板中写几乎原生的Python语法的代码,对Python工程师比较友好,开发效率高。
    • 自带完整的缓存系统。当然也提供了非常好的扩展借口,很容易切换成其他的缓存系统。

Flask渲染Jinja模板:

要渲染一个模板,通过render_template方法即可,以下将用一个简单的例子进行讲解:

from flask import Flask, render_template

app = Flask(__name__)


@app.route('/about/')
def about():
    return render_template('about.html')

当访问/about/的时候,about()函数会在当前目录下的templates文件夹下寻找about.html模板文件。如果想更改模板文件地址,应该在创建app的时候,给Flask传递一个关键字参数template_folder,指定具体的路径,再看以下例子:

from flask import Flask,render_template
     app = Flask(__name__,template_folder=r'C:\templates')

     @app.route('/about/')
     def about():
         return render_template('about.html')

以上例子将会在C盘的templates文件夹中寻找模板文件。

模板中参数传递

1. 在使用`render_template`渲染模版的时候,可以传递关键字参数。以后直接在模版中使用就可以了。
2. 如果你的参数过多,那么可以将所有的参数放到一个字典中,然后在传这个字典参数的时候,使用两个星号,将字典打散成关键参数。

from flask import Flask,render_template

app = Flask(__name__)


@app.route('/')
def hello_world():
    context = {
        'username':'lcg',
        'age': 25,
        'country': 'china',
        'childrens': {
            'name':'abc',
            'height': 180
        }
    }
    return render_template('index.html',**context)


if __name__ == '__main__':
    app.run(debug=True)

index.html:

<body>
    这是从模版中渲染的数据
    <p>{{ username }}</p>
    <p>{{ age }}</p>
    <p>{{ country }}</p>
    <p>{{ childrens.name }}</p>
    <p>{{ childrens['name'] }}</p>
</body>

模板中反向解析URL

模版中的url_for跟我们后台视图函数中`url_for使用起来基本是一模一样的。也是传递视图函数的名字,也可以传递参数。

<a href="{{ url_for('login',ref='/',id='1') }}">登录</a>
<a href="{{ url_for('hello_world') }}">hello</a>

 views.py

@app.route('/accounts/login/<id>/')
def login(id):
    return render_template('login.html')

Jinja2模版过滤器

1.什么是过滤器,语法是什么

1. 有时候我们想要在模版中对一些变量进行处理,那么就必须需要类似于Python中的函数一样,可以将这个值传到函数中,然后做一些操作。在模版中,过滤器相当于是一个函数,把当前的变量传入到过滤器中,然后过滤器根据自己的功能,再返回相应的值,之后再将结果渲染到页面中。
2. 基本语法:`{{ variable|过滤器名字 }}`。使用管道符号`|`进行组合。

2.常用过滤器

default过滤器:

使用方式{{ value|default('默认值') }}。如果value这个key不存在,那么就会使用default过滤器提供的默认值。如果你想使用类似于python中判断一个值是否为False(例如:None、空字符串、空列表、空字典等),那么就必须要传递另外一个参数{{ value|default('默认值',boolean=True) }}。
可以使用or来替代default('默认值',boolean=True)。例如:{{ signature or '此人很懒,没有留下任何说明' }}。

自动转义过滤器:

  1. safe过滤器:可以关闭一个字符串的自动转义。
  2. escape过滤器:对某一个字符串进行转义。
  3. autoescape标签,可以对他里面的代码块关闭或开启自动转义。
@app.route('/')
def hello_world():
    alert = '<script>alert("hello")</script>'
    return render_template('index.html', alert=alert)


<body>
{{ alert }}
</body>

{{ alert|safe }}

下面用法也可以关闭转义

{% autoescape off %}
    {{ alert}}
 {% endautoescape %}
或
 {% autoescape on %}
    {{ alert}}
 {% endautoescape %}
结果一样,同safe过滤器

开启转义:

{% autoescape off %}
    {{ alert|escape}}
 {% endautoescape %}
或
 {% autoescape on %}
    {{ alert|escape}}
 {% endautoescape %}

常用过滤器:

  • first(value):返回一个序列的第一个元素。names|first。
  • format(value,arags,*kwargs):格式化字符串。例如以下代码:
{{ "%s" - "%s"|format('Hello?',"Foo!") }}

将输出:Helloo? - Foo!

  • last(value):返回一个序列的最后一个元素。示例:names|last。
  • length(value):返回一个序列或者字典的长度。示例:names|length。
  • join(value,d=u''):将一个序列用d这个参数的值拼接成字符串。
  • safe(value):如果开启了全局转义,那么safe过滤器会将变量关掉转义。示例:content_html|safe。
  • int(value):将值转换为int类型。
  • float(value):将值转换为float类型。
  • lower(value):将字符串转换为小写。
  • upper(value):将字符串转换为小写。
  • replace(value,old,new): 替换将old替换为new的字符串。
  • truncate(value,length=255,killwords=False):截取length长度的字符串。
  • striptags(value):删除字符串中所有的HTML标签,如果出现多个空格,将替换成一个空格。
  • trim:截取字符串前面和后面的空白字符。
  • string(value):将变量转换成字符串。
  • wordcount(s):计算一个长字符串中单词的个数。

自定义模版过滤器:

过滤器本质上就是一个函数。自定义过滤器需要被注册到jinja2模板当中。@app.template_filter('cut')中cut是以后要用的过滤器的名字。这句话就是将自定义过滤器需注册到jinja2模板当中。

from flask import Flask, render_template

app = Flask(__name__)


@app.template_filter('cut')
def my_cut(value):
    value = value.replace('lcg', '-')
    return value


@app.route('/')
def hello_world():
    val = 'hellolcglcg0bug'
    return render_template('index.html', val=val)


if __name__ == '__main__':
    app.run(debug=True)



<body>

{{ val|cut }}

</body>

自定义时间处理过滤器

 

@app.route('/')
def index():
    context = {
        'create_time': datetime(2017,10,20,16,19,0)
    }
    return render_template('index.html',**context)

@app.template_filter('cut')
def cut(value):
    value = value.replace("hello",'')
    return value

@app.template_filter('handle_time')
def handle_time(time):
    """
    time距离现在的时间间隔
    1. 如果时间间隔小于1分钟以内,那么就显示“刚刚”
    2. 如果是大于1分钟小于1小时,那么就显示“xx分钟前”
    3. 如果是大于1小时小于24小时,那么就显示“xx小时前”
    4. 如果是大于24小时小于30天以内,那么就显示“xx天前”
    5. 否则就是显示具体的时间 2017/10/20 16:15
    """
    if isinstance(time,datetime):
        now = datetime.now()
        timestamp = (now - time).total_seconds()
        if timestamp < 60:
            return "刚刚"
        elif timestamp>=60 and timestamp < 60*60:
            minutes = timestamp / 60
            return "%s分钟前" % int(minutes)
        elif timestamp >= 60*60 and timestamp < 60*60*24:
            hours = timestamp / (60*60)
            return '%s小时前' % int(hours)
        elif timestamp >= 60*60*24 and timestamp < 60*60*24*30:
            days = timestamp / (60*60*24)
            return "%s天前" % int(days)
        else:
            return time.strftime('%Y/%m/%d %H:%M')
    else:
        return time
<p>发表时间:{{ create_time|handle_time }}</p> 

模板控制语句

所有的控制语句都是放在{% ... %}中,并且有一个语句{% endxxx %}来进行结束,Jinja中常用的控制语句有if/for..in..,现对他们进行讲解:

  1. if:if语句和python中的类似,可以使用>,<,<=,>=,==,!=来进行判断,也可以通过and,or,not,()来进行逻辑合并操作,以下看例子:

{% if kenny.sick %}
   Kenny is sick.
{% elif kenny.dead %}
 You killed Kenny!  You bastard!!!
{% else %}
 Kenny looks okay --- so far
{% endif %}

for...in...for循环可以遍历任何一个序列包括列表、字典、元组。并且可以进行反向遍历,以下将用几个例子进行解释:

 

普通的遍历:

 

<ul>
   {% for user in users %}
   <li>{{ user.username }}</li>
   {% endfor %}
</ul>

遍历字典:

<dl>
   {% for key, value in my_dict.iteritems() %}
   <dt>{{ key}}</dt>
   <dd>{{ value}}</dd>
   {% endfor %}
   </dl>

如果序列中没有值的时候,进入else

<ul>
   {% for user in users %}
   <li>{{ user.username}}</li>
   {% else %}
   <li><em>no users found</em></li>
   {% endfor %}
   </ul>

并且Jinja中的for循环还包含以下变量,可以用来获取当前的遍历状态:

loop.index 当前迭代的索引(从1开始)
loop.first 是否是第一次迭代,返回True或False
loop.last 是否是最后一次迭代,返回True或False
loop.length 序列的长度

另外,不可以使用continue和break表达式来控制循环的执行。

测试器

测试器主要用来判断一个值是否满足某种类型,并且这种类型一般通过普通的if判断是有很大的挑战的。语法是:if...is...,先来简单的看个例子:

{% if variable is escaped%}
    value of variable: {{ escaped }}
{% else %}
    variable is not escaped
{% endif %}

以上判断variable这个变量是否已经被转义了,Jinja中内置了许多的测试器,看以下列表:

测试器 说明
callable(object) 是否可调用
defined(object) 是否已经被定义了。
escaped(object) 是否已经被转义了。
upper(object) 是否全是大写。
lower(object) 是否全是小写。
string(object) 是否是一个字符串。
sequence(object) 是否是一个序列。
number(object) 是否是一个数字。
odd(object) 是否是奇数。
even(object) 是否是偶数。

宏和import语句

宏:

模板中的宏跟python中的函数类似,可以传递参数,但是不能有返回值,可以将一些经常用到的代码片段放到宏中,然后把一些不固定的值抽取出来当成一个变量,以下将用一个例子来进行解释:

定义宏

{% macro input(name, value='', type='text') %}
    <input type="{{ type }}" name="{{ name }}" value="{{
    value }}">
    {% endmacro %}

使用宏

<p>{{ input('username') }}</p>
    <p>{{ input('password', type='password') }}</p>

import语句

在真实的开发中,会将一些常用的宏单独放在一个文件中,在需要使用的时候,再从这个文件中进行导入。import语句的用法跟python中的import类似,可以直接import...as...,也可以from...import...或者from...import...as...,假设现在有一个文件,叫做forms.html,里面有两个宏分别为inputtextarea,如下:

forms.html:
    {% macro input(name, value='', type='text') %}
        <input type="{{ type }}" value="{{ value|e }}" name="{{ name }}">
    {% endmacro %}

    {% macro textarea(name, value='', rows=10, cols=40) %}
        <textarea name="{{ name }}" rows="{{ rows }}" cols="{{ cols
        }}">{{ value|e }}</textarea>
    {% endmacro %}

导入宏的例子:

  1. import...as...形式:

{% import 'forms.html' as forms %}
<dl>
  <dt>Username</dt>
  <dd>{{ forms.input('username') }}</dd>
  <dt>Password</dt>
  <dd>{{ forms.input('password', type='password') }}</dd>
</dl>
<p>{{ forms.textarea('comment') }}</p>

 2.from...import...as.../from...import...形式:

{% from 'forms.html' import input as input_field, textarea %}
<dl>
  <dt>Username</dt>
  <dd>{{ input_field('username') }}</dd>
  <dt>Password</dt>
  <dd>{{ input_field('password', type='password') }}</dd>
</dl>
<p>{{ textarea('comment') }}</p>

另外需要注意的是,导入模板并不会把当前上下文中的变量添加到被导入的模板中,如果你想要导入一个需要访问当前上下文变量的宏,有两种可能的方法:

  • 显式地传入请求或请求对象的属性作为宏的参数。
  • 与上下文一起(with context)导入宏。

与上下文中一起(with context)导入的方式如下

{% from '_helpers.html' import my_macro with context %}

include和set语句

include语句:

include语句可以把一个模板引入到另外一个模板中,类似于把一个模板的代码copy到另外一个模板的指定位置,看以下例子:

{% include 'header.html' %}
        Body
    {% include 'footer.html' %}

赋值(set)语句:

有时候我们想在在模板中添加变量,这时候赋值语句(set)就派上用场了,先看以下例子:

{% set name='0bug' %}

  

那么以后就可以使用name来代替0bug这个值了,同时,也可以给他赋值为列表和元组:

{% set navigation = [('index.html', 'Index'), ('about.html', 'About')] %}

赋值语句创建的变量在其之后都是有效的,如果不想让一个变量污染全局环境,可以使用with语句来创建一个内部的作用域,将set语句放在其中,这样创建的变量只在with代码块中才有效,看以下示例:

{% with %}
    {% set foo = 42 %}
    {{ foo }}           foo is 42 here
{% endwith %}

这两种方式都是等价的,一旦超出with代码块,就不能再使用foo这个变量了。

模版继承

Flask中的模板可以继承,通过继承可以把模板中许多重复出现的元素抽取出来,放在父模板中,并且父模板通过定义block给子模板开一个口,子模板根据需要,再实现这个block,假设现在有一个base.html这个父模板,代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    {% block head %}
    <link rel="stylesheet" href="style.css" />
    <title>{% block title %}{% endblock %} - My Webpage</title>
    {% endblock %}
</head>
<body>
    <div id="content">{% block content %}{% endblock %}</div>
    <div id="footer">
        {% block footer %}
        © Copyright 2008 by <a href="http://domain.invalid/">you</a>.
        {% endblock %}
    </div>
</body>
</html>

以上父模板中,抽取了所有模板都需要用到的元素htmlbody等,并且对于一些所有模板都要用到的样式文件style.css也进行了抽取,同时对于一些子模板需要重写的地方,比如titleheadcontent都定义成了block,然后子模板可以根据自己的需要,再具体的实现。以下再来看子模板的代码:

{% extends "base.html" %}
{% block title %}Index{% endblock %}
{% block head %}
    {{ super() }}
    <style type="text/css">
        .important { color: #336699; }
    </style>
{% endblock %}
{% block content %}
    <h1>Index</h1>
    <p class="important">
      Welcome to my awesome homepage.
    </p>
{% endblock %}

首先第一行就定义了子模板继承的父模板,并且可以看到子模板实现了title这个block,并填充了自己的内容,再看head这个block,里面调用了super()这个函数,这个函数的目的是执行父模板中的代码,把父模板中的内容添加到子模板中,如果没有这一句,则父模板中处在head这个block中的代码将会被子模板中的代码给覆盖掉。

另外,模板中不能出现重名的block,如果一个地方需要用到另外一个block中的内容,可以使用self.blockname的方式进行引用,比如以下示例:

<title>{% block title %}{% endblock %}</title>
<h1>{{ self.title() }}</h1>
{% block body %}{% endblock %}

以上示例中h1标签重用了title这个block中的内容,子模板实现了title这个blockh1标签也能拥有这个值。

另外,在子模板中,所有的文本标签和代码都要添加到从父模板中继承的block中。否则,这些文本和标签将不会被渲染。

模板转义

 

转义的概念是,在模板渲染字符串的时候,字符串有可能包括一些非常危险的字符比如<>等,这些字符会破坏掉原来HTML标签的结构,更严重的可能会发生XSS跨域脚本攻击,因此如果碰到<>这些字符的时候,应该转义成HTML能正确表示这些字符的写法,比如>HTML中应该用&lt;来表示等。

但是Flask中默认没有开启全局自动转义,针对那些以.html.htm.xml.xhtml结尾的文件,如果采用render_template函数进行渲染的,则会开启自动转义。并且当用render_template_string函数的时候,会将所有的字符串进行转义后再渲染。而对于Jinja2默认没有开启全局自动转义,作者有自己的原因:

  1. 渲染到模板中的字符串并不是所有都是危险的,大部分还是没有问题的,如果开启自动转义,那么将会带来大量的不必要的开销。
  2. Jinja2很难获取当前的字符串是否已经被转义过了,因此如果开启自动转义,将对一些已经被转义过的字符串发生二次转义,在渲染后会破坏原来的字符串。

在没有开启自动转义的模式下(比如以.conf结尾的文件),对于一些不信任的字符串,可以通过{{ content_html|e }}或者是{{ content_html|escape }}的方式进行转义。在开启了自动转义的模式下,如果想关闭自动转义,可以通过{{ content_html|safe }}的方式关闭自动转义。而{%autoescape true/false%}...{%endautoescape%}可以将一段代码块放在中间,来关闭或开启自动转义,例如以下代码关闭了自动转义:

{% autoescape false %}
  <p>autoescaping is disabled here
  <p>{{ will_not_be_escaped }}
{% endautoescape %}

模板数据类型和运算符

数据类型:

Jinja支持许多数据类型,包括:字符串、整型、浮点型、列表、元组、字典、True/False。

运算符:

  • +号运算符:可以完成数字相加,字符串相加,列表相加。但是并不推荐使用+运算符来操作字符串,字符串相加应该使用~运算符。
  • -号运算符:只能针对两个数字相减。
  • /号运算符:对两个数进行相除。
  • %号运算符:取余运算。
  • *号运算符:乘号运算符,并且可以对字符进行相乘。
  • **号运算符:次幂运算符,比如2**3=8。
  • in操作符:跟python中的in一样使用,比如true返回true
  • ~号运算符:拼接多个字符串,比如HelloWorld将返回HelloWorld

静态文件的配置

Web应用中会出现大量的静态文件来使得网页更加生动美观。静态文件主要包括有CSS样式文件、JavaScript脚本文件、图片文件、字体文件等静态资源。在Jinja中加载静态文件非常简单,只需要通过url_for全局函数就可以实现,看以下代码:

<link href="{{ url_for('static',filename='about.css') }}">

url_for函数默认会在项目根目录下的static文件夹中寻找about.css文件,如果找到了,会生成一个相对于项目根目录下的/static/about.css路径。当然我们也可以把静态文件不放在static文件夹中,此时就需要具体指定了,看以下代码:

app = Flask(__name__,static_folder='/tmp')

那么访问静态文件的时候,将会到/tmp这个文件夹下寻找。

视图

之前我们接触的视图都是函数,所以一般简称视图函数。其实视图也可以基于类来实现,类视图的好处是支持继承,但是类视图不能跟函数视图一样,写完类视图还需要通过app.add_url_rule(url_rule,view_func)来进行注册。以下将对两种类视图进行讲解:

标准视图:

标准视图继承自flask.views.View,并且在子类中必须实现dispatch_request方法,这个方法类似于视图函数,也要返回一个基于Response或者其子类的对象。以下将用一个例子进行讲解:

class BaseView(views.View):
    # 自定义方法,用来获取模板路径
    def get_template_name(self):
        raise NotImplementedError()

    # 必须实现的方法,用来处理请求的 
    def dispatch_request(self):
        if request.method != 'GET':
            return 'method error'

        #这里从self.get_data()中获取数据,子类应该实现这个方法
        context = {'data':self.get_data()}
        return render_template(self.get_template_name(),**context)

class UserView(BaseView):
    # 实现从父类继承的获取模板路径的方法
    def get_template_name(self):
        return 'user.html'

    # 重写获取数据的方法
    def get_data(self):
        return [{
            'username': 'fake',
            'avatar': 'http://www.baidu.com/'
        }]

# 类视图通过add_url_rule方法和url做映射
app.add_url_rule('/users/',view_func=UserView.as_view('userview'))

基于调度方法的视图:

Flask还为我们提供了另外一种类视图flask.views.MethodView,对每个HTTP方法执行不同的函数(映射到对应方法的小写的同名方法上),这对RESTful API尤其有用,以下将用一个例子来进行讲解:

class UserAPI(views.MethodView):
        # 当客户端通过get方法进行访问的时候执行的函数
        def get(self):
            return jsonify({
                'username':'0bug',
                'avatar':'http://www.baidu.com/'
            })

        # 当客户端通过post方法进行访问的时候执行的函数
        def post(self):
            return 'UNSUPPORTED!'

    # 通过add_url_rule添加类视图和url的映射,并且在as_view方法中指定该url的名称,方便url_for函数调用

    app.add_url_rule('/myuser/',view_func=UserAPI.as_view('userapiview'))

用类视图的一个缺陷就是比较难用装饰器来装饰,比如有时候需要做权限验证的时候,比如看以下例子:

 

def user_required(f):
    def decorator(*args,**kwargs):
        if not g.user:
            return 'auth failure'
        return f(*args,**kwargs)
    return decorator

如果要在类视图上进行装饰,只能在as_view函数上进行装饰了,使用方式如下:

view = user_required(UserAPI.as_view('users'))
app.add_url_rule('/users/',view_func=view)

但是一个好消息是,从Flask 0.8开始,还可以通过在类中添加decorators属性来实现对视图的装饰:

class UserAPI(views.MethodView):
    decorators = [user_required]
    ...

  

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部