赞
踩
C:\Users\Administrator>d:
D:\>cd D:\microblog
D:\microblog>python -m venv venv
D:\microblog>
D:\microblog>cd D:\microblog\venv\Scripts
D:\microblog\venv\Scripts>activate
(venv) D:\microblog\venv\Scripts>
注:退出虚拟环境 deactivate
安装Flask:pip install flask
创建一个“Hello,World!” Flask应用程序**
from flask import Flask#从flask包中导入Flask类
app = Flask(__name__)#将Flask类的实例 赋值给名为 app 的变量。这个实例成为app包的成员。
#print('等会谁(哪个包或模块)在使用我:',__name__)
from app import routes#从app包中导入模块routes
#注:上面两个app是完全不同的东西。两者都是纯粹约定俗成的命名,可重命名其他内容。
上述脚本只是创建了一个作为Flask类的实例的应用程序对象,Flask类是从flask包中导入的。传递给Flask类的变量name是一个Python预定义的变量,该变量设置为使用它的模块的名字。可加入一句打印用于理解,如上(不用时注释掉或删除该行代码)。当应用程序运行时,可看到打印的是*包 app*。
当需要加载如模板文件等相关资源时,Flask将使用此处传递的模块的位置作为起点。
传递的变量name总是以正确的方式配置给Flask。
接着,从包 app中导入模块routes,目前尚未编写它。还注意到:routes模块是在脚本底部导入,而不是顶部,因为它始终是完成的。底部导入是避免循环导入(circular import)问题的解决方法。在接下来的routes模块中,需要导入这个脚本(init.py)中的变量app,因此将其放置在底部导入,以避免由这俩个文件之间的相互引用引起的error。
路由,是处理URL 和函数 之间关系的程序。使用**route()装饰器**来把函数绑定到URL。
在Flask中,应用程序 路由的处理程序被编写为Python函数,称为 视图函数,例此模块中的index()。 视图函数映射到一个或多个路由URL,以便Flask知道客户端请求给定URL时要执行的逻辑(Python代码)。app/routes.py代码:
from app import app#从app包中导入 app这个实例
#2个路由
@app.route('/')
@app.route('/index')
#1个视图函数
def index():
return "Hello,World!"#返回一个字符串
@app.route
装饰器 为作为一个参数给定的URL和函数之间创建关联。代码中有两个装饰器,它们共同将URL / 和 / index 关联至index()
函数。这意味着当浏览器这俩个URL中任一个时,Flask将调用此函数(index())并将其返回值(字符串)作为响应 Response传递回浏览器。
为完成这个简单的应用程序,还需在顶层 top-level定义一个Flask应用程序实例 的Python脚本,命名为microblog.py,并仅有一行代码,即导入应用程序实例。代码如下:
from app import app#从app包中导入变量app(它是作为app包成员的变量)
运行之前,设置FLASK_APP
环境变量,它会告诉Flask如何导入刚写的应用程序。
set FLASK_APP=microblog.py
使用命令 flask run
运行程序.
注:环境变量不会在终端中被记住,比如打开新的终端窗口时,得重新设置它。不过,Flask允许注册 在运行flask 命令时要自动导入的环境变量。要使用这个选项,得安装python-dotenv包:
pip install python-dotenv
然后,在项目的顶级目录中创建一个.env
文件(如microblog/microblog.env),文件中编写环境变量名称、值:
FLASK_APP=microblog.py
如有一个预期:html主页有一个 欢迎用户的标题。目前这个应用程序还没用户的概念,也没用户系统。但可用一个 模拟用户,用Python字典实现:
user = {'username':'Miguel'}
创建模拟对象 是一种有用的技术,使我们可专注于应用程序已有的部分,而不必担心尚不存在的部分。
app/routes.py:从视图函数中返回完整的HTML页面
from app import app @app.route('/') @app.route('/index') def index(): user = {'username':'Miguel'} return ''' <html> <head> <title>Home Page - Microblog</title> <head> <body> <h1>Hello,''' + user['username']+ '''!</h1> <body> </html> '''
在cmd中运行程序。更新视图功能,并查看应用程序在浏览器中的显示效果。
(venv) E:\code\网站\Flask\microblog>set FLASK_APP=microblog.py
(venv) E:\code\网站\Flask\microblog>flask run
很明显,HTML代码 和.py代码混在一起了,很不利于今后的维护、理解。就得将网络布局(呈现)与应用程序的逻辑处理代码 分开/分离。模板在此闪亮登场!将助实现表示、业务逻辑的分离。
在Flask中,模板是作为单独的文件编写的,存放在应用程序 包内的 templates文件夹(约定俗成命名为 templates)下。
即在app目录下创建templates文件夹:(venv) E:\code\网站\Flask\microblog>cd app
(venv) E:\code\网站\Flask\microblog\app>mkdir templates
(venv) D:\microblog>cd D:\microblog\app
(venv) D:\microblog\app>mkdir templates
在此目录下创建一个HTML文件index.html
,它将和上方index()
这个视图函数一样的视图功能。
app/templates/index.html:主页面模板
<html>
<head>
<title>{{ title }} - Microblog</title>
</head>
<body>
<h1>Hello,{{ user.username }}!</h1>
</body>
</html>
这是一个标准的、简单的HTML页面。但有一点跟我们写HTML代码不同是title
标签、h1
标签中的 {{ … }}
两对花括号,在此它是个 占位符,作用是将表达式(如文字、数学式子、比较运算符等,其实在Python中是一个Python语句)打印到模板进行输出。具体参考Jinja2官网文档。
这些占位符表示HTML页面中可变的部分,并且只在运行时才知道。
现在HTML页面的呈现已在HTML模板中了,这样接着就可简化一下视图函数index()
了。修改routes.py文件
app/routes.py:使用render_template()
函数
from app import app
from flask import render_template#从flask包中导入render_template函数
@app.route('/')
@app.route('/index')
def index():
user = {'username':'Miguel'}
return render_template('index.html', title='Home', user=user)
将模板(index.html)转换为完整HTML页面的操作称之为 呈现(render,译作 递交、表达、给予,在此译作 “渲染”)。为了渲染模板,由从flask包中导入的render_template()
完成,此函数“携带”模板文件名(index.html)、模板参数的变量列表,并返回相同的模板,不过其中所有占位符都替换为实际值。
render_template()函数
调用与Flask框架捆绑在一起的Jinja2模板引擎。Jinja2会用相应的值替换{{ ... }}块
,这个相应的值由render_template()
调用时提供的参数给出。
参考:flask.render_template()
上述过程只是理解了:Jinja2模板引擎 如何在渲染过程中用实际值 替换 (模板中的)占位符。
接下来将认识到更多 Jinja2在模板文件中支持的更多强大操作:if条件、for循环、继承等。
源自官网的这句话:
There are a few kinds of delimiters. The default Jinja delimiters are configured as follows:
{% ... %} for Statements
{{ ... }} for Expressions to print to the template output
{# ... #} for Comments not included in the template output
# ... ## for Line Statements
if、for、继承
均在{% ... %}块
中写控制语句。
形如:
{% if title %}
...语句块
{% else %}
...语句块
{% endif %}
app/templates/index.html:模板中添加条件语句
<html>
<head>
{% if title %}
<title>{{ title }} - Microblog</title>
{% else %}
<title>Welcome to Microblog!</title>
{% endif %}
</head>
<body>
<h1>Hello,{{ user.username }}!</h1>
</body>
</html>
上述代码中if语句块的功能是:若视图函数index()
没有传递title占位符变量的值,则index.html模板
将会提供默认值(else语句块中),而不是显示空标题。
尝试将routes.py中render_template()
中的title='Home',
删除。效果:图略
在模板中形如:
{% for post in posts %}
...语句块
{% endfor %}
需求:登录用户可在主页中查看最新的帖子。
实现:
首先,用虚拟对象的方法来创建一些用户、帖子,以供显示。
app/routes.py:视图函数中的假帖子
from app import app from flask import render_template#从flask包中导入render_template函数 @app.route('/') @app.route('/index') def index(): user = {'username':'Miguel'}#用户 posts = [#创建一个列表:帖子。里面元素是两个字典,每个字典里元素还是字典,分别作者、帖子内容。 { 'author': {'username':'John'}, 'body':'Beautiful day in Portland!' }, { 'author': {'username':'Susan'}, 'body':'The Avengers movie was so cool!' } ] return render_template('index.html', title='Home', user=user, posts=posts)
帖子列表 可包含任意数量的元素,由视图函数index()
决定将在页面中显示的帖子数量。而模板index.html
不能假设这有多少个帖子,因此它需要准备好以通用方式呈现视图发送来的尽可能多的帖子。在模板index.html
中,用for
循环遍历所有的帖子并呈现。
app/templates/index.html:在模板中的for循环
<html>
<head>
{% if title %}
<title>{{ title }} - Microblog</title>
{% else %}
<title>Welcome to Microblog!</title>
{% endif %}
</head>
<body>
<h1>Hello,{{ user.username }}!</h1>
{% for post in posts %}
<div><p>{{ post.author.username }} says: <b>{{ post.body }}</b></p></div>
{% endfor %}
</body>
</html>
运行程序,图略
形如:
{% extends "base.html" %}
{% block content %}
...
{% endblock %}
现在,大部分Web应用程序在页面顶部有一个导航栏,它常包含一些常用链接:如登录、退出、编辑个人资料等。可以很轻松地将导航栏添加到index.html模板
,甚至更多的HTML页面中。但随着应用程序的增长(页面数量的增多),这些页面都将使用相同的导航栏,不可能每一个页面都增加一份相同的导航栏代码。
Jinja2具有模板继承
功能,完美解决上述问题。在实际操作中,将所有模板共有的页面布局部分
移至基础模板中,其他模板则继承自它。
实例:实现一个简单的导航栏,其他模板继承它。
在app/templates目录下创建一个基础模板文件 base.html。
app/templates/base.html:带导航栏的基础模板
<html>
<head>
{% if title %}
<title>{{ title }} - Microblog</title>
{% else %}
<title>Welcome to Microblog</title>
{% endif %}
</head>
<body>
<div>Microblog:<a href="/index">Home</a></div>
<hr>
{% block content %}
{% endblock %}
</body>
</html>
在上述基础模板中,块block
控制语句用于定义派生模板可自行插入的位置。块block
被赋予唯一的名字 content
,派生模板在提供其内容时可引用这个名称。
修改index.html这个模板,让其继承base.html模板。
app/templates/index.html:从基础模板继承
{% extends "base.html" %}
{% block content %}
<h1>Hello,{{ user.username }}!</h1>
{% for post in posts %}
<div><p>{{ post.author.username }} says: <b>{{ post.body }}</b></p></div>
{% endfor %}
{% endblock %}
base.html基础模板 实现处理常规页面的结构,则派生模板index.html简化大部分内容。
extends语句
建立了两个模板之间的继承关系,因此,Jinja2就会知道:当它被要求渲染index.html
时,需要将其嵌入base.html
中。这俩模板具有匹配的block语句
名称content
,这就是Jinja2如何将两个模板合并为一个模板的方法。
运行程序,图略
今后,当再需要为应用程序创建其他页面时,就可省去编写相同代码的麻烦,并让应用程序的所有页面共享相同的外观
,而只需一个步骤:创建继承自base.html模板的派生模板。
目前为止,项目结构:
microblog/
venv/
app/
templates/
base.html
index.html
__init__.py
routes.py
microblog.py
参考
https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-ii-templates
在Flask中,处理应用程序中的Web表单,将使用到**Flask-WTF**扩展库,它是Flask和WTForms的简单集成,主要功能有:使用CSRF(Cross-site request forgery,译作 跨站请求伪造)令牌保护表单、文件上传、支持reCAPTCHA(译作 反全自动区分计算机和人类的图灵测试,简单点就是:验证码)。扩展是Flask生态系统中非常重要的一部分。今后还会需要更多的扩展。
进入虚拟环境中,安装Flask-WTF:pip install flask-wtf
目前为止,这个应用程序足够简单,无需担心它的配置。Flask(以及Flask扩展)在如何执行操作方面提供了很多自由,并需要做一些决定,并将这些决定作为一个配置变量列表传递给框架。
应用程序 有多种格式可指定配置选项。最基本的方案:在app.config这个字典中,将定义的变量作为键。形如:
app = Flask(__name__)
app.config['SECRET_KEY'] = 'I am a secret, you can't guess.'
#需要的话,可继续添加更多的变量
尽管上述语法可为Flask成功创建配置选项,但根据 关注点分离原则(Separation of concerns, SoC),所以不要将配置放在创建应用程序的相同位置,而是:将配置保存在单独的.py文件中,并使用类存储配置变量,将该.py文件放在项目顶级目录下。
D:\microblog\config.py:密钥配置
import os
class Config:
SECRET_KEY = os.environ.get('SECRET_KEY') or 'you will never guess'
SECRET_KEY这个配置变量,将会被Flask及其扩展使用其值作为加密秘钥,用于生产签名或令牌。而Flask-WTF使用它来保护Web表单来免受CSFR攻击。
密钥的值 是具有两个术语的表达式,由or运算符连接。第一个术语是查找环境变量的值;第二个术语是一个硬编码的字符串。当然这个安全性还是很低的。当将应用程序部署在生产服务器上时,得设置一个安全级别高的。
其中os.environ是获取本机系统的各种信息(如环境变量等,你打印出来就明白了,哈哈),它是一个字典。我觉得os.environ.get(‘SECRET_KEY’)在开发环境中并没有用,是None,不知部署后是什么。
有了上述这个配置文件,接下来得让Flask应用程序读取并应用它。在创建Flask应用程序实例后,就用app.config.from_object()方法完成:
app/init.py:Flask配置
from flask import Flask
from config import Config#从config模块导入Config类
app = Flask(__name__)
app.config.from_object(Config)
from app import routes
查看刚才配置的密钥是什么:
(venv) D:\microblog>python
Python 3.6.3 (v3.6.3:2c5fed8, Oct 3 2017, 18:11:49) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> from microblog import app
>>> app.config['SECRET_KEY']
'you will never guess'
Flask-WTF扩展使用Python类来表示Web表单。表单类只是将表单的字段定义为类变量。
再次根据SoC(关注点分离原则),新建一个forms.py模块来存放Web表单类。在此,定义一个用户登录表单,要求用户输入用户名、密码,还包含“Remember Me”复选框、提交按钮。
app/forms.py:用户登录表单
from flask_wtf import FlaskForm#从flask_wtf包中导入FlaskForm类
from wtforms import StringField,PasswordField,BooleanField,SubmitField#导入这些类
from wtforms.validators import DataRequired
class LoginForm(FlaskForm):
username = StringField('Username', validators=[DataRequired()])
password = PasswordField('Password', validators=[DataRequired()])
remember_me = BooleanField('Remember Me')
submit = SubmitField('Sign In')
这条语句表示:这个用户登录表单的字段类型的4个类是直接从WTForms包导入的,因为Flask-WTF扩展是不提供自定义(字段类型?)版本。对于每个字段,将在LoginForm类中将对象创建为类变量。每个字段都有一个描述或标签作为第一个参数。
在某些字段中看到的可选参数validators将验证行为附加到字段中,如用户名、密码肯定是需要进行验证的。DataRequired验证器 只是简单地检查该字段不会提交为空。当然还有其他的验证器可用。
有了上一步的登录表单,接下来得将表单添加到HTML模板中,让其在网页上呈现。
LoginForm类中定义的字段知道如何将自己渲染为HTML。
app/templates/login.html:用户登录表单模板
{% extends "base.html" %} {% block content %} <h1>Sign In</h1> <form action="" method="post" novalidate> {{ form.hidden_tag() }} <p> {{ form.username.label }}<br> {{ form.username(size=32) }} </p> <p> {{ form.password.label }}<br> {{ form.password(size=32) }} </p> <p>{{ form.remember_me() }} {{ form.remember_me.label }}</p> <p>{{ form.submit() }}</p> </form> {% endblock %}
这个用户登录表单模板使用了extends继承语句继承base.html模板,以确保一致的布局,即基础模板包含了所有页面的顶部导航栏。
在此的用户登录表单模板期望从LoginForm类实例化的表单对象作为参数给出,这个参数将由登录视图函数(目前还未编写)发送。
以下将讲述HTML知识,上述这段HTML代码中:标签用作Web表单的容器。
其中
写过HTML Web表单的同学可能会发现这个模板中没有HTML字段,这是因为表单对象中的字段知道如何将自己呈现(渲染)为HTML,需要做的就是{{ form.<field_name>.label }}需要的字段标签、{{ form.<field_name>() }}需要的字段。对于需要其他HTML属性的字段,可将这些属性作为参数传递。此模板中的用户名、密码字段将size作为参数添加到这个HTML标签作为属性。这还是可将CSS类、或ID附加到表单字段的方法。
在编写完上一步的用户登录表单模板后,想要在浏览器中看到此表单的最后一步是:在应用程序中编写一个它的视图函数,用于渲染该模板。
因此,编写一个映射到/login URL的视图函数login(),并将其传递给模板进行渲染。在routes模块中增加代码:
app/routes.py:用户登录视图函数
from flask import render_template
from app import app
from app.forms import LoginForm
#...
@app.route('/login')
def login():
form = LoginForm()#表单实例化对象
return render_template('login.html', title='Sign In', form=form)
上述视图函数很简单,从forms.py
模块中导入LoginForm
类,然后实例化该类,最后将其发送到模板。form=form
,return
中将form实例对象
赋值给form变量
,这将获得表单字段所需的全部内容。为了便于访问登录表单,在基础模板中改进,即在导航栏中包含指向它的链接:app/templates/base.html:导航栏中增加登录链接
<div>
Microblog:
<a href="/index">Home</a>
<a href="/login">Login</a>
</div>
尝试点击上述“Sign In”提交按钮,浏览器将出现405错误“Method Not Allowed”。图略
在上一步中,用户登录的视图函数执行了一半的工作,即可在网页上显示表单。但它没有处理用户提交的数据的逻辑。这是Flask-WTF让这项逻辑处理变得非常简单的优势。更新用户登录视图函数代码,它接受、验证用户提交的数据:
app/routes.py:接收登录凭据
from flask import render_template,flash,redirect
@app.route('/login',methods=['GET','POST'])
def login():
form = LoginForm()
if form.validate_on_submit():
flash('Login requested for user {},remember_me={}'.format(form.username.data,form.remember_me.data))
return redirect('/index')
return render_template('login.html',title='Sign In',form=form)
在**@app.routes()**装饰器中参数methods作用是:告诉Flask这个视图函数接受GET和POST请求方法,覆盖默认值(即只接受GET请求)。HTTP协议中,GET请求是将信息返回给客户端(如浏览器)的请求,到目前为止,该应用程序中的所有请求都属于这种类型;POST 请求通常在浏览器上服务器提交表单数据时使用。上述出现“Method Not Allowed”,是因为浏览器尝试发送POST请求,而应用程序没有配置去接受它。
form.validate_on_submit()方法完成所有表单处理工作。当浏览器发送GET接收带有表单的网页请求时,此方法将返回False,此时函数会跳过if语句并直接在函数的最后一行呈现模板。
当用户在浏览器按下提交按钮时,浏览器发送POST请求,form.validate_on_submit()将收集所有数据,运行附加到字段的所有验证器,如果一切正常,它将返回True,表明数据有效且可由应用程序处理。但如果至少有一个字段未通过验证,则函数就会返回False,接着就像上述GET请求那样。
当form.validate_on_submit()返回True,这个登录视图函数将调用两个函数,分别是flash()、redirect(),均从flask包导入的。
flash() 用于向用户显示消息,如让用户知道某些操作是否成功。目前为止,将使用其机制作为临时解决方案,因为暂无用户登录未真实所需的基础结构,此时只是显示一条消息用于确认应用程序已收到凭据。
redirect()用于指示客户端(浏览器)自动导航到作为参数给出的其他页面(如上述代码中的/index页面,即重定向到应用程序的/index页面)。
当调用flash()函数时,Flask会存储该消息,但闪烁的消息不会神奇地出现在Web页面中。应用程序的模板需要以适用于站点布局的方式呈现/渲染这些闪烁的消息。因此,将这些消息添加到基础模板中,以便所有模板都继承此功能。更新基础模板:
页请求时,此方法将返回False,此时函数会跳过if语句并直接在函数的最后一行呈现模板。
当用户在浏览器按下提交按钮时,浏览器发送POST请求,form.validate_on_submit()将收集所有数据,运行附加到字段的所有验证器,如果一切正常,它将返回True,表明数据有效且可由应用程序处理。但如果至少有一个字段未通过验证,则函数就会返回False,接着就像上述GET请求那样。
当form.validate_on_submit()返回True,这个登录视图函数将调用两个函数,分别是flash()、redirect(),均从flask包导入的。
flash() 用于向用户显示消息,如让用户知道某些操作是否成功。目前为止,将使用其机制作为临时解决方案,因为暂无用户登录未真实所需的基础结构,此时只是显示一条消息用于确认应用程序已收到凭据。
redirect()用于指示客户端(浏览器)自动导航到作为参数给出的其他页面(如上述代码中的/index页面,即重定向到应用程序的/index页面)。
当调用flash()函数时,Flask会存储该消息,但闪烁的消息不会神奇地出现在Web页面中。应用程序的模板需要以适用于站点布局的方式呈现/渲染这些闪烁的消息。因此,将这些消息添加到基础模板中,以便所有模板都继承此功能。更新基础模板:
app/templates/base.html:基础模板中的闪烁消息
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。