Flask 学习笔记整理(未完成)

作者: 王炳明 分类: Python 基础教程 发布时间: 2021-01-18 22:28 热度:51

一、视图函数

Flask是一个微框架,它使得我们可以用一个文件来运行一个app,如下

# app/index.py

from flask import Flask

app = Flask(__name__)

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

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

那如何启动呢

Python hello.py

参数配置

# 设置调试模式:代码修改后,可以自动重新载入。  
1. app.run(debug=True)
2. app.debug = True
   app.run()

# 设置监听所有网络,公开服务器,前提要关闭debug
app.run(host='0.0.0.0')

路由

@app.route('/')
def index():
    return 'Index Page'

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

### 多个路由指向一个视图函数
@app.route('/')
@app.route('/hello')
def hello():
    return 'Hello World'

# 指定变量传入函数
@app.route('/user/<username>')
def show_user_profile(username):
    return 'User %s' % username

# 转换器:int,float,path
@app.route('/post/<int:post_id>')
def show_post(post_id):
    # show the post with the given id, the id is an integer
    return 'Post %d' % post_id

# 绑定请求方式
@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        do_the_login()
    else:
        show_the_login_form()

重定向

两个函数

url_for:以视图函数的名称作为参数,返回对应的url地址,还可以做url的拼接:url_for('profile', username='John Doe')
redirect:重定向

@app.route('/')  
def index():  
    login_url = url_for('login')  
    return redirect(login_url)   

@app.route('/login/')  
def login():  
    return  u'这是登陆页面' 

另外需要注意的是
在jinja2中使用 url_for 获取路由:{{ url_for(‘.index’) }},必须得加点。
而使用 url_for 获取静态文件地址,可以这样 {{ url_for(“static”, filename=”css/style.css”) }}

获取静态文件路径有一个问题,只能在http下使用,在https无法使用。有一种解决方法是覆写 url_for 方法。

STATIC_URL_ROOT = '//xxx.com/css/'

@app.context_processor
def override_url_for():
    return dict(url_for=static_url_for)

def static_url_for(endpoint, **values):
    if endpoint == 'static':
        filename = values.get('filename', None)
        if filename:
            file_path = STATIC_URL_ROOT + filename
            return file_path
    else:
        return url_for(endpoint, **values)

请求对象

request.form.get(‘name’, ”) 是获取表单数据
request.args.get(‘name’, ”) 是获取 URL 中提交的参数 ( ?key=value )

from flask import request

@app.route('/login', methods=['POST', 'GET'])
def login():
    error = None
    # 判断请求方式
    if request.method == 'POST':
        if valid_login(request.form['username'],
                       request.form['password']):
            return log_the_user_in(request.form['username'])
        else:
            error = 'Invalid username/password'
    elif request.method == 'GET':
        pass
    return render_template('login.html', error=error)

文件上传

要想发送文件,要确保在 HTML 表单中设置enctype="multipart/form-data"属性,不然你的浏览器根本不会发送文件。

request.files[‘the_file’]:获取发送的文件对象。可以通过f.save()方法,直接存储文件。

from flask import request
from werkzeug.utils import secure_filename

@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
    if request.method == 'POST':
        f = request.files['the_file']
        f.save('/var/www/uploads/' + secure_filename(f.filename))
    ...

更多有趣的内容,可以参阅:Flask 实现上传下载及进度条展示

二、模板引擎

假如没有HTML模板。
我们要返回一个页面,可能是这样实现的。

from flask import Flask

app = Flask(__name__)

@app.route('/')
@app.route('/index')
def index():
    user = { 'nickname': 'MING' } # fake user
    return '''
<html>
  <head>
    <title>MING's Blog</title>
  </head>
  <body>
    <h1>Hello, ''' + user['name'] + '''</h1>
  </body>
</html>
'''

这样做,很明显是不合适的。
一是、在视图函数中(Python代码)中嵌入了大量的 HTML代码,无法阅读,难以维护。
二是、将HTML代码写入Python文件中,将无法实现前端代码和后端代码并行开发。

所以通常的做法是通过render_template这个函数,指定哪个html模板,并传入模板参数(其实就是传入Python变量以便在HTML代码中可以使用)。

from flask import Flask
from flask import render_template

app = Flask(__name__)

@app.route('/')
@app.route('/index')
def index():
    user = { 'name': 'MING' }
    return render_template("index.html",
                           title = "MING's Blog",
                           user = user)

而我们的HTML代码放在何处呢?
在最早的时候,在app目录下,我们有新建了一个templates文件夹,就是用来放置HTML模板文件的。

# index.html
<html>
  <head>
    <title>{{ title }}</title>
  </head>
  <body>
      <h1>Hello, {{ user.name }}!</h1>
  </body>
</html>

运行一下,看看是什么效果。
Flask 学习笔记整理(未完成)插图

通过对比,你应该会好奇,为什么之前的 {{ title }}{{ user.name }} 会变成我们传入的 MING's BlogMING 的。

其实这个渲染的过程是通过我们调用的render_template函数实现的,其内部调用了 Jinja2 模板引擎。Jinja2 模板引擎是 Flask 框架的一部分。Jinja2 会把模板参数提供的相应的值替换了 {{…}} 块。

关于 Jinja2 的具体内容可查阅官方文档:http://jinja.pocoo.org/

这里介绍几个常用的。

打印变量

像操作Python一样

{{ user.name }}
{{ user["name"] }}

运行表达式

{% do navigation.append('a string') %}

注释语句

{# {{ user.name }} #}

with语句

with 关键 字。这使得创建一个新的内作用域。这个作用域中的变量在外部是不可见的。

下面两种方法是等价的。

{% with foo = 42 %}
    {{ foo }}
{% endwith %}

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

流程控制:if-else

<html>
  <head>
    {% if title %}
    <title>{{title}} - microblog</title>
    {% else %}
    <title>Welcome to microblog</title>
    {% endif %}
  </head>
  <body>
      <h1>Hello, {{user.nickname}}!</h1>
  </body>
</html>

循环语句:for

<html>
  <head>
    <title>MING's Blog</title>
  </head>
  <body>
    <h1>Hi, {{user.nickname}}!</h1>
    {% for post in posts %}
    <p>{{post.author.nickname}} says: <b>{{post.body}}</b></p>
    {% endfor %}
  </body>
</html>

继承与包含

在一个网站的所有页面,有一些是共同的部分,比如顶部的导航栏。在导航栏里面有注册,登陆等按钮。如果我们有很多页面,每个页面都要重复这段代码,就显得很冗余,而且如果以后某一个链接改了,所有的页面都得改。这是不小的工作量。万一哪个页面没同步更新,就会导致页面失效,这就是大问题了。

相反,我们可以利用 Jinja2 的模板继承的特点,这允许我们把所有模板公共的部分移除出页面的布局,接着把它们放在一个基础模板中,所有使用它的模板可以导入该基础模板。

这里涉及到三个用法
– {% extends “base.html” %}
– {% block footer %}{% endblock %}
– {{ super() }}

所以让我们定义一个基础模板,该模板包含导航栏以及上面谈论的标题(文件 app/templates/base.html):

<html>
  <head>
    <title>MING's Blog</title>
  </head>
  <body>
    <div>Microblog: <a href="/index">Home</a></div>
    <hr>
    {% block content %}{% endblock %}
  </body>
  <footer>
      {% block footer %}
      <p>Posted:MING</p>
          <p>Contact with:<a href="someone@example.com">someone@example.com</a> </p>
      {% endblock %}
  </footer>
</html>

在头部声明一下,继承自 base.html 文件,并定义 代码块。

{% extends "base.html" %}

{% block content %}
<h1>Hi, {{user.name}}!</h1>
{% for post in posts %}
<div><p>{{post.name}} says: <b>{{post.body}}</b></p></div>
{% endfor %}
{% endblock %}

{% block footer %}
    <hr>
    {{ super() }}
{% endblock %}

上面讲了继承,避免代码重复写,其实还有一种方法,就是 include 包含页面的方法。可以把在大量页面都需要的HTML代码抽离出来保存为一个单独文件,在需要的地方 include 进来就可以。

在这里,建议把所有需要被include的文件,放入一个单独的文件夹,include下。比如,在这个文件夹下有两个文件 _header.html 和 _metas.html,那么在需要使用的地方,列表的话,就可以这样写,如果是单个文件,就把中括号去掉。

{% include ['include/_header.html', 'include/_metas.html'] %}

嵌套块和作用域

上面我们讲到了 block ,这个是有一个作用域的,默认的块不允许访问块外作用域中的变量,比如下面,{{item}} 是无法获取值的。

{% for item in seq %}
    <li>{% block loop_item %}{{ item }}{% endblock %}</li>
{% endfor %}

正确的书写方法是,在块声明中添加 scoped 修饰,就把块设定到作用域中

{% for item in seq %}
  <li>{% block loop_item scoped %}{{ item }}{% endblock %}</li>
{% endfor %}

宏定义

宏,换个通俗的解释来说,就是个函数。

定义宏的方法,如下:

{% macro input(name, value='', type='text', size=20) -%}
    <input type="{{ type }}" name="{{ name }}" value="{{
        value|e }}" size="{{ size }}">
{% endmacro %}

如果,定义的宏很多,那我们可以将其都放进入一个文件中 _macro.html 中。

然后在需要的地方导入即可。导入的方法,有讲究的,必须要用别名

{% import '_macro.html' as ui %}

{% ui.input('username') %}
{% ui.input('password', type='password') %}

禁用转义

转义:渲染中会对html代码做处理。
不转义:浏览器会正常解析这些代码。

其中传递过来的title值为"<h1>Hello, World!</h1>"

禁用转义有三种写法:
第一种:使用过滤器

{{ title|safe }}

第二种

{% autoescape off %}
{{ title }}
{% endautoescape %}

第三种

{% autoescape false %}
{{title}}
{% endautoescape %}

自定义过滤器

官方的过滤器清单:http://docs.jinkan.org/docs/jinja2/templates.html#builtin-filters

如何定义自己的过滤器呢
比如我实现一个markdown转html的过滤器

首先安装一个markdown包

pip install Markdown==2.3.1 -i https://pypi.douban.com/simple/

然后在app中添加如下代码,这样就添加了一个过滤器了。

@app.template_filter("md")
def markdown_to_html(txt):
    from markdown import markdown
    return markdown(txt)

这样在模板中,就可以使用它

{{ body|md|safe }}

自定义函数

同样在app.py中添加如下代码,这样就注册了一个函数。

def read_md(filename):
    from functools import reduce
    with open(filename,encoding='UTF-8') as f:
        content = reduce(lambda x,y:x+y, f.readlines())
    return content

@app.context_processor
def inject_method():
    return dict(read_md=read_md)

这样在模板中,就可以使用它

{{ read_md('article.md')|md|safe }}

articel.md 是同级目录下的一个使用 markdown语法 编写的文件。

自定义一个测试函数

除了过滤器,Jinja2 中还有一种 “测试函数”。它用来要测试一个变量或表达式,你要在变量后加上一个 is 以及测试函数的名称。

例如,要得出 一个值是否定义过,你可以用 name is defined ,这会根据 name 是否定义返回 true 或 false 。

name 作用参数 传入 define 这个函数中,为真就返回True 。

在Jinja2 中已经内置了很多实用的测试函数,除此之外,它还提供定制功能。

from flask import request

@app.template_test("current_link")
def is_current_link(link):
    return link == request.path

这样我们就定义了一个测试函数,如何使用呢?

{% for label,link in links %}
    {% if not loop.first %}|{% endif %}
    <a href="{% if link is current_link %}#
    {% else %}
    {{ link }}
    {% endif %}
    ">{{ label }}</a>
{% endfor %}

参考文档

  • http://docs.jinkan.org/docs/jinja2/templates.html#builtin-tests
  • http://docs.jinkan.org/docs/jinja2/templates.html#builtin-filters

三、表单验证

  • 使用Flask-WTF 扩展。
pip install Flask-WTF
  • 查看官方文档:http://docs.jinkan.org/docs/flask-wtf/index.html

http://docs.jinkan.org/docs/flask/quickstart.html#accessing-request-data

四、数据库

  • 使用SQLAlchemy
pip install flask-sqlalchemy -i https://pypi.douban.com/simple/
pip install flask-migrate -i https://pypi.douban.com/simple/

五、参考教程

weixin

文章有帮助,请作者喝杯咖啡?

发表评论

电子邮件地址不会被公开。 必填项已用*标注