Flask-论坛开发-2-Jinja2模板

对Flask感兴趣的,可以看下这个视频教程:http://study.163.com/course/courseLearn.htm?courseId=1004091002

1. Jinja2 模板介绍和路径查找

1.1 模板介绍

如果想自定义返回的网页样式,可以在视图函数中直接返回 html 代码,这样浏览器就能看到你自定义的网页样式了。但是一个复杂的页面,需要很多行代码来实现,这时在视图函数中返回这些代码就显得异常难受,于是 Flask 引入了一个 Jinja2 模板。

1.2 路径查找

所谓模板实际上就是一些被编写好的具有一定格式的 html 文件。在 pycharm 左侧一栏,项目下有两个文件夹: statictemplate,分别用于存放静态文件(如css,js,img文件)和模板文件(如html),所以我们的 html 文件应该放在 template 文件夹下。

那么,如何在主程序中调用模板文件呢?

  1. template 文件夹下新建一个 html 文件
  2. 在主程序中导入 render_template 模块
  3. 调用语法:render_template('abc.html'),注意不用写路径,flask 会自动去 template 文件夹下查找 abc.html,但有文件夹除外(即模板文件放在子目录下的情况需要从 template 目录下开始写路径)

代码如下:

import render_template
return render_template('index.html')

如果你不想讲模板文件放在 Flask 默认的路径下,你还可以改成自定义的位置,如下:

app.Flask(__name__,template_folder='C:/templates')

这样,Flask 就会去你指定的 C:/templates 中查找,而不是默认的路径下查找,所以你需要将模板文件全都剪切到你指定的路径下。但实际上

2. 模板传参

在 web 项目开发的大多数情况下,我们需要在 html 文件中从后台程序传入一些参数,然后将带有这些参数的 html 文件返回浏览器。这时候就需要在 html 文件中引用这些后台的参数,方法是 {{ Params }} 用 2 个花括号括起来,同时还要在后台程序做一些传参的动作。具体如下:

2.1 传简单参数

  1. 后端传参:

     render_template('index',username=u'蚂蚁有毒',gender=u'男',age=18)
    
  2. 前端引用:

     <h1>用户名:{{ username }}</h1>
     <h1>性别:{{ gender }}</h1>
     <h1>年龄:{{ age }}</h1>
    

2.2 传字典

但是如果参数越来越多,则代码会变得很复杂,可读性差,管理难度大。那么我们可以用一个字典(DICT)来定义一组参数。如下:

  1. 后端传参

     user = {
         'username':id,
         'gender':u'男',
         'age':18
     }
    
     # 调用时传入一个关键字参数即可
     return render_template('index.html',**user)
    
  2. 前端引用:

     <h1>用户名:{{ username }}</h1>
     <h1>性别:{{ gender }}</h1>
     <h1>年龄:{{ age }}</h1>
    

或者是如下所示:

  1. 后端传参

     user = {
         'username':id,
         'gender':u'男',
         'age':18
     }
     
     # 调用时传入一个关键字参数即可
     return render_template('index.html',User=user)
    
  2. 前端引用:

     <h1>用户名:{{ User.username }}</h1>
     <h1>性别:{{ User.gender }}</h1>
     <h1>年龄:{{ User.age }}</h1>
    

2.3 还可以在字典中定义字典:

  1. 后端传参

     user = {
         'username':id,
         'gender':u'男',
         'age':18
     	'children':{
     		'name':'myyd',
     		'country':'china',
     		'age':3
     	}
     }
    
  2. 前端引用

     <h1>我的个人空间</h1>
     <h3>用户名:{{ username }}</h3>
     <h3>性别:{{ gender }}</h3>
     <h3>年龄:{{ age }}</h3>
     <hr>
     <h3>孩子</h3>
     <p>名字:{{ children.name }}</p>
     <p>国家:{{ children.country }}</p>
     <p>年龄:{{ children[age] }}</p>
    

3. 模板中使用 url_for

在模板中,如果我们想跳转到另外一个页面上,可以借助 <a> 标签,如 <a href='/login/'>登录</a>。 但是如果你的模板路径一旦改变,就要去修改每一个 <a> 标签所指定的 href。这样非常麻烦,所以我们还是需要借助 Flask 中的 url_for 来实现。看下例:

例:在一个项目中,template 路径下存放有 2 个模板,index.htmllogin.html,在 index.html 页面上,需要跳转到 login.html 页面,用 url_for 如何实现?

# 传统的实现方法:
<a href='/login/'>我要登录</a>	# 其中,/login/ 是 login.html 页面的路径

# 使用 url_for 的方法:
<a href={{ url_for('login') }}>我要登录<a/>	# 其中,login 是 login.html 页面所关联的视图函数名

这里要注意,在模板中使用 url_for 来实现页面跳转的时候,用的也是 {{ }} 这个括号。但是一般情况下:

  1. 当模板中需要传递变量的时候使用:{{ }}
  2. 当模板中需要传递函数或者程序语句的时候使用:{% %}

同时,在使用 url_for 的时候,还可以在页面跳转的同时传递参数,如:<a href={{ url_for('login',name=myyd) }}>我要带参数登录</a>

注释小技巧:按 ctrl+/ 组合键可以快速注释。

4. 过滤器

过滤器可以理解为 Linux 中的管道符 |,它的作用是:将获取的变量经过管道符后筛选出想要的内容。在 flask 中有很多过滤器,这里介绍 2 个比较常用的过滤器:defaultlength。要注意的是,过滤器只能针对变量 {{ params }} 使用。

4.1 default

default 过滤器的作用:如果当前变量不存在,或者该变量的值为 None,可以使用指定的默认值

对于 default 过滤器,我们做一个实验:如果用户有头像则显示自己的头像,如果用户没有头像则显示默认头像。

在 html 文件中使用如下所示:

<img src="{{ avatar | default('https://i.imgur.com/ROhBvig.png') }}">

该行代码表示:
    如果后端主程序有传递 avatar 变量过来,那么就使用 avatar 变量的值;
    如果后端主程序没有传递 avatar 变量过来,那么就使用 default 过滤器指定的内容

对于本例而言,default 后面跟的图片地址应该是默认头像的地址,avatar 变量内保存的值应该是用户头像

再举一个例子,当用户自定义了个性签名,则显示用户的签名,若用户没有自定义个性签名,则使用默认短语。

  1. 后端传参

     @app.route('/')
     def index():
         userdetail = {
             'username':'MYYD',
             'gender':'男',
             'age':18,
     		'signature':None
         }
         return render_template('index.html',**userdetail)
    
  2. 前端引用

     <h1>我的个人空间</h1>
     <h3>用户名:{{ username }}</h3>
     <h3>性别:{{ gender }}</h3>
     <h3>年龄:{{ age }}</h3>
     <h4>个性签名:{{ signature | default('此人很懒,啥都没写。',boolean=True) }}</h4>
     <hr>
    
     这里要注意,因为 signature 是肯定有传参过来的,但是用户没有设置,所以在数据库中会被置为 None。所以需要借助 boolean=True 来判断用户有没有自定义个性签名的功能。
     实际上,这里可以用一个关键字 or 来代替,即:{{ signature or '此人很懒,啥都没写。' }},这样写起来更方便。
    

4.2 length

length 过滤器可以统计有长度属性的变量的长度。语法与 default 过滤器一样,但不用在后面跟上指定的变量。对于 length 过滤器,我们做一个实验:统计评论的数量并显示评论内容。

  1. 主程序代码:

     comments = [
         {
             'user':u'蚂蚁有毒',
             'content':u'我不喜欢这个东西'
         },
         {
             'user':u'杨烺',
             'content':u'有同感,我也是'
         }
     ]
     
     return render_template('index.html',comments = comments)
    
  2. html 模板代码:

     <p>
         评论数:({{ comments | length }})
     </p>
     <hr>
     {% for comment in comments %}
         <li>
             {{ comment.user }}:
         </li>
         <ol>
             {{ comment.content }}
         </ol>
     {% endfor %}
    

5. 常用过滤器

除了上面介绍的 2 种过滤器以外,还有很多过滤器,下面一一介绍。

5.1 escape 和 safe

其中,escape 可以对某个(段)字符(代码)进行转义,而 safe 是关闭自动转义。

唉,累。这一节的内容不想做笔记了。

6. 自定义过滤器

如果 jinja2 内置的过滤器不能满足我们的需求,也就是我不想做笔记的那一节介绍的那些过滤器不能满足我们的需求,那我们可以考虑定义自己的过滤器。

比如说有这样一些需求:当用户在你的网站上发表了一篇文章或者帖子,你需要获取这篇文章或帖子的发表的时间,但是如果对于最近才发表的文章来说,直接把发表时间返回浏览器的话会显得很死板,你想要对这个时间进行进一步的处理,显示该文章是几分钟几小时或者几天前发表的,而不是显示日期。对于这个需求,jinja2 并没有内置相关的过滤器来实现,同时这个需求也是非常的常见,那么这就需要我们自定义一个过滤器来满足这样的需求了。

在 Flask 中自定义过滤器实际上很简单,jinja2 的过滤器本质上就是一个函数,所以我们再自定义过滤器的时候只需要实现一个函数即可。下面给个例子:

例子:自定义一个过滤器,将传过来的参数中的 star 关键字删掉。

  1. 后端传参

     app.config['TEMPLATES_AUTO_RELOAD']=True	# 设置模板自动加载
     @app.route('/article/')
     def article():
         artdetail = {
             'author':'myyd',
             'content':'Twinkle, twinkle, little star. Twinkle, twinkle, little star.'
         }
         return render_template('article.html',**artdetail)
     
     # 自定义过滤器,将 star 关键字删除
     @app.template_filter('delete')	# 定义过滤器的装饰器
     def delete(value):	# 参数是前端引用时传给管道符的内容
         value = value.replace('star','')	# 将关键字 star 替换成空,就相当于删除了
         return value	# 返回过滤后的内容
    
  2. 前端引用

     <!DOCTYPE html>
     <html lang="en">
     <head>
         <meta charset="UTF-8">
         <title>article</title>
     </head>
     <body>
         <p>作者:{{ author }}</p>
         <p>内容:{{ content | delete }}</p>
         {#delete 过滤器将内容过滤#}
     </body>
     </html>
    

现在,再来实现我们本节一开始的需求,自定义一个时间过滤器。

详细需求:

  1. 如果时间间隔小于1分钟,就显示"刚刚"
  2. 如果时间间隔大于一分钟小于1小时,就显示"xxx分钟前"
  3. 如果时间大于1小时小于24小时,就显示"xxx小时前"
  4. 如果时间间隔大于24小时小于10天,就显示"xxx天前"
  5. 否则就显示具体时间。
  1. 后端传参

     @app.route('/article/')
     def article():
         artdetail = {
             'author':'myyd',
             'content':'Twinkle, twinkle, little star. Twinkle, twinkle, little star.',
             'create_time':datetime(2018,2,14,21,29,5,86341)
         }
         return render_template('article.html',**artdetail)
    
     @app.template_filter('time_helper')
     def time_helper(creat_time):
         if isinstance(creat_time,datetime):	# 先判断参数是否符合时间格式,实现一定容错能力
             inteval = datetime.now() - creat_time	# 获取发表时间和当前时间的时间间隔,以下就是一些逻辑判断(简单的if else)
             if inteval.total_seconds() <= 60:
                 return '刚刚'
             elif inteval.total_seconds() > 60 and inteval.total_seconds() <= 60*60:
                 delay = int(inteval.total_seconds()/60)
                 return '%s分钟前' % delay
             elif inteval.total_seconds() > 60*60 and inteval.total_seconds() <= 60*60*24:
                 delay = int(inteval.total_seconds()/(60*60))
                 return '%s小时前' % delay
             elif inteval.total_seconds() > 60*60*24 and inteval.total_seconds() <= 60*60*24*10:
                 delay = int(inteval.total_seconds()/(60*60*24))
                 return '%s天前' % delay
             else:
                 return creat_time.strftime('%Y.%m.%d %H:%M')	# 定义时间格式
         else:
             return creat_time
    
  2. 前端引用

     <!DOCTYPE html>
     <html lang="en">
     <head>
         <meta charset="UTF-8">
         <title>article</title>
     </head>
     <body>
         <p>作者:{{ author }}</p>
         <p>发表时间:{{ create_time | time_helper }}</p>
     </body>
     </html>
    

7. 模板中的判断与循环

实际上,我们还可以在模板(html文件)中嵌入 python 的代码:{% code %},这是 jinja2 的语法,可以嵌入 if 语句和 for 语句来在 html 文件中执行相关的逻辑操作。

7.1 if 语句

  1. 后端传参

     @app.route('/')
     def index():
         return render_template('index.html')
     
     @app.route('/detail/<is_login>')
     def detail(is_login):
         if int(is_login) == 1:
             userinfo = {
                 'username':'myyd',
                 'age':18,
                 'gender':'男'
             }
             return render_template('detail.html', **userinfo)
         else:
             return render_template('detail.html')
    
  2. 前端引用

     # index.html
     <!DOCTYPE html>
     <html lang="en">
     <head>
         <meta charset="UTF-8">
         <title>首页</title>
     </head>
     <body>
         <h3>欢迎来到蚂蚁有毒的首页</h3>
         <a href={{ url_for('detail',is_login=1) }}>登录看详情</a>
     </body>
     </html>
    
     # detail.html
     <!DOCTYPE html>
     <html lang="en">
     <head>
         <meta charset="UTF-8">
         <title>个人中心</title>
     </head>
     <body>
         <h3>欢迎来到蚂蚁有毒的个人中心</h3>
         {% if username and age==18 %}
             <a href="#">{{ username }}</a>
             <a href="#">注销</a>
         {% else %}
             <a href="#">登录</a>
             <a href="#">注册</a>
         {% endif %}
     </body>
     </html>
    

7.2 for 循环

for 循环的语法,在 html 中调用 for 语句和 if 语句的语法是一样的,都是在 {% %} 中写入 for 关键字。我们可以借助 html 中的 for 语句来遍历一些变量(List or Dict or Truple)。

和 python 中一样,html 中的 for 循环遍历时也是 for ... in ... 的语法。其中,jinja2 中的 for 循环还给我们定义了以下变量,用来获取当前的遍历状态:

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

但是需要注意的是:不可以使用 continue 和 break 来控制 html 中的 for 循环
  1. 后端传参

     users = {
         'username':u'蚂蚁有毒',
         'gender':u'男',
         'age':18
     }
     websites = ['www.baidu.com','www.google.com','www.qq.com']
     return render_template('index.html',user=users,website=websites)     
    
  2. 后端引用

     {% for k,v in user.items() %}
         <p>{{ k }}:{{ v }}</p>
     {% endfor %}
     <hr>
     {% for website in websites %}
         <p>{{ website }}</p>
     {% endfor %}
    

其他例子参考我的另一片博客:http://www.cnblogs.com/myyd/p/8195771.html

7.4 小案例-九九乘法表

不多说,直接贴代码:

<table border="1">
    <tabody>
        {% for x in range(1,10) %}
            <tr>
                {% for y in range(1,x+1) %}
                    <td>{{ y }}*{{ x }}={{ x*y }}</td>
                {% endfor %}
            </tr>
        {% endfor %}
    </tabody>
</table>

其中,第五行也可以写成:{% for y in range(1,100) if y<=x %},为什么这么写呢?之前有介绍,jinja2 中的 for 循环是没有 break 和 continue 的。像这种写法(在for循环中添加if语句),可以实现 python 中 break 的功能。

8. 宏的概念和使用

  1. 宏的概念

    模板中的宏,和 python 中的函数类似,可以传递参数,但是不能有返回值。可以将一些经常用到的代码片段放到宏里边,然后将一些不固定的值抽取出来当成一个变量,而固定的值可以在调用的时候不用填写。

    宏的语法是:

     {% macro label_name(args1,args2='',args3='text') %}
     	<label_name args1={{ args1 }},args2={{ args2 }},args3={{ args3 }}>
     {% endmacro %}
    
     其中,args1 是必传参数,args2 和 args3 是可变参数,可传可不传,并且已经指定不传时的默认参数。
    
  2. 宏的使用

    以 input 标签为例:

     {% macro input(name='',value='',type='text') %}
         <input type="{{ type }}",value="{{ value }}",name="{{ name }}">
     {% endmacro %}
     <hr>
     <table>
         <tabody>
             <tr>
                 <td>用户名:</td>
                 <td>{{ input(username) }}</td>
             </tr>
             <tr>
                 <td>密码:</td>
                 <td>{{ input(password,type='password') }}</td>
             </tr>
             <tr>
                 <td>{{ input(type='submit',value='提交') }}</td>
             </tr>
         </tabody>
     </table>
    
  3. 宏的导入

    在实际开发的过程中,我们并不会将宏的定义和宏的引用放在同一个 html 文件中,而是将有关宏的所有相关定义都放在一个专门的 html 文件中,如 macros.html,然后再根据需求从 macro.html 中导入我们需要的宏。

    macros.html 文件中应只有宏定义相关的代码,如下:

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

    和 python 一样,导入的方式有两种(在html模板的顶行):

     1. 方法一:
     {% from "macros.html" import input %}
     或者
     {% from "macros.html" import input as my_input %}
    
     2. 方法二:
     {% import "macros.html as macros"%}
    
     注意:macros.html 文件的导入,和模板文件一样,搜索 teamplates 路径。
    

    两种方法都可以导入,他们的调用方法略有差异:

     1. 调用一:
     input
     或者
     my_input
     
     2. 调用二:
     macros.input
    

    如果想在导入宏的时候,将当前模板从后台获取的参数也传给宏,那么可以在导入的时候使用关键字 with context 来实现。如:{% from "macros.html" import input with context %}

9. include 标签的使用

  1. 这个标签相当于将模板中的代码粘贴到当前位置。
  2. include 引入的模板,也是以 templates 目录为根目录进行导入。

具体的例子:

  1. include 模板文件:存放在 template/conmon/ 目录下

     ### header.html ###
     <style>
         .nav ul li{
             float: left;
             margin: 0 20px;
         }
         .nav ul{
             overflow: hidden;
         }
     </style>
     <nav class="nav">
         <ul>
             <li>首页</li>
             <li>课程详情</li>
             <li>视频教学</li>
             <li>关于我们</li>
         </ul>
     </nav>
    
     ### bottom.html ###
     <footer>
         底部信息
     </footer>
    
  2. 首页和详情模板文件

     ### index.html ###
     <!DOCTYPE html>
     <html lang="en">
     <head>
         <meta charset="UTF-8">
         <title>蚂蚁有毒</title>
     </head>
     <body>
         {% include "conmon/header.html" %}
             <div class="content">
                 <p>欢迎来到首页</p>
             </div>
         {% include "conmon/bottom.html" %}
     </body>
     </html>
    
     ### detail.html ###
     <!DOCTYPE html>
     <html lang="en">
     <head>
         <meta charset="UTF-8">
         <title>个人中心</title>
     </head>
     <body>
         {% include "conmon/header.html" %}
             <p>欢迎来到个人中心</p>
         {% include "conmon/bottom.html" %}
     </body>
     </html>
    

10. 模板中的 set 和 with 语句

10.1 set 语句

在模板中,可以用 set 语句来定义变量,示例如下:

# index.html

{% set username=myyd %}
<p>用户名:{{ username }}</p>

用 set 语句在模板中定义变量的作用是:一旦定义了一个变量,那么该变量可以在后面的代码中用到,和 python 中的变量定义是一样的作用。

10.2 with 语句

with 语句定义的变量,只能在 with 语句块中使用,而不是像 set 那样作用范围是整个 html 文件中。示例如下:

# index.html

{% with password = 'myydpassword' %}
	<p>密码:{{ password }}</p>
{% endwith %}

实际上,对于实现这种需求,也可以这么写:

# index.html

{% with %}
	{% set password = 'myydpassword' %}
	<p>密码:{{ password }}</p>
{% endwith %}

11. 加载静态文件

css,js,img 这 3 个文件都属于静态文件,都要放在项目的 static 文件夹下。如果我们要对模板中的一些内容进行渲染,如:对 <a> 标签的内容进行渲染,那么我们可以将原本写在 <head> 标签中的 <style> 标签的内容放到 css 文件中,再在模板中使用 url_for() 将 css 文件的渲染方式链接进来。步骤如下:

  1. 加载 css 文件:<link> 标签

    1. static 文件夹下创建一个新的文件夹 css,再在 css 文件夹下创建一个 css 文件,可随意命名(好记就行),如:head.css

    2. 在使用了 <a></a> 标签的模板中,若想引用 css 文件的内容,可以用 url_for() 链接进来,但格式与链接网址略有不同。正确链接静态文件的方式为:

       在模板 <head> 标签范文内使用 <link> 标签:
       <link rel=''stylesheet href='{{ url_for('static',filename='css/head.css') }}'>
       注意!这里使用的 url_for 在前面要加一个 "static" 参数,文件路径和之前的规则一样
      
  2. 加载 img 文件 <img> 标签

    1. 第一步和加载 css 文件的第一步一样,创建文件夹和文件:/static/img/index.jpg

    2. 第二步在模板中需要插入图片的地方使用如下代码:

       <img src='{{ url_for('static',filename='img/index.jpg') }}' alt=''>
      
  3. 加载 js 文件 <script> 标签

    1. 第一步和加载 css 文件的第一步一样,创建文件夹和文件:static/js/index.js

    2. 第二步在模板中需要插入脚本的地方使用如下代码:

       <script src="{{ url_for('static',filename='js/index.js') }}"></script>
      

一般静态文件也就这三样,使用方法如上所示。

12. 模板继承和 block

12.1 继承

继承的概念和面向对象编程的类的继承是一样的,只不过在这里继承的对象是模板。可以创建一个常用模板,并且定义相关接口,可以供其他模板所使用。继承的作用是:可以把一些公共的代码放在父模板中,避免编写重复的代码。

当创建好了一个模板后,在子模板中可以使用 {% extends 'base.html' %} 来继承该模板。

12.2 使用 block

但是如果我要在子模板中编写自己所特有的内容,应该怎么办?这时候就需要在父模板中写一个接口,来让子模板实现:

  1. 在父模板需要的地方定义接口的方式是:{% block abc %}{% endblock %}

  2. 在子模板中同样需要写上 {% block abc %} code {% endblock %},并且在 code 处写子模板需要的代码。

  3. 需要注意的是,子模板必须在父模板定义的接口中写代码,不然没有作用。如下所示:

     1. 父模板(Base.html):
     
         <!DOCTYPE html>
         <html lang="en">
         <head>
             <meta charset="UTF-8">
             {% block title %}
                 <title>Base</title>
             {% endblock %}
             <style>
                 .nav{
                     background: #3a3a3a;
                     height: 65px;
                 }
                 ul{
                     overflow: hidden;
                 }
                 ul li{
                     float: left;
                     list-style: none;
                     padding: 0 10px;
                     line-height: 65px;
                 }
                 ul li a{
                     color: #fff;
                 }
             </style>
         </head>
         <body>
         
             <div class="nav">
                 <ul>
                     <li><a href="#">首页</a></li>
                     <li><a href="#">发布问答</a></li>
                 </ul>
             </div>
         
         {% block content %}{% endblock %}
         
         </body>
         </html>
     
     2. 子模板(index.html):
     
         {% extends 'base.html' %}
         
         {% block title %}
             <title>首页</title>
         {% endblock %}
         
         {% block content %}
             <h1>蚂蚁有毒的首页</h1>
         {% endblock %}
    

此外,还需要注意几点:

  1. 子模板中的第一行必须是继承语句,即 extends 语句。
  2. 子模板中的代码,必须放在 block 块里面,否则不能被渲染。
  3. 子模板还可以在某个 block 下使用 super()self.block名() 调用父模板中的样式 和 调用其他 block 的样式。

13. Flask 高级视图

类视图的底层机制也是通过类来实现,用类实现之前视图函数的功能有很多好处,因为类本身就有很多特性,如继承和多态等。不过类视图在定义完成之后必须通过 add_url_rule() 映射类视图与 url 的关系。

13.1 add_url_rule 讲解

add_url_rule() 是一个类方法,用来添加视图函数与 url 的映射。它的定义是:add_url_rule(self, rule, endpoint=None, view_func=None, **options):,其中 self 代表它本身;rule 定义了一个规则,实际上就是指定了一条 uri,如 /list/endpoint 可以为空,它表示 rule 的别名,或者说是视图函数的别名,优先级高于视图函数的名称;当用户访问 rule 指定的 uri 时,由 view_funct 指定的函数来执行用户的请求; **option 是一个关键字参数,可传可不传。

如果在调用的时候没有传入 endpoint 参数,那么就会默认使用 view_func 指定的名字来作为 endpoint。也就是说,只要你传入了 endpoint 参数,那么当你使用 url_for() 来获取 url 的时候,传入的参数必须是 endpoint 指定的值,而不是 view_func 中指定的函数名了。

实际上,@app.route() 这个装饰器的底层实现,也是通过 add_url_rule() 来实现的,只不过他没有传入 endpoint 参数,所以 url_for() 反转时输入的是函数名。

如下例代码所示:

def my_list():
    return '这是列表页'

app.add_url_rule('/list/',endpoint='myyd',view_func=my_list)

当用户访问 domianname/list/ 的时候,会返回 这是列表页 字样,因为 app.add_url_rule() 中指定了 /list/my_list() 的关系。

13.2 标准类视图及其使用场景

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

from flask import Flask,views	# 要记得导入 views 模块

app = Flask(__name__)

class ListView(views.View):
    def dispatch_request(self):
        return '我是列表页'

app.add_url_rule('/list/',endpoint='list',view_func=ListView.as_view('list'))

需要注意的是:标准类视图必须实现 dispatch_request 方法,因为所有的请求过来都会执行该方法。这个方法的返回值就相当于之前视图函数的返回值,必须返回 Response 或其子类的对象,或者是字符串和元组。

同样的必须要用 app.add_url_rule() 来实现 url 与视图的映射,同时 view_func 参数需要用类视图下的 as_view() 方法来转换。

总结:

  1. 必须继承自 flask.views.View
  2. 必须实现 dispatch_request() 方法;
  3. 必须使用 app.add_url_rule() 来映射。

小例子:

需求:需要在登录页面和注册页面都放同一个广告。

# 定义广告类视图
class ADSView(views.View):
	def __init__(self):
		super(ADSView,self).__init__()
		self.context = {
			'ads':'小米手机就是强'
		}

# 定义登录页面类视图
class LoginView(ADSView):
	def dispatch_request(self):
		return render_template('login.html',**self.context)
		# 将参数传到前端,前端页面直接用 {{ ads }} 调用即可。当你想要更换所有页面的广告时只需要在父类中实现即可

# 定义注册页面
class RegistView(ADSView):
	def dispatch_request(self):
		return render_template('regist.html',**self.context)

# 视图与url映射
app.add_url_rule('/login/',endpoint='login',view_func=LoginView.as_view('login'))
app.add_url_rule('/regist/',endpoint='regist',view_func=LoginView.as_view('regist'))

13.3 基于调度方法来实现的类视图

基于调度方法的类视图,是根据请求的 method 来执行不同的方法的。如果用户发送的是 get 请求,那么就会执行这个类的 get 方法;如果用户发送的是 post 请求,那么将会执行这个类的 post 方法。当然,除了这两个方法外,还可以实现其他请求的方法,如 delete、put 等。

使用基于调度方法的类视图要注意:必须继承自 views.MethodView

给个例子如下:

# 需求:使用基于调度方法的类视图,模拟登录功能
# 其中,使用get或post请求,都返回同一个模板文件

---------------------------------------------------------------------------

# python 

from flask import Flask,views,render_template,request
app = Flask(__name__)
app.config['TEMPLATE_AUTO_RELOAD'] = True

class LoginView(views.MethodView):
    def __render(self,info=None):
        return render_template('login.html',info=info)
    def get(self):
        return self.__render(info='请登录')
    def post(self):
        username = request.form.get('username')
        password = request.form.get('password')
        if username == 'myyd' and password == '111111':
            return self.__render(info='登录成功')
        else:
            return self.__render(info='登录失败,请检查用户名或密码')

app.add_url_rule('/login/',endpoint='login',view_func=LoginView.as_view('login'))

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

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

---------------------------------------------------------------------------

# login.html

<form action="" method="post">
    <table>
        <tbody>
            <tr>
                <td>用户名:</td>
                <td><input type="text" name="username"></td>
            </tr>
            <tr>
                <td>密码:</td>
                <td><input type="password" name="password"></td>
            </tr>
            <tr>
                <td></td>
                <td><input type="submit" value="提交"></td>
            </tr>
        </tbody>
    </table>
</form>
{% if info %}
    <p style="color: red">{{ info }}</p>
{% endif %}

13.4 类视图中的装饰器

装饰器的作用就不过多介绍了,看下面的代码:

def login_request(func):
    @wraps(func)
    def wrapper(*args,**kwargs):
        username = request.args.get('username')
        if username == 'MYYD':
            return func(*args,**kwargs)
        else:
            return '请先登录'
    return wrapper

这是一个登录限制的装饰器,在访问某些需要登录的页面之前需要先登录。

  1. 那么,在函数视图中,怎么去使用这个装饰器呢?如下代码所示:必须要将装饰器放在 app.route 下面,否则该装饰器无法作用于对应的视图函数。

     @app.route('/settings/')
     @login_request
     def settings():
         return '这是设置界面'
    
  2. 而在类视图中,如何使用装饰器呢?很简单,Flask 给我们的类视图封装了一个 decorators 的属性,该属性可以传入除了 app.route 外的所有装饰器,因为 app.route 我们已经用 app.add_url_rule 来代替了。而类视图中的 decorators 属性接收的参数是一个列表 [ ],我们可以根据需求传入多个装饰器。具体的用法如下代码所示:

     class ProfileView(views.View):
         decorators = [login_request]	# 注意,传入的参数是装饰器函数名,不带括号
         def dispatch_request(self):
             return '个人中心'
    

14. 蓝图

蓝图适用于大型的 Flask 项目,简单粗暴地根据名称 Blueprint 翻译而成。可以将多个模块单独抽离出来,将相同模块的视图函数放在同一个蓝图下,方便管理。

14.1 蓝图的基本使用

基本语法如下:

  1. 在蓝图文件中导入 Blueprintfrom flask import Blueprint

  2. 在主 app 文件中,导入蓝图对象并进行注册:

     from blueprint.users import user_bp
     app.register_blueprint(users_bp)
    
  3. 蓝图的用法和 app 的用法是一样的,如:

     user_bp = Blueprint('/profile/',__name__)	# 定义蓝图,并指定url前缀
     @user_bp.route('/profile/')		# 映射url与视图函数
     def profile():
     	return '个人中心页面'
    
  4. 定义 url 前缀:

    如果你想把不同模块的 url 进行整理,如过要为每个模块的 url 都设置一个前缀,那么可以在定义蓝图的时候使用 url_prifix 关键字,对同模块下的不同 url 都设置同一个前缀,如下:

     user_bp = Blueprint('/profile/',__name__,url_prefix='/user')
    

    代表所有用户模块下的 url 都必须在原来视图函数定义的 url 上加上前缀 /user,那么原来【个人中心页面】是通过 /profile/ 来访问,现在需要通过 /user/profile/ 才能访问得到。

完整代码如下:

# users.py	

from flask import Blueprint,render_template
users_bp = Blueprint('users',__name__,url_prefix='/user')

@users_bp.route('/profile/')
def profile():
    return '个人中心页面'

@users_bp.route('/settings/')
def settings():
    return '个人设置界面'

# news.py

from flask import Blueprint
news_bp = Blueprint('news',__name__)

@news_bp.route('/list/')
def news_list():
    return '这是新闻列表页'

@news_bp.route('/detail/')
def news_detail():
    return '这是新闻详情页'

# 主 app 文件

from flask import Flask
from blueprint.users import users_bp
from blueprint.news import news_bp

app = Flask(__name__)
app.config['TEMPLATE_AUTO_RELOAD'] = True
app.register_blueprint(users_bp)
app.register_blueprint(news_bp)

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

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

14.2 使用蓝图的注意事项

  1. 蓝图中模板文件寻找规则

    如果想在蓝图文件中渲染模板文件 template,那么和在 app 文件中渲染模板文件的方法是一样的,使用 render_template 即可,这种方法也是去 templates 目录中去找的。

    当然还有第二种方法,自定义模板文件的查找路径,在定义蓝图的时候使用关键字 template_folder 指定查找的路径即可。如下所示:

     users_bp = Blueprint('users',__name__,template_folder='users_templates')
     @users_bp.route('/profile/')
     def profile():
         return render_template('user_template.html')
    

    但是需要注意的是,使用这种方法时若不指定绝对路径(如C:\xxx\xxx),那么会默认以当前蓝图所在目录为根目录去 template_folder 指定的目录中进行查找。如下图所示:

    相对路径

    如果蓝图文件是 users.py,那么就会以 users.py 所在目录 blueprint 为根目录去 template_folder 指定的 users_templates 目录中去查找 user_template.html 文件进行渲染并返回。

    不过使用第二种方法时,还是会先去 templates/ 目录中查找,如果找不到才去指定的 blueprint/users_templates/ 中查找,如果在 templates 中找到了则不会去 blueprint/users_templates/ 中查找了。

  2. 蓝图中静态文件寻找规则

    和查找模板文件一样,在蓝图中定义的视图函数,如果要去调用相关的静态文件,也是默认去找项目中的 static 目录。

    如果在定义蓝图时使用关键字 static_folder 指定自定义的目录时,也是有两种路径指定方法(绝对路径和相对路径),若为相对路径,也是以蓝图文件所在目录为根目录进行寻找。

    但是与传统方式不一样的是:在使用 url_for 时,引用静态文件必须加上蓝图名称,手动指定的目录才生效。如:url_for('users.static','user_style.css') ,这样就会去 users 定义的路径中查找了。并且该目录的优先级高于项目的 static 目录。

    也就是说,在使用 url_for 时,如果没有加上蓝图的名称,就会查找默认的 static 目录;如果加上了蓝图的名称,就会查找蓝图指定的目录。

  3. url_for 反转蓝图

    如果想要使用 url_for 实现对蓝图中的视图函数到 url 的反转,那么必须加上蓝图名称。如:

    定义蓝图如下:

     user_bp = Blueprint('user',__name__,url_prefix='/user')
    
     @user_bp.route('/profile/')
     def profile():
     	return '个人详情页面'
    

    如果想要获取蓝图中的视图函数 profile 对应的 url,那么在使用 url_for 的时候需要加上蓝图名称 user_bp

     <a href='{{ url_for('user.profile') }}'>跳转到个人中心</a>
    

    而不是:

     <a href='{{ url_for('profile') }}'>跳转到个人中心</a>
    
     # 因为这种方法是去主 app 文件中查找函数名为 profile 的视图函数对应的 url
     # 而不是蓝图中函数名为 profile 的视图函数对应的 url
     # 而即使在同一个蓝图中进行反转,也需要指定蓝图的名字
    

14.3 蓝图中的子域名

使用子域名,可以实现对内容的更好管理,可以将网站的不同功能分类保存在子域名中,这样可以让网站结构更加合理。

使用子域名,需要注意几个地方:

  1. 在构建蓝图时需要传递一个参数:subdomain,来指定这个域名的前缀。

    article_bp = BluePrint('article',__name__,subdomain='articles')

  2. 主 app 文件中要配置 app.configSERVER_NAME 参数。

    app.config[SERVER_NAME] = myyd.com:5000

    但是要注意:不能为 IP 地址和 localhost 设置子域名,即不可以出现 articles.127.0.0.1:5000articles.localhost:5000 这样的域名。

  3. 修改 C:\Windows\System32\drivers\etc\hosts 文件,将你自定义的域名按照格式添加到文件中作映射。如下:

     127.0.0.1	myyd.com
     127.0.0.1	articles.myyd.com
    
     # 注意域名和子域名都要设置
    

完整代码如下:

# article.py

from flask import Blueprint
article_bp = Blueprint('article',__name__,subdomain='articles')

@article_bp.route('/')
def article():
    return '文章子域名页面'

# 主 app 文件

from blueprint.articles import article_bp
app = Flask(__name__)
app.config['SERVER_NAME'] = 'myyd.com:5000'
app.register_blueprint(article_bp)

以上代码实现了:在本机访问 article.myyd.com 域名时会返回 "文章子域名页面"。

posted @ 2018-03-24 19:51  蚂蚁有毒  阅读(583)  评论(0编辑  收藏  举报