Python全栈(七)Flask框架之2.Flask视图和模板

一、url_for构造URL和指定地址末尾的/

1.通过url_for构造URL

一般我们通过一个URL就可以映射到某一个函数。反过来,知道一个函数时,也可以获得对应的URL,url_for()函数可以进行反转、实现这个功能。
url_for()函数接收一个及以上的参数,第一个参数是函数名,接收对应URL规则的命名参数,如果函数中有参数,则将这些参数传入url_for()函数第一个参数的后面。

进行测试:

from flask import Flask,url_for

app = Flask(__name__)


@app.route('/')
def index():
    # 根据函数的名字进行反转,得到函数对应的路由
    print(url_for('article_list',aid=2))
    return 'Hello World'


@app.route('/article/<aid>')
def article_list(aid):
    return 'article %s' % aid


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

运行开启服务后访问http://127.0.0.1:5000/,打印

127.0.0.1 - - [09/Apr/2020 16:46:24] "GET /article/3 HTTP/1.1" 200 -
127.0.0.1 - - [09/Apr/2020 16:46:30] "GET / HTTP/1.1" 200 -
/article/2

显然,此时通过url_for()方法得到了一个函数对应的路由,并显示在日志中;
有参数时,调用url_for()方法也要传入参数。

通过url_for()函数来构建URL从而在代码中拼URL的好处有两点:

  • 将来如果修改了URL,但没有修改该URL对应的函数名,就可以直接通过url_for()函数来获取地址而不用手动替换所有对应的URL。
  • url_for()函数可以转义一些特殊字符和unicode字符串,这在有时候显得很有用。

进一步测试:

from flask import Flask,url_for

app = Flask(__name__)


@app.route('/')
def index():
    print(url_for('article_list',aid=2))
    print(url_for('notice'))
    print(url_for('follow', fid=2,page=5))
    return 'Hello World'


@app.route('/article/<aid>')
def article_list(aid):
    return 'article %s' % aid

@app.route('/notice')
def notice():
    return 'Notice is as follows'

@app.route('/follows/<fid>')
def follow(fid):
    return 'Follower %s' % fid

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

再次访问,打印:

/article/2
/notice
/follows/2?page=5

显然,如果url_for()方法中提供的参数多于传入的函数的参数时,以查询参数的形式返回路由。
斜杠/ 转码测试:

from flask import Flask,url_for

app = Flask(__name__)


@app.route('/')
def index():
    print(url_for('follow', fid=2,page=5,param='/'))
    return 'Hello World'


@app.route('/article/<aid>')
def article_list(aid):
    return 'article %s' % aid

@app.route('/notice')
def notice():
    return 'Notice is as follows'

@app.route('/follows/<fid>')
def follow(fid):
    return 'Follower %s' % fid

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

打印:

127.0.0.1 - - [09/Apr/2020 17:07:09] "GET / HTTP/1.1" 200 -
/follows/2?page=5&param=%2F

显然,此时 / 被编码成ASCII码 %2F

2.指定URL末尾的斜杠

有些URL的末尾是有斜杠的,有些URL末尾是没有斜杠的,这其实是两个不同的URL。
例如定义路由@app.route('/article/'),当访问一个结尾不带斜线的URL/article,会被重定向到带斜线的URL/article/上去。
但是在定义路由的时候,如果在末尾没有加上斜杠,在访问的时候又加上了斜杠,这时候就会抛出一个404错误页面。例如@app.route("/article")没有在末尾加斜杠,此时只能访问到/article在访问/article/的时候,就会抛出一个404错误。
因此建议在定义路由时加上末尾的/,这与在访问时加 / 与否都可以访问到。

二、指定HTTP方法

默认情况下定义的路由只能使用GET请求,可以在@app.route()中可以传入一个关键字参数methods来指定本方法支持的HTTP方法。
如果想用post请求,可以示意Postman工具来模拟,可点击https://download.csdn.net/download/CUFEECR/12319280进行下载,点击安装包进行安装,安装完成后直接打开运行,即可进行各种方法的网络请求。
先进行测试:

from flask import Flask,url_for,request

app = Flask(__name__)


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


@app.route('/article/<aid>')
def article_list(aid):
    return 'article %s' % aid

@app.route('/login/')
def login():
    return 'login'


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

服务运行之后,在Postman中进行测试,如下:
flask http method post test
显然,此时Post方法不被允许,在路由中添加方法,再测试:

from flask import Flask,url_for,request

app = Flask(__name__)


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


@app.route('/article/<aid>')
def article_list(aid):
    return 'article %s' % aid

@app.route('/login/', methods=['GET','POST'])
def login():
    return 'login'


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

再在Postman中测试:
flask http method post addmethod test
显然,装饰器将让login的URL既能支持GET方法又能支持POST方法,此时可以用POST方法请求到。
在给定了methods参数后,就只能用方法列表中的方法请求,如果列表中没有get方法则不能再用get方法进行请求。
还可以传入参数,在flask项目中接收参数。
测试如下:

from flask import Flask,url_for,request

app = Flask(__name__)


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


@app.route('/article/<aid>')
def article_list(aid):
    return 'article %s' % aid

@app.route('/login/',methods=['GET','POST'])
def login():
    print(request.form.get('name'))
    return 'login'


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

Postman中请求:
flask http method post params test
查看控制台,会看到:

127.0.0.1 - - [09/Apr/2020 19:00:14] "POST /login/ HTTP/1.1" 200 -
corley

得到了请求的方法和得到的数据。

接收参数的方法:

  • get请求接收参数
request.args.get('xxx')
  • post请求接收参数
request.form.get('xxx')

三、页面跳转和重定向

重定向在页面上体现的操作就是浏览器会从一个页面自动跳转到另外一个页面。
比如用户访问了一个需要权限的页面,但是该用户当前并没有登录,此时应该重定向到登录页面。
重定向分为:

  • 永久性重定向
    http的状态码是301,多用于旧网址被废弃了要转到一个新的网址确保用户的访问,一个典型的例子是京东网站,你输入www.jingdong.com的时候,会被重定向到www.jd.com,因为jingdong.com这个网址已经被废弃了,被改成jd.com,所以这种情况下应该用永久重定向。
  • 暂时性重定向
    http的状态码是302,表示页面的暂时性跳转。比如访问一个需要权限的网址,如果当前用户没有登录,应该重定向到登录页面,这种情况下,应该用暂时性重定向。

在flask中,重定向是通过flask.redirect(url, code=302)这个方法来实现的,url表示需要重定向到的URL,可以结合url_for()函数来使用,code表示重定向类型状态码,默认是302也即暂时性重定向,可以修改成301来实现永久性重定向。

模拟访问个人中心页面,未携带name参数而重定向到登录页面:

from flask import Flask,url_for,request,redirect

app = Flask(__name__)


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


@app.route('/article/<aid>')
def article_list(aid):
    return 'article %s' % aid

@app.route('/login/')
def login():
    print(request.form.get('name'))
    return 'login'

@app.route('/profile/')
def profile():
    name = request.args.get('name')
    if name:
        return name
    else:
        return redirect('/login')

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

访问演示如下:
flask http method redirect  test
显然,请求中未携带name参数时会自动重定向到登录页面。
控制台中显示为:

127.0.0.1 - - [09/Apr/2020 19:23:09] "GET /profile/ HTTP/1.1" 302 -
127.0.0.1 - - [09/Apr/2020 19:23:09] "GET /login/ HTTP/1.1" 200 -
None

显然,请求状态码时302。
但是直接重定向到/login显得不够灵活严谨,因为加入登录的地址改变,比如变成/signin等,就必须重新修改代码,此时就可以用url_for()方法,来传入方法名而不是路由地址,即便地址改变,也可以正常重定向,如下:

from flask import Flask,url_for,request,redirect

app = Flask(__name__)


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


@app.route('/article/<aid>')
def article_list(aid):
    return 'article %s' % aid

@app.route('/login/')
def login():
    print(request.form.get('name'))
    return 'login'

@app.route('/profile/')
def profile():
    name = request.args.get('name')
    if name:
        return name
    else:
        # return redirect('/login')
        return redirect(url_for('login'))

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

效果与之前相同。
同时,也可以修改状态码为301,例如redirect(url_for('login'), code=301)

四、函数的返回值 - 响应(Response)

视图函数中可以返回以下类型的值:

  • Response对象。
  • 字符串
    其实Flask是根据返回的字符串类型重新创建一个werkzeug.wrappers.Response对象,Response将该字符串作为主体,状态码为200,MIME类型为text/html,然后返回该Response对象。
  • 元组
    元组中格式是(response,status,headers),response为一个字符串,status值是状态码,headers是响应头。

如果不是以上三种类型,Flask会通过Response.force_type(rv,request.environ)转换为一个请求对象。

之前在函数中返回的都是字符串,现进行返回列表的测试:

from flask import Flask,url_for,request,redirect

app = Flask(__name__)


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


@app.route('/article/<aid>')
def article_list(aid):
    return 'article %s' % aid

@app.route('/login/')
def login():
    print(request.form.get('name'))
    return 'login'

@app.route('/profile/')
def profile():
    name = request.args.get('name')
    if name:
        return name
    else:
        # return redirect('/login')
        return redirect(url_for('login'))

@app.route('/about')
def about():
    return ['123']

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

访问http://127.0.0.1:5000/about,会报错:
flask response list test
提示返回的类型只能是string、dict、tuple、Response instance或WSGI callable,其他类型会报错,现换成字典测试:

from flask import Flask,url_for,request,redirect

app = Flask(__name__)


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


@app.route('/article/<aid>')
def article_list(aid):
    return 'article %s' % aid

@app.route('/login/')
def login():
    print(request.form.get('name'))
    return 'login'

@app.route('/profile/')
def profile():
    name = request.args.get('name')
    if name:
        return name
    else:
        # return redirect('/login')
        return redirect(url_for('login'))

@app.route('/about')
def about():
    return {'name':'Corley'}

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

显示:
flask response dict test
也可以返回元组,是元组时,只返回元组的第一个元素;
在一般情况下,返回元组的用法是return '关于我们',200,即return '字符串',状态码
返回Response测试:

from flask import Flask,url_for,request,redirect,Response

app = Flask(__name__)


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


@app.route('/article/<aid>')
def article_list(aid):
    return 'article %s' % aid

@app.route('/login/')
def login():
    print(request.form.get('name'))
    return 'login'

@app.route('/profile/')
def profile():
    name = request.args.get('name')
    if name:
        return name
    else:
        # return redirect('/login')
        return redirect(url_for('login'))

@app.route('/about')
def about():
    return Response('关于我们')

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

显示:
flask response response test
可以看到,给Response传入字符串参数后,返回的和字符串是类似的;
Response('关于我们')相当于Response('关于我们',status=200,mimetype='text/html')
Response的用法是Response('字符串',状态码,mimetype='')
也可以用make_response()方法创建Response对象并返回,这个方法可以设置额外的数据,比如设置cookie、header等信息,测试如下:

from flask import Flask,url_for,request,redirect,Response,make_response

app = Flask(__name__)


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


@app.route('/article/<aid>')
def article_list(aid):
    return 'article %s' % aid

@app.route('/login/')
def login():
    print(request.form.get('name'))
    return 'login'

@app.route('/profile/')
def profile():
    name = request.args.get('name')
    if name:
        return name
    else:
        # return redirect('/login')
        return redirect(url_for('login'))

@app.route('/about')
def about():
    return make_response('关于我们')

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

效果与前者是一样的。

五、flask模板简介

模板是一个web开发必备的模块,因为在渲染一个网页的时候,并不是只渲染一个纯文本字符串,而是需要渲染一个有富文本标签的页面,这时候就需要使用模板了。
在Flask中,配套的模板是Jinja2,Jinja2的作者也是Flask的作者。这个模板非常强大,并且执行效率高。

函数返回HTML代码测试:

from flask import Flask

app = Flask(__name__)


@app.route('/')
def index():
    return '<input id="kw" name="wd" class="s_ipt" value="" maxlength="255" autocomplete="off">'


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

显示:
flask template return html test
显然,是可以解析HTML的,但是这很不利于前后端分离,从而给开发工作带来很大影响。
在一般开发时应该将HTML代码于Python代码分离,将前端代码放入templates目录中,再通过render_template()方法来渲染模板即可。
在项目目录下创建templates目录,templates目录下创建index.html,再测试:

from flask import Flask,render_template

app = Flask(__name__)


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

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

显示与之前相同。
render_template()方法根据传的值会在默认模板目录templates下寻找匹配模板。
在templates目录下再创建一个子目录,里边创建一个模板user.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>用户中心</title>
</head>
<body>
    <h1>个人中心</h1>
</body>
</html>

此时要加载user.html必须添加完整的子文件夹路径,如下:

from flask import Flask,render_template

app = Flask(__name__)


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


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

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

显示:
flask template return template subfolder test

如果想自定义模板保存目录,可以在初始化FLask时,传入template_folder参数来指定指定具体的路径,如下:

app = Flask(__name__, template_folder='./template_files')

此时你可以将所有模板文件放入template_files文件夹中,但是不建议自定义模板目录。

注意:
在模板代码中尽量不要含有注释,因为Flask也会解析注释掉的代码。

六、flask模板传参

在Flask项目中给模板传入参数:

from flask import Flask,render_template

app = Flask(__name__)


@app.route('/')
def index():
    return render_template('index.html', username='小度', age=18,hobby='Basketball')


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

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

模板index.html为:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Flask</title>
</head>
<body>
    <input id="kw" name="wd" class="s_ipt" value="" maxlength="255" autocomplete="off">
    <h1>{{username}}</h1>
    <h2>{{age}}</h2>
    <h3>{{hobby}}</h3>
</body>
</html>

运行起来之后可以看到:
flask template pass params test
显然,传递的参数值被渲染到了页面。
但是当模板中要传递的参数过多的时候,把所有参数放在一个函数中会显得传参部分的代码很臃肿,不是一个好的选择,此时可以进一步改进,使用字典进行包装,并且还可以加两个 * 号,来转换成关键字参数。
测试如下:

from flask import Flask,render_template

app = Flask(__name__)


@app.route('/')
def index():
    context = {
        'username':'小度',
        'age':18,
        'hobby':'Basketball'
    }
    return render_template('index.html', context=context)


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

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

模板中也需要改变:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Flask</title>
</head>
<body>
    <input id="kw" name="wd" class="s_ipt" value="" maxlength="255" autocomplete="off">
    <h1>{{context.username}}</h1>
    <h2>{{context.age}}</h2>
    <h3>{{context.hobby}}</h3>
</body>
</html>

此时运行之后与之前效果相同。
当然,模板代码也可以不改变,即直接用username而不是context.username,
此时Python代码应该为:

from flask import Flask,render_template

app = Flask(__name__)


@app.route('/')
def index():
    context = {
        'username':'小度',
        'age':18,
        'hobby':'Basketball'
    }
    return render_template('index.html', **context)


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

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

即用 ** 修饰context。
context中再嵌套字典测试:

from flask import Flask,render_template

app = Flask(__name__)


@app.route('/')
def index():
    context = {
        'username':'小度',
        'age':18,
        'hobby':{
            'Basketball':'Basketball',
            'Football':'Football',
            'PingPong':'PingPong'
        }
    }
    return render_template('index.html', **context)


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

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

模板代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Flask</title>
</head>
<body>
    <input id="kw" name="wd" class="s_ipt" value="" maxlength="255" autocomplete="off">
    <h1>{{username}}</h1>
    <h2>{{age}}</h2>
    <h4>{{hobby.Basketball}}</h4>
    <h4>{{hobby['Football']}}</h4>
    <h4>{{hobby.PingPong}}</h4>
</body>
</html>

显示:
flask template pass params  dict nesting test
可以看到,嵌套字典取值的方法有2种:

  • 属性调用
    例如hobby.Basketball
  • 字典取值方法
    例如hobby['Football']

嵌套列表取值测试:

from flask import Flask,render_template

app = Flask(__name__)


@app.route('/')
def index():
    context = {
        'username':'小度',
        'age':18,
        'hobby':{
            'Basketball':'Basketball',
            'Football':'Football',
            'PingPong':'PingPong'
        },
        'books':['Python','Java','PHP']
    }
    return render_template('index.html', **context)


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

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

模板代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Flask</title>
</head>
<body>
    <input id="kw" name="wd" class="s_ipt" value="" maxlength="255" autocomplete="off">
    <h1>{{username}}</h1>
    <h2>{{age}}</h2>
    <h4>{{hobby.Basketball}}</h4>
    <h4>{{hobby['Football']}}</h4>
    <h4>{{hobby.PingPong}}</h4>
    <h4>{{books.0}}</h4>
    <h4>{{books[1]}}</h4>
    <h4>{{books.2}}</h4>
</body>
</html>

显示:
flask template pass params list test
显然,列表的取值也有两种方法:

  • 下标属性取值
    books.0
  • 列表取值方法
    books[1]

字典和列表的取值都有两种方法,建议使用属性取值方法,这在Flask中显得更地道。

发布了106 篇原创文章 · 获赞 1056 · 访问量 29万+

猜你喜欢

转载自blog.csdn.net/CUFEECR/article/details/105429634