flask官网入门文档笔记与新项目搭建

记录安装过程与新项目搭建过程

摘取官网中比较重要的部分翻译

几个比较好的项目推荐:

可参考教程:https://github.com/Microndgt/The-Flask-Mega-Tutorial

https://www.jianshu.com/p/b44ae3ffa1b6

https://www.codingpy.com/article/flask-web-development-book-pdf/

对应视频:https://www.bilibili.com/video/av5708093

电商系统:https://github.com/371854496/flask-vue-xcx-

仿网易云https://blog.csdn.net/AA_LiuIsMe_AA/article/details/90181132

源码:https://download.csdn.net/download/aa_liuisme_aa/11176525

音乐推荐系统:

https://github.com/caodaqian/Music-Recommend/blob/master/Src/App/models.py

音乐库管理系统:

https://github.com/SixSen/music-website-flask/blob/master/app/home/views.py

Table of Contents

前言:

安装:

快速上手

外部可见的服务器

路由

URL 构建

HTTP 方法¶

静态文件

渲染模板

操作请求数据

本地环境

请求对象

文件上传

Cookies

重定向和错误

关于响应

JSON 格式的 API

会话

消息闪现

日志

音乐库管理系统


前言:

配置和惯例

刚起步的时候 Flask 有许多带有合理缺省值的配置值和惯例。按照惯例, 模板和静态文件存放在应用的 Python 源代码树的子目录中,名称分别为 templates 和 static 。惯例是可以改变的,但是你大可不必改变, 尤其是刚起步的时候。

Flask 包含许多可以自定义其行为的钩子。考虑到你的定制需求, Flask 的类专为继承 而打造。

安装:

依赖会自动安装。

Werkzeug 用于实现 WSGI ,应用和服务之间的标准 Python 接口。

Jinja 用于渲染页面的模板语言。

MarkupSafe 与 Jinja 共用,在渲染页面时用于避免不可信的输入,防止注入攻击。

ItsDangerous 保证数据完整性的安全标志数据,用于保护 Flask 的 session cookie.

Click 是一个命令行应用的框架。用于提供 flask 命令,并允许添加自定义 管理命令。

快速上手

一个最小的 Flask 应用如下:

from flask import Flask

app = Flask(__name__)

 

@app.route('/')

def hello_world():

    return 'Hello, World!'

那么,这些代码是什么意思呢?

1.首先我们导入了 Flask 类。 该类的实例将会成为我们的 WSGI 应用。

2.接着我们创建一个该类的实例。第一个参数是应用模块或者包的名称。如果你使用 一个单一模块(就像本例),那么应当使用 __name__ ,因为名称会根据这个 模块是按应用方式使用还是作为一个模块导入而发生变化(可能是 ‘__main__’ , 也可能是实际导入的名称)。这个参数是必需的,这样 Flask 才能知道在哪里可以 找到模板和静态文件等东西。更多内容详见 Flask 文档。

3.然后我们使用 route() 装饰器来告诉 Flask 触发函数的 URL 。

4.函数名称被用于生成相关联的 URL 。函数最后返回需要在用户浏览器中显示的信息。

外部可见的服务器

运行服务器后,会发现只有你自己的电脑可以使用服务,而网络中的其他电脑却 不行。缺省设置就是这样的,因为在调试模式下该应用的用户可以执行你电脑中 的任意 Python 代码。

如果你关闭了调试器或信任你网络中的用户,那么可以让服务器被公开访问。 只要在命令行上简单的加上 --host=0.0.0.0 即可:

$ flask run --host=0.0.0.0

这行代码告诉你的操作系统监听所有公开的 IP 。

路由

使用 route() 装饰器来把函数绑定到 URL:

@app.route('/hello')

def hello():

    return 'Hello, World'

变量规则

通过把 URL 的一部分标记为 <variable_name> 就可以在 URL 中添加变量。标记的 部分会作为关键字参数传递给函数。通过使用 <converter:variable_name> ,可以 选择性的加上一个转换器,为变量指定规则。请看下面的例子:

@app.route('/user/<username>')

def show_user_profile(username):

    # show the user profile for that user

    return 'User %s' % escape(username)

 

@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

string

(缺省值) 接受任何不包含斜杠的文本

int

接受正整数

float

接受正浮点数

path

类似 string ,但可以包含斜杠

uuid

接受 UUID 字符串

以下两条规则的不同之处在于是否使用尾部的斜杠。:

@app.route('/projects/')

def projects():

    return 'The project page'

 

@app.route('/about')

def about():

    return 'The about page'

projects 的 URL 是中规中矩的,尾部有一个斜杠,看起来就如同一个文件夹。 访问一个没有斜杠结尾的 URL 时 Flask 会自动进行重定向,帮你在尾部加上一个斜杠。

about 的 URL 没有尾部斜杠,因此其行为表现与一个文件类似。如果访问这个 URL 时添加了尾部斜杠就会得到一个 404 错误。这样可以保持 URL 唯一,并帮助 搜索引擎避免重复索引同一页面。

URL 构建

url_for() 函数用于构建指定函数的 URL。它把函数名称作为第一个 参数。它可以接受任意个关键字参数,每个关键字参数对应 URL 中的变量。未知变量 将添加到 URL 中作为查询参数。

from flask import Flask, escape, url_for

 

app = Flask(__name__)

 

@app.route('/')

def index():

    return 'index'

@app.route('/user/<username>')

def profile(username):

    return '{}\'s profile'.format(escape(username))

 

with app.test_request_context():

    print(url_for('index'))

    print(url_for('profile', username='John Doe'))

HTTP 方法¶

Web 应用使用不同的 HTTP 方法处理 URL 。当你使用 Flask 时,应当熟悉 HTTP 方法。 缺省情况下,一个路由只回应 GET 请求。 可以使用 route() 装饰器的 methods 参数来处理不同的 HTTP 方法:

from flask import request

 

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

def login():

    if request.method == 'POST':

        return do_the_login()

    else:

        return show_the_login_form()

静态文件

使用特定的 'static' 端点就可以生成相应的 URL

url_for('static', filename='style.css')

这个静态文件在文件系统中的位置应该是 static/style.css 。

渲染模板

Flask 自动为你配置 Jinja2 模板引擎。

使用 render_template() 方法可以渲染模板,你只要提供模板名称和需要 作为参数传递给模板的变量就行了。下面是一个简单的模板渲染例子:

from flask import render_template

 

@app.route('/hello/')

@app.route('/hello/<name>')

def hello(name=None):

    return render_template('hello.html', name=name)

Flask 会在 templates 文件夹内寻找模板。因此,如果你的应用是一个模块, 那么模板文件夹应该在模块旁边;如果是一个包,那么就应该在包里面:

情形 1 : 一个模块:

/application.py

/templates

    /hello.html

情形 2 : 一个包:

/application

    /__init__.py

    /templates

        /hello.html

模板在继承使用的情况下尤其有用。其工作原理参见 模板继承 方案文档。简单的说,模板继承可以使每个页面的特定元素(如页头、导航和页尾) 保持一致。

自动转义默认开启。因此,如果 name 包含 HTML ,那么会被自动转义。如果你可以 信任某个变量,且知道它是安全的 HTML (例如变量来自一个把 wiki 标记转换为 HTML 的模块),那么可以使用 Markup 类把它标记为安全的,或者在模板 中使用 |safe 过滤器。更多例子参见 Jinja 2 文档。

下面 Markup 类的基本使用方法:

>>> from flask import Markup

>>> Markup('<strong>Hello %s!</strong>') % '<blink>hacker</blink>'

Markup(u'<strong>Hello &lt;blink&gt;hacker&lt;/blink&gt;!</strong>')

操作请求数据

在 Flask 中由全局 对象 request 来提供请求信息。如果你有一些 Python 基础,那么 可能 会奇怪:既然这个对象是全局的,怎么还能保持线程安全?答案是本地环境

本地环境

这个只有在做单元测试时才有用。在测试 时会遇到由于没有请求对象而导致依赖于请求的代码会突然崩溃的情况。对策是自己创建 一个请求对象并绑定到环境。最简单的单元测试解决方案是使用 test_request_context() 环境管理器。通过使用 with 语句 可以绑定一个测试请求,以便于交互。

请求对象

首先,你必须从 flask 模块导入请求对象:

from flask import request

通过使用 method 属性可以操作当前请求方法,通过使用 form 属性处理表单数据(在 POST 或者 PUT 请求 中传输的数据)。以下是使用上述两个属性的例子:

@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'

    # the code below is executed if the request method

    # was GET or the credentials were invalid

    return render_template('login.html', error=error)

form 属性中不存在这个键时会发生什么?会引发一个 KeyError 。 如果你不像捕捉一个标准错误一样捕捉 KeyError ,那么会显示一个 HTTP 400 Bad Request 错误页面。因此,多数情况下你不必处理这个问题。

要操作 URL (如 ?key=value )中提交的参数可以使用 args 属性:

searchword = request.args.get('key', '')

用户可能会改变 URL 导致出现一个 400 请求出错页面,这样降低了用户友好度。因此, 我们推荐使用 get 或通过捕捉 KeyError 来访问 URL 参数。

文件上传

用 Flask 处理文件上传很容易,只要确保不要忘记在你的 HTML 表单中设置 enctype="multipart/form-data" 属性就可以了。否则浏览器将不会传送你的文件。

from flask import request

 

@app.route('/upload', methods=['GET', 'POST'])

def upload_file():

    if request.method == 'POST':

        f = request.files['the_file']

        f.save('/var/www/uploads/uploaded_file.txt')

文件上传之前其在客户端系统中的名称,可以使用 filename 属性。

f.save('/var/www/uploads/' + secure_filename(f.filename))

 

Cookies

要访问 cookies ,可以使用 cookies 属性。可以使用响应 对象 的 set_cookie 方法来设置 cookies 。请求对象的 cookies 属性是一个包含了客户端传输的所有 cookies 的字典。在 Flask 中,如果使用session ,那么就不要直接使用 cookies ,因为 session比较安全一些。

使用 延迟的请求回调 方案可以在没有响应对象的情况下设置一个 cookie 。

重定向和错误

使用 redirect() 函数可以重定向。使用 abort() 可以 更早退出请求,并返回错误代码:

from flask import abort, redirect, url_for

 

@app.route('/')

def index():

    return redirect(url_for('login'))

 

@app.route('/login')

def login():

    abort(401)

    this_is_never_executed()

它让一个用户从索引页重定向到一个无法访问的页面(401 表示禁止访问)

缺省情况下每种出错代码都会对应显示一个黑白的出错页面。使用 errorhandler() 装饰器可以定制出错页面:

from flask import render_template

 

@app.errorhandler(404)

def page_not_found(error):

    return render_template('page_not_found.html'), 404

 

关于响应

视图函数的返回值会自动转换为一个响应对象。如果返回值是一个字符串,那么会被 转换为一个包含作为响应体的字符串、一个 200 OK 出错代码 和一个 text/html 类型的响应对象。以下是转换的规则:

  1. 如果视图返回的是一个响应对象,那么就直接返回它。
  2. 如果返回的是一个字符串,那么根据这个字符串和缺省参数生成一个用于返回的 响应对象。
  3. 如果返回的是一个字典,那么调用 jsonify 创建一个响应对象。
  4. 如果返回的是一个元组,那么元组中的项目可以提供额外的信息。元组中必须至少 包含一个项目,且项目应当由 (response, status) 、 (response, headers) 或者 (response, status, headers) 组成。 status 的值会重载状态代码, headers 是一个由额外头部值组成的列表 或字典。
  5. 如果以上都不是,那么 Flask 会假定返回值是一个有效的 WSGI 应用并把它转换为 一个响应对象。

如果想要在视图内部掌控响应对象的结果,那么可以使用 make_response() 函数。

@app.errorhandler(404)

def not_found(error):

    return render_template('error.html'), 404

@app.errorhandler(404)

def not_found(error):

    resp = make_response(render_template('error.html'), 404)

    resp.headers['X-Something'] = 'A value'

    return resp

 

JSON 格式的 API

如果从视图 返回一个 dict ,那么它会被转换为一个 JSON 响应。

@app.route("/me")

def me_api():

    user = get_current_user()

    return {

        "username": user.username,

        "theme": user.theme,

        "image": url_for("user_image", filename=user.image),

    }

如果 dict 还不能满足需求,还需要创建其他类型的 JSON 格式响应,可以使用 jsonify() 函数。该函数会序列化任何支持的 JSON 数据类型。

@app.route("/users")

def users_api():

    users = get_all_users()

    return jsonify([user.to_json() for user in users])

 

会话

除了请求对象之外还有一种称为 session 的对象,允许你在不同请求 之间储存信息。这个对象相当于用密钥签名加密的 cookie ,即用户可以查看你的 cookie ,但是如果没有密钥就无法修改它。

使用session之前你必须设置一个密钥。举例说明:

from flask import Flask, session, redirect, url_for, escape, request

 

app = Flask(__name__)

 

# Set the secret key to some random bytes. Keep this really secret!

app.secret_key = b'_5#y2L"F4Q8z\n\xec]/'

 

@app.route('/')

def index():

    if 'username' in session:

        return 'Logged in as %s' % escape(session['username'])

    return 'You are not logged in'

 

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

def login():

    if request.method == 'POST':

        session['username'] = request.form['username']

        return redirect(url_for('index'))

    return '''

        <form method="post">

            <p><input type=text name=username>

            <p><input type=submit value=Login>

        </form>

    '''

 

@app.route('/logout')

def logout():

    # remove the username from the session if it's there

    session.pop('username', None)

    return redirect(url_for('index'))

这里用到的 escape() 是用来转义的。如果不使用模板引擎就可以像上例 一样使用这个函数来转义。

如何生成一个好的密钥 :

生成随机数的关键在于一个好的随机种子,因此一个好的密钥应当有足够的随机性。 操作系统可以有多种方式基于密码随机生成器来生成随机数据。使用下面的命令 可以快捷的为 Flask.secret_key ( 或者 SECRET_KEY )生成值:

$ python -c 'import os; print(os.urandom(16))'

基于 cookie 的session的说明: Flask 会取出会话对象中的值,把值序列化后储存到 cookie 中。在打开 cookie 的情况下,如果需要查找某个值,但是这个值在请求中 没有持续储存的话,那么不会得到一个清晰的出错信息。

除了缺省的客户端会话之外,还有许多 Flask 扩展支持服务端会话。

消息闪现

闪现系统的基本工作原理是在请求结束时 记录一个消息,提供且只提供给下一个请求使用。通常通过一个布局模板来展现闪现的 消息。

flash() 用于闪现一个消息。在模板中,使用 get_flashed_messages() 来操作消息。

日志

以下是一些日志调用示例:

app.logger.debug('A value for debugging')

app.logger.warning('A warning occurred (%d apples)', 42)

app.logger.error('An error occurred')

logger 是一个标准的 Logger Logger 类, 更多信息详见官方的 logging 文档。

音乐库管理系统:

https://github.com/SixSen/music-website-flask/blob/master/app/home/views.py

解决github下不下来:https://www.cnblogs.com/USTC-ZCC/p/11163292.html

码云这几年发展很快啊

新建music_website的py3.7虚拟环境

使用requirements安装:https://www.cnblogs.com/imzhizi/p/python-pip-requirementstxt-wen.html

pip install -r requirements.txt

若安装超时使用镜像:

https://blog.csdn.net/weixin_34722995/article/details/71751837?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task

在 pip 后面跟-i 来指定源,比如用豆瓣的源来安装 whois库:

pip install whois -i http://pypi.douban.com/simple

可以在使用pip的时候加参数-i https://pypi.tuna.tsinghua.edu.cn/simple

例如:pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pyspider,这样就会从清华这边的镜像去安装pyspider库。

运行准备:导入sql,修改model与init、home/view(pymysql)中的连接数据库为flask_website

启动项目:https://blog.csdn.net/qq_40648358/article/details/104410698

python manage.py runserver

python manage.py runserver -p port -h host

浏览器查看:http://localhost:5000

不错,里面存的歌都是我爱听的

修改

使用bootstrap-glyphicons 字体图标:

https://www.runoob.com/bootstrap/bootstrap-glyphicons.html

注册登陆常用提示:

Sign up

XX can't be blank

Email is invalid

Confirm the password

Enter the password again

The username has been registered

设置的菜单在menu

歌曲榜单菜单在fav

Py2->py3:

print->print()

cPickle-> import pickle as pk

拆分后view中无法使用app

  File "D:\anacondaProject\music-website-flask\app\home\views.py", line 70, in welcome

    app.result = result

NameError: name 'app' is not defined

解决方法:

from flask import Blueprint, current_app

 

heysyni = Blueprint('heysyni', __name__)

 

@heysyni.route('/heysyni/')

def aheysyni():

    #Got my app here

    app = current_app._get_current_object()

Flask应用在处理客户端请求(request)时,会在当前处理请求的线程中推送(push)一个上下文实例和请求实例(request),请求结束时就会弹出(pop)请求实例和上下文实例,所以current_app和request是具有相同的生命周期的,且是绑定在当前处理请求的线程上的。

如果一个没有推送上下文实例就直接使用current_app,会报错

参考:https://www.javaear.com/question/9946136.html

https://segmentfault.com/q/1010000005155007

https://www.ctolib.com/docs-flaskcn-c-151014.html

https://segmentfault.com/q/1010000005865632/a-1020000005865704/revision#r6

https://www.cnblogs.com/angrycode/p/11456932.html

至此疑惑已解决。

导入pickle时报错UnicodeDecodeError: 'ascii' codec can't decode byte 0x?? in position 1: ordinal not in range(128)

py2中存储的pickle和py3中pickle无法读取的兼容性问题解决方案

参考:https://blog.csdn.net/zjuPeco/article/details/78449371

https://blog.csdn.net/accumulate_zhang/article/details/78597823

import pickle

with open("factor_solve_data.pkl",'rb') as f:

       factor_data = pickle.load(f, encoding='latin1')

则报错TypeError: cannot use a string pattern on a bytes-like object

参考:

https://blog.csdn.net/junlee87/article/details/78780831?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task

https://blog.csdn.net/s1162276945/article/details/81239478?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task

https://www.zhangshengrong.com/p/boNwZZnMaw/

import pickle

class StrToBytes:

  def __init__(self, fileobj):

    self.fileobj = fileobj

  def read(self, size):

    return self.fileobj.read(size).encode()

  def readline(self, size=-1):

    return self.fileobj.readline(size).encode()

 

read = open('XXX.pkl', 'r')

data = pickle.load(StrToBytes(read),encoding='iso-8859-1')

 

print(data)

使用:

class StrToBytes:

    def __init__(self, fileobj):

        self.fileobj = fileobj

    def read(self, size):

        return self.fileobj.read(size).encode()

        # return self.fileobj.read(size).decode()

    def readline(self, size=-1):

        return self.fileobj.readline(size).encode()

        # return self.fileobj.readline(size).decode()

 

#模型pickle

def load_pickle():

       with open('data/pickles/where2go_model.pkl', 'r') as data_file:

       # with open('data/pickles/where2go_model.pkl', 'rb') as data_file:

              data_dict = pkl.load(StrToBytes(data_file), encoding='iso-8859-1')

              return data_dict

报错cannot use a string pattern on a bytes-like object

  File "D:\anacondaProject\music-website-flask\app\home\views.py", line 80, in userinput

    ms = app.where2go.most_similar(data)

  File "D:\anacondaProject\music-website-flask\app\where2go_model.py", line 132, in most_similar

    terms = self.parse_search_query(input)

  File "D:\anacondaProject\music-website-flask\app\where2go_model.py", line 78, in parse_search_query

    string = re.sub('-\s*', '+-', string)

py3传个参数还变成bytes了呢

转换成string:str()

报错:"word 'b'beijing'' not in vocabulary

  File "D:\anacondaProject\music-website-flask\app\home\views.py", line 81, in userinput

    ms = app.where2go.most_similar(data)

  File "D:\anacondaProject\music-website-flask\app\where2go_model.py", line 144, in most_similar

    master_vector = multiplier * self.model[word]

b'beijing'可能是byte->str过程中多出来的前缀

bytes转无前缀str,参考:https://blog.csdn.net/qq_21153619/article/details/84841184

str(b'haha', encoding='utf-8')

bytes.decode(b'haha')

只在第一次报过的错,但第二次运行就好了

*****一大堆<--top_places_json end

127.0.0.1 - - [17/Mar/2020 02:13:16] "POST /map HTTP/1.1" 500 -

    return self.view_functions[rule.endpoint](**req.view_args)

  File "D:\anacondaProject\music-website-flask\app\home\views.py", line 85, in userinput

    app.result['top_places'] = top_places_json

TypeError: 'NoneType' object does not support item assignment

mvc中使用urlfor找引用路径

Py3字符串截取:

print str[6:] #截取第七个字符到结尾(即截掉前6个)static/banners

File "D:\anacondaProject\music-website-flask\app\templates\home\welcome.html", line 77, in template

var popupContent =  '<a target="_blank" class="popup" href="' + feature.properties.url + '">' + '<div class=crop>

<img src="{ { url_for(\'static\',filename=\''+feature.properties.image+'\') }}' +  + '" height/></div><div class=text-center style="padding:15px 0 0 0"><font size="5">' + feature.properties.title + '</font></div></a>';

jinja2.exceptions.TemplateSyntaxError: unexpected char '\\' at

expected token ',', got 'static'

参考:https://blog.csdn.net/qq_39241986/article/details/80680392

在flask中使用urlfor: https://segmentfault.com/q/1010000012038984

如果需要去掉这个错误,你需要使用url_for,但是在单独的js文件里面,url_for是无法被解析的。

href = "{ {url_for('card_detail', card_no=card_no) }}"

js中写为

"{ {url_for('card_detail')}}?card_no=" + car_no

"{ {url_for('card_detail')}}?card_no=" + car_no

"{ { url_for(‘static’)}}?filename="+feature.properties.image

均无效,尝试搭建后台接口传图片,但flask对https://localhost:8080之类的前缀会默认修改为flask端口+当前路由,尝试使用flask路由返回文件

参考:https://blog.csdn.net/xw_2_xh/article/details/96175571

https://www.cnblogs.com/yaoqingzhuan/p/10997201.html

 

# -*- coding: utf-8 -*-

from flask import Flask, render_template, send_file, send_from_directory,json, jsonify,make_response

 

app = Flask(__name__)  #实例化flask app

 

#file_name是客户端传来的需要下载的文件名

@app.route('/get_file/<file_name>', methods=['GET'])

def get_file(file_name):

    directory = config.APP_PATH

    try:

        response = make_response(

            send_from_directory(directory, file_name, as_attachment=True))

        return response

    except Exception as e:

        return jsonify({"code": "异常", "message": "{}".format(e)})

 

if __name__ == '__main__':

    app.run(debug=False, host='0.0.0.0', port=80)

from flask import Flask, send_from_directory

 

app = Flask(__name__)

 

@app.route("/download")

def index():

    return send_from_directory("D:\desk\id",filename="123.jpg",as_attachment=True)

 

if __name__ == '__main__':

    print(app.url_map)

    app.run(host="10.205.1.15", port=5000)

使用这个要注意路由前缀虽然相同,但要改函数名

http://localhost:8080/banners/default.png

报错send_from_directory' is not defined

参考:

https://stackoom.com/question/2LP1e/NameError-%E6%9C%AA%E5%AE%9A%E4%B9%89%E5%85%A8%E5%B1%80%E5%90%8D%E7%A7%B0-send-from-directory-%E5%85%B3%E9%97%AD

from flask import send_from_directory

还是404

搜索:

page_data = Music.query.filter(

        Music.music_name.ilike('%' + key + '%')

    ).order_by(

        Music.listen.desc()

    )

    page_data.key = key

<h4>{ { v.music_name }}</h4>

<a href="{ { url_for('home.play',id=v.music_id) }}"

                                                               class="label label-primary pull-right"><span

                            class="glyphicon glyphicon-play"></span>&nbsp;&nbsp;&nbsp;Detail&nbsp;&nbsp;</a>

Flask页面继承:

{% extends "home/home.html" %}

 

{% block username %}

    { { name }}

{% endblock %}

extends代表继承页面

在父页面用block ***挖空,在子页面block ***填上(一一对应)

收藏url:

{ { url_for('home.like',id=v.music_id) }}

 

猜你喜欢

转载自blog.csdn.net/lagoon_lala/article/details/104099496