Flask学习之旅 --- 中级篇

2019-07-22  本文已影响0人  成长之路丶

jinja2模板

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

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

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

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

1.1 Flask模板简介

  1. 在渲染模版的时候,默认会从项目根目录下的templates目录下查找模版。
  2. 如果不想把模版文件放在templates目录下,那么可以在Flask初始化的时候指定template_folder来指定模版的路径。

1.2 模版参数

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

flask代码:

# 先导render_template模块
from flask import Flask,render_template

app = Flask(__name__)
# 第一种方式
@app.route("/")
def hello_world():
    content = {
    "name":"张三",
    "age":18
    }    
    return render_template("index.html",**content)


# 第二种方式
@app.route("/list/")
def my_list():  
    return render_template("list.html",name="张三",age=18)


# 第三种方式
@app.route("/detail/")
def goods_detail():
    content = {
    "name":"张三",
    "age":18
    }    
    return render_template("detail.html",content=content)

模板中的代码:

3种方式对应的模板文件:

第一种方式模板文件格式
<p>{{name}}</p>
<p>{{age}}</p>
第二种方式模板文件格式
<p>{{name}}</p>
<p>{{age}}</p>
第三种方式模板文件格式
<p>{{content.name}}</p>
<p>{{content.age}}</p>
字典格式
<p>{{content["name"]}}</p>
<p>{{content["age"]}}</p>

1.3 模板中使用url_for

模版中的url_for跟我们后台视图函数中的url_for使用起来基本是一模一样的。也是传递视图函数的名字,也可以传递参数。
使用的时候,需要在url_for左右两边加上一个{{ url_for('func') }}

flask代码:

from flask import Flask,render_template,url_for

app = Flask(__name__)


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

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


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

模板中的代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>知了课堂首页</title>
</head>
<body>
    这是从模版中渲染的
{#    {{ 用来存放变量 }}#}
{#    {% 用来执行函数或者逻辑代码 %}#}
    <p><a href="{{ url_for('login',ref='/',id='1') }}">登录</a></p>
</body>
</html>

1.4 过滤器的基本使用

1.4.1 什么是过滤器,语法是什么

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

1.4.2 常用过滤器

1. default过滤器:

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

2. 自动转义过滤器:
  1. safe过滤器:可以关闭一个字符串的自动转义。
  2. escape过滤器:对某一个字符串进行转义。
  3. autoescape标签,可以对他里面的代码块关闭或开启自动转义。
    {% autoescape off/on %}
        ...代码块
    {% endautoescape %}
    
3. 常用过滤器

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

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

1.4.3 自定义模版过滤器

过滤器本质上就是一个函数。如果在模版中调用这个过滤器,那么就会将这个变量的值作为第一个参数传给过滤器这个函数,然后函数的返回值会作为这个过滤器的返回值。需要使用到一个装饰器:@app.template_filter('cut')

@app.template_filter('cut')
def cut(value):
    value = value.replace("hello",'')
    return value
<p>{{ article|cut }}</p>
flask代码:
from flask import Flask,render_template
from datetime import datetime

app = Flask(__name__)
app.config['TEMPLATES_AUTO_RELOAD'] = True

@app.route('/')
def index():
    context = {
        'position': -9,
        'signature': '<script>alert("hello")</script>',
        'persons':['python','ketang'],
        'age': "18",
        'article': 'hello pythonworld hello',
        '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


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

模板中的代码:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>python课堂</title>
</head>
<body>
{#    <p>位置的绝对值是:{{ position|abs }}</p>#}
{#    <p>个性签名:{{ signature or '此人很懒,没有留下任何说明' }}</p>#}
{#        <p>个性签名:{{ signature|safe }}</p>#}
{#    {% autoescape off %}#}
{#    {% endautoescape %}#}
{#    <p>{{ persons[0] }}</p>#}
{#    <p>{{ "我的名字是:%s"|format('xiaoming') }}</p>#}
{#    <p>人数{{ persons|length }}</p>#}
{#    {% if age|int == 18 %}#}
{#        <p>年龄是18岁</p>#}
{#    {% else %}#}
{#        <p>年龄不是18岁</p>#}
{#    {% endif %}#}
{#    <p>{{ article|replace('hello','aaa') }}</p>#}
{#    <p>{{ article|truncate(length=5) }}</p>#}
{#    <p>{{ signature|striptags }}</p>#}
{#    <p>{{ article|cut }}</p>#}
    <p>发表时间:{{ create_time|handle_time }}</p>
</body>
</html>

1.5 模板中的语句

1.5.1 if条件判断语句

if条件判断语句必须放在{% if statement %}中间,并且还必须有结束的标签{% endif %}。和python中的类似,可以使用>,<,<=,>=,==,!=来进行判断,也可以通过and,or,not,()来进行逻辑合并操作。

flask代码:

from flask import Flask,render_template

app = Flask(__name__)
app.config.update({
    'DEBUG': True,
    'TEMPLATES_AUTO_RELOAD': True
})

@app.route('/')
def index():
    context = {
        'username': 'abc',
        'age': 17
    }
    return render_template('index.html',**context)


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

模板中的代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>
    {% if username == 'xiaoming' %}
        <p>首页</p>
    {% else %}
        <p>我不是首页</p>
    {% endif %}

    {% if age >= 18 %}
        <p>你可以进入网吧了</p>
    {% else %}
        <p>未成年人禁止进入网吧</p>
    {% endif %}
</body>
</html>

1.5.2 for循环语句

jinja2中的for循环,跟python中的for循环基本上是一模一样的。也是for...in...的形式。并且也可以遍历所有的序列以及迭代器。但是唯一不同的是,jinja2中的for循环没有breakcontinue语句。

flask代码:
from flask import Flask,render_template

app = Flask(__name__)
app.config['TEMPLATES_AUTO_RELOAD'] = True

@app.route('/')
def index():
    context = {
        'users':['xiaoming1','xiaoming2','xiaoming3'],
        'person': {
            'username': 'xiaoming',
            'age': 18,
            'country': 'china'
        },
        'books':[
            {
                'name': '三国演义',
                'author':'罗贯中',
                'price': 110
            },{
                'name': '西游记',
                'author':'吴承恩',
                'price': 109
            },{
                'name': '红楼梦',
                'author':'曹雪芹',
                'price': 120
            },{
                'name': '水浒传',
                'author':'施耐庵',
                'price': 119
            }
        ]
    }
    return render_template('index.html',**context)


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

模板中的代码:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>for循环</title>
</head>
<body>
    <ul>
        {% for user in users|reverse %}
            <li>{{ user }}</li>
        {% else %}
            <li>沒有任何值</li>
        {% endfor %}
    </ul>

    <table>
        <thead>
            <tr>
                <th>用户名</th>
                <th>年龄</th>
                <th>国家</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                {% for key in person.keys() %}
                    <td>{{ key }}</td>
                {% endfor %}
            </tr>
        </tbody>
    </table>

    <table>
        <thead>
            <tr>
                <th>序号</th>
                <th>书名</th>
                <th>作者</th>
                <th>价格</th>
                <th>总数</th>
            </tr>
        </thead>
        <tbody>
            {% for book in books %}
                {% if loop.first %}
                    <tr style="background: red;">
                {% elif loop.last %}
                    <tr style="background: pink;">
                {% else %}
                    <tr>
                {% endif %}
                    <td>{{ loop.index0 }}</td>
                    <td>{{ book.name }}</td>
                    <td>{{ book.author }}</td>
                    <td>{{ book.price }}</td>
                    <td>{{ loop.length }}</td>
                </tr>
            {% endfor %}
        </tbody>
    </table>

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

1.5.3 set语句

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

{% set username='python课堂' %}
<p>用户名:{{ username }}</p>

一旦定义了这个变量,那么在后面的代码中,都可以使用这个变量,就类似于Python的变量定义是一样的。

1.5.4 with语句

with语句定义的变量,只能在with语句块中使用,超过了这个代码块,就不能再使用了。示例代码如下:

{% with classroom = 'python1班' %}
<p>班级:{{ classroom }}</p>
{% endwith %}

with语句也不一定要跟一个变量,可以定义一个空的with语句,以后在with块中通过set定义的变量,就只能在这个with块中使用了:

{% with %}
    {% set classroom = 'python1班' %}
    <p>班级:{{ classroom }}</p>
{% endwith %}

1.6 宏详解

1.6.1 宏的基本使用

模板中的宏跟python中的函数类似,可以传递参数,但是不能有返回值,可以将一些经常用到的代码片段放到宏中,然后把一些不固定的值抽取出来当成一个变量。
使用宏的时候,参数可以为默认值。相关示例代码如下:

  1. 定义宏:
    {% macro input(name, value='', type='text') %}
    <input type="{{ type }}" name="{{ name }}" value="{{
    value }}">
    {% endmacro %}
    
  2. 使用宏:
    <p>{{ input('username') }}</p>
    <p>{{ input('password', type='password') }}</p>
    

1.6.2 导入宏

  1. import "宏文件的路径" as xxx
  2. from '宏文件的路径' import 宏的名字 [as xxx]
  3. 宏文件路径,不要以相对路径去寻找,都要以templates作为绝对路径去找。
  4. 如果想要在导入宏的时候,就把当前模版的一些参数传给宏所在的模版,那么就应该在导入的时候使用with context。示例:from 'xxx.html' import input with context
flask代码:
from flask import Flask,render_template

app = Flask(__name__)
app.config['TEMPLATES_AUTO_RELOAD'] = True

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


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

模板文件中的代码:

{#{% from "macros.html" import input as input_field %}#}
{% import "macros/macros.html" as macros with context %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>python宏</title>
</head>
<body>
    <h1>python登录</h1>
    <table>
        <tbody>
            <tr>
                <td>用户名:</td>
                <td>{{ macros.input('username') }}</td>
            </tr>
            <tr>
                <td>密码:</td>
                <td>{{ macros.input("password",type="password") }}</td>
            </tr>
            <tr>
                <td></td>
                <td>{{ macros.input(value="提交",type="submit") }}</td>
            </tr>
        </tbody>
    </table>
    <p>{{ username }}</p>
</body>
</html>

宏文件:

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

1.7 include详解

1.7.1 include标签

  1. 这个标签相当于是直接将指定的模版中的代码复制粘贴到当前位置。
  2. include标签,如果想要使用父模版中的变量,直接用就可以了,不需要使用with context
  3. include的路径,也是跟import一样,直接从templates根目录下去找,不要以相对路径去找。

flask代码:

from flask import Flask,render_template

app = Flask(__name__)
app.config['TEMPLATES_AUTO_RELOAD'] = True

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

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


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

模板中的文件:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>python课堂</title>
</head>
<body>
    {% include "common/header.html" %}
    <div class="content">
        中间的
    </div>
    {% include "common/footer.html" %}
</body>
</html>

footer.html中的代码:

<footer>
    这是底部
</footer>

header.html中的代码:

<style>
    .nav ul{
        overflow: hidden;
    }
    .nav ul li{
        float: left;
        margin: 0 20px;
    }
</style>
<nav class="nav">
    <ul>
        <li>首页</li>
        <li>课程详情</li>
        <li>视频教程</li>
        <li>关于我们</li>
        <li>{{ username }}</li>
    </ul>
</nav>

1.8 静态文件

加载静态文件使用的是url_for函数。然后第一个参数需要为static,第二个参数需要为一个关键字参数filename='路径'。示例:

{{ url_for("static",filename='xxx') }}

路径查找,要以当前项目的static目录作为根目录。

模板中的代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>python课堂</title>
    <link rel="stylesheet" href="{{ url_for('static',filename="css/index.css") }}">
    <script src="{{ url_for("static",filename='js/index.js') }}"></script>
</head>
<body>
<img src="{{ url_for("static",filename='imgs/buyudaren.jpg') }}" alt="">
</body>
</html>

1.9 模版继承

1.9.1 为什么需要模版继承

模版继承可以把一些公用的代码单独抽取出来放到一个父模板中。以后子模板直接继承就可以使用了。这样可以重复性的代码,并且以后修改起来也比较方便。

1.9.2 模版继承语法

使用extends语句,来指明继承的父模板。父模板的路径,也是相对于templates文件夹下的绝对路径。示例代码如下:

{% extends "base.html" %}

1.9.3 block语法

一般在父模版中,定义一些公共的代码。子模板可能要根据具体的需求实现不同的代码。这时候父模版就应该有能力提供一个接口,让父模板来实现。从而实现具体业务需求的功能。
在父模板中:

{% block block的名字 %}
{% endblock %}

在子模板中:

{% block block的名字 %}
子模板中的代码
{% endblock %}

1.9.4 调用父模版代码block中的代码

默认情况下,子模板如果实现了父模版定义的block。那么子模板block中的代码就会覆盖掉父模板中的代码。如果想要在子模板中仍然保持父模板中的代码,那么可以使用{{ super() }}来实现。示例如下:
父模板:

{% block body_block %}
        <p style="background: red;">这是父模板中的代码</p>
    {% endblock %}

子模板:

{% block body_block %}
    {{ super() }}
    <p style="background: green;">我是子模板中的代码</p>
{% endblock %}

1.9.5 调用另外一个block中的代码

如果想要在另外一个模版中使用其他模版中的代码。那么可以通过{{ self.其他block名字() }}就可以了。示例代码如下:

{% block title %}
    python课堂首页
{% endblock %}

{% block body_block %}
    {{ self.title() }}
    <p style="background: green;">我是子模板中的代码</p>
{% endblock %}

1.9.6 其他注意事项

  1. 子模板中的代码,第一行,应该是extends
  2. 子模板中,如果要实现自己的代码,应该放到block中。如果放到其他地方,那么就不会被渲染。
上一篇下一篇

猜你喜欢

热点阅读