一、URL与视图
1、Flask简介
flask是一款非常流行的Python Web框架,出生于2010年,作者是Armin Ronacher,本来这个项目只是作者在愚人节的一个玩笑,后来由于非常受欢迎,进而成为一个正式的项目。
flask自2010年发布第一个版本以来,大受欢迎,深得开发者的喜爱,目前在Github上的Star数已经超过55.5k了,有超Django之趋势。flask能如此流行的原因,可以分为以下几点:
- 微框架、简洁、只做他需要做的,给开发者提供了很大的扩展性。
- Flask和相应的插件写得很好,用起来很爽。
- 开发效率非常高,比如使用SQLAlchemy的ORM操作数据库可以节省开发者大量书写sql的时间。
Flask的灵活度非常之高,他不会帮你做太多的决策,一些你都可以按照自己的意愿进行更改。比如:
- 使用Flask开发数据库的时候,具体是使用SQLAlchemy还是MongoEngine,选择权完全掌握在你自己的手中。区别于Django,Django内置了非常完善和丰富的功能,并且如果你想替换成你自己想要的,要么不支持,要么非常麻烦。
- 把默认的Jinija2模板引擎替换成其他模板引擎都是非常容易的。
2、安装Flask
在终端输入命令 pip install flask 即可安装。
3、新建第一个flask程序
新建项目中框架选择需pycharm专业版才有这功能
4、运行flask项目
在浏览器中输入http://127.0.0.1:5000就能看到hello world了。需要说明一点的是,app.run这种方式只适合于开发,如果在生产环境中,应该使用Gunicorn或者uWSGI来启动。如果是在终端运行的,可以按ctrl+c来让服务停止。
5、设置为DEBUG模式
默认情况下flask不会开启DEBUG模式,开启DEBUG模式后,flask会在每次保存代码的时候自动的重新载入代码,此时网页直接刷新就能看到效果,并且如果代码有错误,会在终端进行提示。 需要注意的是,只能在开发环境下开启DEBUG模式,因为DEBUG模式会带来非常大的安全隐患。
6、配置文件
Flask项目的配置,都是通过app.config对象来进行配置的。比如要配置一个项目的SECRET_KEY,那么可以使用app.config[‘SECRET_KEY’] = "xxx"来进行设置
常用有这几种方法:
1、在py文件中直接硬编码:
缺点:需要一个个写,涉及文件多的话显得很累赘
2、将所有配置项写成一个配置文件,然后使用者进行模块导入
Flask项目内置了许多的配置项,所有的内置配置项,可以点这查看~
7、URL与视图函数的映射
@app.route()是什么?
- 在Python中,只要是带着@的,基本上就是装饰器,装饰器的本质是扩展原本函数功能的一种函数
- 而这里的app.route(‘URL’)就是在Flask框架中非常重要的一个装饰器,它的作用是在程序运行时,装饰一个视图函数,用给定的URL规则和选项注册它,这里不理解也无所谓,能用即可。
从之前的helloworld.py文件中,我们已经看到,一个URL要与执行函数进行映射,使用的是@app.route装饰器。@app.route装饰器中,可以指定URL的规则来进行更加详细的映射,比如现在要映射一个文章详情的URL,文章详情的URL是/article/id/,id有可能为1、2、3…,那么可以通过以下方式:
@app.route('/article/<id>/')
def article(id):
return '%s article detail' % id
其中,尖括号是固定写法,语法为,variable默认的数据类型是字符串。如果需要指定类型,则要写成converter:variable,其中converter就是类型名称,可以有以下几种:
- string: 默认的数据类型,接受没有任何斜杠/的字符串。
- int: 整形
- float: 浮点型。
- path: 和string类似,但是可以传递斜杠/。
- uuid: uuid类型的字符串。
- any:可以指定多种路径,这个通过一个例子来进行说明:
@app.route('/<any(article,blog):url_path>/')
def item(url_path):
return url_path
以上例子中,item这个函数可以接受两个URL,一个是/article/,另一个是/blog/。并且,一定要传url_path参数,当然这个url_path的名称可以随便。
如果不想定制子路径来传递参数,也可以通过传统的?=的形式来传递参数,例如:/article?id=xxx,这种情况下,可以通过request.args.get(‘id’)来获取id的值。如果是post方法,则可以通过request.form.get(‘id’)来进行获取
8、视图转URL(url_for)
一般我们通过一个URL就可以执行到某一个函数。如果反过来,我们知道一个函数,怎么去获得这个URL呢?url_for函数就可以帮我们实现这个功能。url_for()函数接收两个及以上的参数,他接收函数名作为第一个参数,接收对应URL规则的命名参数,如果还出现其他的参数,则会添加到URL的后面作为查询参数。
9、指定指定(methods)HTTP方法:
在@app.route()中可以传入一个关键字参数methods来指定本方法支持的HTTP方法,默认情况下,只能使用GET请求,看以下例子: 以上装饰器将让login的URL既能支持GET又能支持POST。
10、页面跳转和重定向(redirect):
重定向分为永久性重定向和暂时性重定向,在页面上体现的操作就是浏览器会从一个页面自动跳转到另外一个页面。比如用户访问了一个需要权限的页面,但是该用户当前并没有登录,因此我们应该给他重定向到登录页面。
- 永久性重定向:http的状态码是301,多用于旧网址被废弃了要转到一个新的网址确保用户的访问,最经典的就是京东网站,你输入www.jingdong.com的时候,会被重定向到www.jd.com,因为jingdong.com这个网址已经被废弃了,被改成jd.com,所以这种情况下应该用永久重定向。
- 暂时性重定向:http的状态码是302,表示页面的暂时性跳转。比如访问一个需要权限的网址,如果当前用户没有登录,应该重定向到登录页面,这种情况下,应该用暂时性重定向。
在flask中,重定向是通过flask.redirect(location,code=302)这个函数来实现的,location表示需要重定向到的URL,应该配合之前讲的url_for()函数来使用,code表示采用哪个重定向,默认是302也即暂时性重定向,可以修改成301来实现永久性重定向。 两个例子都为如果没有登录或者找不到该用户,即给你重定向到首页或者登录页面!
二、Jinja模板
1、模板简介
模板是一个web开发必备的模块。因为我们在渲染一个网页的时候,并不是只渲染一个纯文本字符串,而是需要渲染一个有富文本标签的页面。这时候我们就需要使用模板了。在Flask中,配套的模板是Jinja2,Jinja2的作者也是Flask的作者。这个模板非常的强大,并且执行效率高。以下对Jinja2做一个简单介绍!
2、Flask渲染Jinja模板(render_template)
要渲染一个模板,通过render_template方法即可,以下将用一个简单的例子进行讲解:
from flask import Flask,render_template
app = Flask(__name__)
@app.route('/about/')
def about():
return render_template('about.html')
当访问/about/的时候,about()函数会在当前目录下的templates(默认不建议修改)文件夹下寻找about.html模板文件。如果想更改模板文件地址,应该在创建app的时候,给Flask传递一个关键字参数template_folder,指定具体的路径,再看以下例子:
from flask import Flask,render_template
app = Flask(__name__,template_folder=r'C:\templates')
@app.route('/about/')
def about():
return render_template('about.html')
以上例子将会在C盘的templates文件夹中寻找模板文件。还有最后一点是,如果模板文件中有参数需要传递,应该怎么传呢,我们再来看一个例子:
from flask import Flask,render_template
app = Flask(__name__)
@app.route('/about/')
def about():
return render_template('about.html',**{'user':'zhiliao'})
以上例子介绍了两种传递参数的方式,因为render_template需要传递的是一个关键字参数,所以第一种方式是顺其自然的。但是当你的模板中要传递的参数过多的时候,把所有参数放在一个函数中显然不是一个好的选择,因此我们使用字典进行包装,并且加两个*号,来转换成关键字参数。
渲染模板例子:
3、Jinja2模版概述
? 视图函数的主要作用是,处理业务逻辑,返回响应内容
? flask是使用jinja2这个模板引擎来渲染模板
使用模板的好处
- 视图函数只负责业务逻辑和数据处理
- 模板取到视图函数的数据结果进行展示
- 代码结构清晰,耦合度低
模板传参
- 再使用render_template渲染模板的时候,可以传递关键字参数,以后直接在模板中使用即可
- 如果参数过多,可以将所有的参数放到一个字典或列表中。将字典打散成关键字参数可以在参数前面加**
from flask import Flask,render_template
app = Flask(__name__)
student = {
'name': 'zhangsan',
'age':8,
'gender':'男'
}
student_list = [
{'name': 'zhangsan','age':18,'gender':'男'},
{'name': 'lisi','age':68,'gender':'女'},
{'name': 'wangwu','age':16,'gender':'男'}
]
student_dict = {
'a':{'name': 'zhangsan','age':18,'gender':'男'},
'b':{'name': 'lisi','age':28,'gender':'女'},
'c':{'name': 'wangwu','age':19,'gender':'男'}
}
@app.route('/test1')
def test1():
return render_template('01.html', **student)
@app.route('/test2')
def test2():
return render_template('02.html', stu_list = student_list)
@app.route('/test3')
def test3():
return render_template('03.html', stu_dict = student_dict)
if __name__ == '__main__':
app.run()
01.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>第一个模板</title>
</head>
<body>
学生姓名:{{ name }}
学生年龄:{% if age >= 18 %}
已经成年
{% else %}
未成年
{% endif %}
学生性别:{{ gender }}
</body>
</html>
结果 02.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>第二个模板</title>
</head>
<body>
{{ stu_list }}
<table border="1px">
<tr>
<td>序号</td>
<td>姓名</td>
<td>年龄</td>
<td>性别</td>
</tr>
{% for stu in stu_list %}
<tr>
<td>{{ loop.index }}</td>
<td>{{ stu.name }}</td> <!-- 由于stu是字典,有三种写法得到key的value-->
{% if stu.age >= 60 %}
<td>已退休</td>
{% elif stu.age >= 18 %}
<td>已成年</td>
{% else %}
<td>未成年</td>
{% endif %}
{
<td>{{ stu['gender'] }}</td>
</tr>
{% endfor %}
</table>
</body>
</html>
结果 03.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>第三个模板</title>
</head>
<body>
{{ stu_dict }}
<table border="1px">
<tr>
<td>序号</td>
<td>姓名</td>
<td>年龄</td>
<td>性别</td>
</tr>
{% for stu_key,stu in stu_dict.items() %}
<tr>
<td>{{ loop.index }},key:{{ stu_key }}</td>
<td>{{ stu.name }}</td> <!-- 由于stu是字典,有三种写法得到key的value-->
{% if stu.age >= 60 %}
<td>已退休</td>
{% elif stu.age >= 18 %}
<td>已成年</td>
{% else %}
<td>未成年</td>
{% endif %}
{
<td>{{ stu['gender'] }}</td>
</tr>
{% endfor %}
</table>
</body>
</html>
结果 语法
- 控制结构(逻辑代码){%%}
- 变量取值{{}}
- 注释{##}
更多参考原文章
4、模板过滤器
过滤器是通过管道符号(|)进行使用的,例如:{{ name|length }},将返回name的长度。过滤器相当于是一个函数,把当前的变量传入到过滤器中,然后过滤器根据自己的功能,再返回相应的值,之后再将结果渲染到页面中。Jinja2中内置了许多过滤器,在这里可以看到所有的过滤器,现对一些常用的过滤器进行讲解:
-
abs(value):返回一个数值的绝对值。 例如:-1|abs。 -
default(value,default_value,boolean=false):如果当前变量没有值,则会使用参数中的值来代替。name|default(‘xiaotuo’)——如果name不存在,则会使用xiaotuo来替代。boolean=False默认是在只有这个变量为undefined的时候才会使用default中的值,如果想使用python的形式判断是否为false,则可以传递boolean=true。也可以使用or来替换。 -
escape(value)或e:转义字符,会将<、>等符号转义成HTML中的符号。例如:content|escape或content|e。 -
first(value):返回一个序列的第一个元素。names|first。 -
format(value,*arags,**kwargs):格式化字符串。例如以下代码:
{{ "%s" - "%s"|format('Hello?',"Foo!") }}
将输出:Helloo? - Foo!
-
last(value):返回一个序列的最后一个元素。示例:names|last。 -
length(value):返回一个序列或者字典的长度。示例:names|length。 -
join(value,d=u’'):将一个序列用d这个参数的值拼接成字符串。
下边做实验
-
safe(value):如果开启了全局转义,那么safe过滤器会将变量关掉转义。示例:content_html|safe。 -
int(value):将值转换为int类型。 -
float(value):将值转换为float类型。 -
lower(value):将字符串转换为小写。 -
upper(value):将字符串转换为小写。 -
replace(value,old,new): 替换将old替换为new的字符串。 -
truncate(value,length=255,killwords=False):截取length长度的字符串。 -
striptags(value):删除字符串中所有的HTML标签,如果出现多个空格,将替换成一个空格。 -
trim:截取字符串前面和后面的空白字符。 -
string(value):将变量转换成字符串。 -
wordcount(s):计算一个长字符串中单词的个数。
也可以自定义过滤器,但是用的不多~
5、控制语句
所有的控制语句都是放在{% … %}中,并且有一个语句{% endxxx %}来进行结束,Jinja中常用的控制语句有if/for…in…,现对他们进行讲解:
- if:if语句和python中的类似,可以使用>,<,<=,>=,==,!=来进行判断,也可以通过and,or,not,()来进行逻辑合并操作,以下看例子:
- for…in…:for循环可以遍历任何一个序列包括列表、字典、元组。并且可以进行反向遍历,以下将用几个例子进行解释:
- 遍历字典:
<ul>
{% for user in users %}
<li>{{ user.username|e }}</li>
{% else %}
<li><em>no users found</em></li>
{% endfor %}
</ul>
并且Jinja中的for循环还包含以下变量,可以用来获取当前的遍历状态:
6、测试器
测试器主要用来判断一个值是否满足某种类型,并且这种类型一般通过普通的if判断是有很大的挑战的。语法是:if…is…,先来简单的看个例子:
{% if variable is escaped%}
value of variable: {{ escaped }}
{% else %}
variable is not escaped
{% endif %}
以上判断variable这个变量是否已经被转义了,Jinja中内置了许多的测试器,看以下列表:
7、宏和import语句
一、宏: 模板中的宏跟python中的函数类似,可以传递参数,但是不能有返回值,可以将一些经常用到的代码片段放到宏中,然后把一些不固定的值抽取出来当成一个变量,以下将用一个例子来进行解释:
{% macro input(name, value='', type='text') %}
<input type="{{ type }}" name="{{ name }}" value="{{ value|e }}">
{% endmacro %}
以上例子可以抽取出了一个input标签,指定了一些默认参数。那么我们以后创建input标签的时候,可以通过他快速的创建:
<p>{{ input('username') }}</p>
<p>{{ input('password', type='password') }}</p>
二、import语句: 在真实的开发中,会将一些常用的宏单独放在一个文件中,在需要使用的时候,再从这个文件中进行导入。import语句的用法跟python中的import类似,可以直接import…as…,也可以from…import…或者from…import…as…,假设现在有一个文件,叫做forms.html,里面有两个宏分别为input和textarea,如下:
{% macro input(name, value='', type='text') %}
<input type="{{ type }}" value="{{ value|e }}" name="{{ name }}">
{% endmacro %}
{% macro textarea(name, value='', rows=10, cols=40) %}
<textarea name="{{ name }}" rows="{{ rows }}" cols="{{ cols
}}">{{ value|e }}</textarea>
{% endmacro %}
三、导入宏的例子:
- import…as…形式:
{% import 'forms.html' as forms %}
<dl>
<dt>Username</dt>
<dd>{{ forms.input('username') }}</dd>
<dt>Password</dt>
<dd>{{ forms.input('password', type='password') }}</dd>
</dl>
<p>{{ forms.textarea('comment') }}</p>
- from…import…as…/from…import…形式:
{% from 'forms.html' import input as input_field, textarea %}
<dl>
<dt>Username</dt>
<dd>{{ input_field('username') }}</dd>
<dt>Password</dt>
<dd>{{ input_field('password', type='password') }}</dd>
</dl>
<p>{{ textarea('comment') }}</p>
另外需要注意的是,导入模板并不会把当前上下文中的变量添加到被导入的模板中,如果你想要导入一个需要访问当前上下文变量的宏,有两种可能的方法:
显式地传入请求或请求对象的属性作为宏的参数。
与上下文一起(with context)导入宏。
与上下文中一起(with context)导入的方式如下:
{% from '_helpers.html' import my_macro with context %}
8、include和set语句
include和set语句
一、include语句: include语句可以把一个模板引入到另外一个模板中,类似于把一个模板的代码copy到另外一个模板的指定位置,看以下例子:
{% include 'header.html' %}
主体内容
{% include 'footer.html' %}
<h3>网页头</h3>
<h3>网页尾部</h3>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{% include 'header.html' %}
<p>网页内容</p>
{% include 'footer.html'%}
</body>
</html>
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def index():
return render_template('index.html')
@app.route("/test")
def test():
return render_template('test.html')
if __name__ == '__main__':
app.run(debug=True)
结果:
二、赋值(set)语句: 有时候我们想在在模板中添加变量,这时候赋值语句(set)就派上用场了,先看以下例子:
{% set name='zhiliao' %}
那么以后就可以使用name来代替zhiliao这个值了,同时,也可以给他赋值为列表和元组:
{% set navigation = [('index.html', 'Index'), ('about.html', 'About')] %}
赋值语句创建的变量在其之后都是有效的,如果不想让一个变量污染全局环境,可以使用with语句来创建一个内部的作用域,将set语句放在其中,这样创建的变量只在with代码块中才有效,看以下示例:
{% with %}
{% set foo = 42 %}
{{ foo }} foo is 42 here
{% endwith %}
也可以在with的后面直接添加变量,比如以上的写法可以修改成这样:
{% with foo = 42 %}
{{ foo }}
{% endwith %}
这两种方式都是等价的,一旦超出with代码块,就不能再使用foo这个变量了。
9、模版继承(extends)
Flask中的模板可以继承,通过继承可以把模板中许多重复出现的元素抽取出来,放在父模板中,并且父模板通过定义block给子模板开一个口,子模板根据需要,再实现这个block,假设现在有一个base.html这个父模板,代码如下:
以上父模板中,抽取了所有模板都需要用到的元素html、body等,并且对于一些所有模板都要用到的样式文件style.css也进行了抽取,同时对于一些子模板需要重写的地方,比如title、head、body都定义成了block,然后子模板可以根据自己的需要,再具体的实现。以下再来看子模板的代码:
效果如下:
10、转义
转义的概念是,在模板渲染字符串的时候,字符串有可能包括一些非常危险的字符比如<、>等,这些字符会破坏掉原来HTML标签的结构,更严重的可能会发生XSS跨域脚本攻击,因此如果碰到<、>这些字符的时候,应该转义成HTML能正确表示这些字符的写法,比如>在HTML中应该用<来表示等。
但是Flask中默认没有开启全局自动转义,针对那些以.html、.htm、.xml和.xhtml结尾的文件,如果采用render_template函数进行渲染的,则会开启自动转义。并且当用render_template_string函数的时候,会将所有的字符串进行转义后再渲染。而对于Jinja2默认没有开启全局自动转义,作者有自己的原因:
- 渲染到模板中的字符串并不是所有都是危险的,大部分还是没有问题的,如果开启自动转义,那么将会带来大量的不必要的开销。
- Jinja2很难获取当前的字符串是否已经被转义过了,因此如果开启自动转义,将对一些已经被转义过的字符串发生二次转义,在渲染后会破坏原来的字符串。
在没有开启自动转义的模式下(比如以.conf结尾的文件),对于一些不信任的字符串,可以通过{{ content_html|e }}或者是{{ content_html|escape }}的方式进行转义。在开启了自动转义的模式下,如果想关闭自动转义,可以通过{{ content_html|safe }}的方式关闭自动转义。而{%autoescape true/false%}…{%endautoescape%}可以将一段代码块放在中间,来关闭或开启自动转义,例如以下代码关闭了自动转义:
{% autoescape false %}
<p>autoescaping is disabled here
<p>{{ will_not_be_escaped }}
{% endautoescape %}
11、数据类型和运算符
一、数据类型: Jinja支持许多数据类型,包括:字符串、整型、浮点型、列表、元组、字典、True/False。
二、运算符:
- +号运算符:可以完成数字相加,字符串相加,列表相加。但是并不推荐使用+运算符来操作字符串,字符串相加应该使用~运算符。
- -号运算符:只能针对两个数字相减。
- /号运算符:对两个数进行相除。
- %号运算符:取余运算。
- *号运算符:乘号运算符,并且可以对字符进行相乘。
- 号运算符:次幂运算符,比如23=8。
- in操作符:跟python中的in一样使用,比如{{1 in [1,2,3]}}返回true。
- ~号运算符:拼接多个字符串,比如{{“Hello” ~ “World”}}将返回HelloWorld。
12、静态文件的配置
Web应用中会出现大量的静态文件来使得网页更加生动美观。类似于CSS样式文件、JavaScript脚本文件、图片文件、字体文件等静态资源。在Jinja中加载静态文件非常简单,只需要通过url_for全局函数就可以实现,看以下代码:
<link href="{{ url_for('static',filename='about.css') }}">
url_for函数默认会在项目根目录下的static文件夹中寻找about.css文件,如果找到了,会生成一个相对于项目根目录下的/static/about.css路径。当然我们也可以把静态文件不放在static文件夹中,此时就需要具体指定了,看以下代码:
app = Flask(__name__,static_folder='C:\static')
那么访问静态文件的时候,将会到/static这个文件夹下寻找。
三、视图高级
一、类视图
之前我们接触的视图都是函数,所以一般简称视图函数。其实视图也可以基于类来实现,类视图的好处是支持继承,但是类视图不能跟函数视图一样,写完类视图还需要通过app.add_url_rule(url_rule,view_func)来进行注册。以下将对两种类视图进行讲解:
一、标准类视图: 标准类视图是继承自flask.views.View,并且在子类中必须实现dispatch_request方法,这个方法类似于视图函数,也要返回一个基于Response或者其子类的对象。以下将用一个例子进行讲解:
from flask.views import View
class PersonalView(View):
def dispatch_request(self):
return "知了课堂"
类视图通过add_url_rule方法和url做映射
app.add_url_rule('/users/',view_func=PersonalView.as_view('personalview'))
二、基于调度方法的视图: Flask还为我们提供了另外一种类视图flask.views.MethodView,对每个HTTP方法执行不同的函数(映射到对应方法的小写的同名方法上),以下将用一个例子来进行讲解:
class LoginView(views.MethodView):
def get(self):
return render_template("login.html")
def post(self):
email = request.form.get("email")
password = request.form.get("password")
if email == 'xx@qq.com' and password == '111111':
return "登录成功!"
else:
return "用户名或密码错误!"
app.add_url_rule('/myuser/',view_func=LoginView.as_view('loginview'))
如果用类视图,我们怎么使用装饰器呢?比如有时候需要做权限验证的时候,比如看以下例子:
from flask import session
def login_required(func):
def wrapper(*args,**kwargs):
if not session.get("user_id"):
return 'auth failure'
return func(*args,**kwargs)
return wrapper
装饰器写完后,可以在类视图中定义一个属性叫做decorators,然后存储装饰器。以后每次调用这个类视图的时候,就会执行这个装饰器。示例代码如下:
class UserView(views.MethodView):
decorators = [user_required]
...
二、蓝图和子域名
一、蓝图 之前我们写的url和视图函数都是处在同一个文件,如果项目比较大的话,这显然不是一个合理的结构,而蓝图可以优雅的帮我们实现这种需求。以下看一个使用蓝图的文件的例子:
静态文件默认到static文件夹中查找,模板文件默认到templates文件夹下查找,一般不建议修改默认路径~
二、子域名 子域名在许多网站中都用到了,比如一个网站叫做xxx.com,那么我们可以定义一个子域名cms.xxx.com来作为cms管理系统的网址,子域名的实现一般也是通过蓝图来实现,在之前章节中,我们创建蓝图的时候添加了一个url_prefix=/user作为url前缀,那样我们就可以通过/user/来访问user下的url。但使用子域名则不需要。另外,还需要配置SERVER_NAME,比如app.config[SERVER_NAME]=‘example.com:9000’。并且在注册蓝图的时候,还需要添加一个subdomain的参数,这个参数就是子域名的名称,先来看一下蓝图的实现(admin.py):
from flask import Blueprint
bp = Blueprint('admin',__name__,subdomain='admin')
@bp.route('/')
def admin():
return 'Admin Page'
这个没有多大区别,接下来看主app的实现:
from flask import Flask
import admin
app.config['SERVER_NAME'] = 'example.com:8000'
app.register_blueprint(admin.bp)
if __name__ == '__main__':
app.run(host='0.0.0.0',port=8000,debug=True)
写完以上两个文件后,还是不能正常的访问admin.example.com:8000这个子域名,因为我们没有在host文件中添加域名解析,你可以在最后添加一行127.0.0.1 admin.example.com,就可以访问到了。另外,子域名不能在127.0.0.1上出现,也不能在localhost上出现。
四、SQLAlchemy
MySQL数据库: 在网站开发中,数据库是网站的重要组成部分。只有提供数据库,数据才能够动态的展示,而不是在网页中显示一个静态的页面。数据库有很多,比如有SQL Server、Oracle、PostgreSQL以及MySQL等等。MySQL由于价格实惠、简单易用、不受平台限制、灵活度高等特性,目前已经取得了绝大多数的市场份额。因此我们在Flask中,也是使用MySQL来作为数据存储。
1、mysql安装
1、在MySQL的官网下载MySQL数据库安装文件:https://dev.mysql.com/downloads/mysql/
2、安装步骤略
2、navicat数据库操作软件:
安装完MySQL数据库以后,就可以使用MySQL提供的终端客户端软件来操作数据库。如下: 这个软件所有的操作都是基于sql语言,对于想要熟练sql语言的同学来讲是非常合适的。但是对于在企业中可能不是一款好用的工具。在企业中我们推荐使用mysql workbench以及navicat这种图形化操作的软件。而mysql workbench是mysql官方提供的一个免费的软件,正因为是免费,所以在一些功能上不及navicat。navicat for mysql是一款收费的软件。官网地址如下:https://www.navicat.com.cn/products。使用的截图如下:
3、MySQL驱动程序安装:
我们使用Django来操作MySQL,实际上底层还是通过Python来操作的。因此我们想要用Flask来操作MySQL,首先还是需要安装一个驱动程序。在Python3中,驱动程序有多种选择。比如有pymysql以及mysqlclient等。这里我们就使用mysqlclient来操作。mysqlclient安装非常简单。只需要通过pip install mysqlclient即可安装。
4、Flask-SQLAlchemy连接数据库
1、SQLAlchemy和Flask-SQLAlchemy的区别: SQLAlchemy:是一个独立的ORM框架,可以独立于Flask存在,也可以在其他项目中使用,比如在Django中。 Flask-SQLAlchemy:对SQLAlchemy的一个封装,能够更适合在flask中使用。(所以此处没有必要从SQLAlchemy学起)
2、安装和验证:
- 安装连接数据库的库:pip install pymysql
- 安装:pip install flask-sqlalchemy
3、连接数据库:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
HOSTNAME = '127.0.0.1'
PORT = '3306'
DATABASE = 'xt_flask'
USERNAME = 'root'
PASSWORD = 'root'
DB_URI = 'mysql+pymysql://{}:{}@{}:{}/{}?charset=utf8'.format(USERNAME,PASSWORD,HOSTNAME,PORT,DATABASE)
app.config['SQLALCHEMY_DATABASE_URI'] = DB_URI
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
db = SQLAlchemy(app)
@app.route('/')
def hello_world():
engine = db.get_engine()
with engine.connect() as conn:
result = conn.execute("select 1")
print(result.fetchone())
return 'Hello World!'
if __name__ == '__main__':
app.run()
5、ORM模型映射到数据库
1、要使用ORM来操作数据库,首先需要创建一个类来与对应的表进行映射。
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
HOSTNAME = '127.0.0.1'
PORT = '3306'
DATABASE = 'zl_flask'
USERNAME = 'root'
PASSWORD = 'root'
DB_URI = 'mysql+pymysql://{}:{}@{}:{}/{}?charset=utf8'.format(USERNAME,PASSWORD,HOSTNAME,PORT,DATABASE)
app.config['SQLALCHEMY_DATABASE_URI'] = DB_URI
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
db = SQLAlchemy(app)
class Article(db.Model):
__tablename__ = "article"
id = db.Column(db.Integer,primary_key=True,autoincrement=True)
title = db.Column(db.String(200),nullable=False)
content = db.Column(db.Text,nullable=False)
db.create_all()
6、ORM增删改查操作
2、添加数据
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
HOSTNAME = '127.0.0.1'
PORT = '3306'
DATABASE = 'zl_flask'
USERNAME = 'root'
PASSWORD = 'root'
DB_URI = 'mysql+pymysql://{}:{}@{}:{}/{}?charset=utf8'.format(USERNAME,PASSWORD,HOSTNAME,PORT,DATABASE)
app.config['SQLALCHEMY_DATABASE_URI'] = DB_URI
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
db = SQLAlchemy(app)
class Article(db.Model):
__tablename__ = "article"
id = db.Column(db.Integer,primary_key=True,autoincrement=True)
title = db.Column(db.String(200),nullable=False)
content = db.Column(db.Text,nullable=False)
db.create_all()
@app.route("/article")
def article_view():
insert table article values(xx)
article = Article(title="钢铁是怎样炼成的",content="xxx")
db.session.add(article)
db.session.commit()
3、查询数据
参考以上代码~ 4、修改数据
参考以上代码~
article = Article.query.filter_by(id=1)[0]
article.content = "yyy"
db.session.commit()
return "数据操作成功"
刷新网页,然后刷新navicat 5、删除数据
参考以上代码~
Article.query.filter_by(id=1).delete()
db.session.commit()
return "数据操作成功"
7、表关系
表之间的关系存在三种:一对一、一对多、多对多。而SQLAlchemy中的ORM也可以模拟这三种关系。因为一对一其实在SQLAlchemy中底层是通过一对多的方式模拟的,所以先来看下一对多的关系:
1、外键 在Mysql中,外键可以让表之间的关系更加紧密。而SQLAlchemy同样也支持外键。通过ForeignKey类来实现,并且可以指定表的外键约束。
2、一对多
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
HOSTNAME = '127.0.0.1'
PORT = '3306'
DATABASE = 'zl_flask'
USERNAME = 'root'
PASSWORD = 'root'
DB_URI = 'mysql+pymysql://{}:{}@{}:{}/{}?charset=utf8'.format(USERNAME,PASSWORD,HOSTNAME,PORT,DATABASE)
app.config['SQLALCHEMY_DATABASE_URI'] = DB_URI
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
db = SQLAlchemy(app)
migrate = Migrate(app,db)
class User(db.Model):
__tablename__ = "user"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
username = db.Column(db.String(200),nullable=False)
user = db.relationship("User",backref=db.backref("extension",uselist=False))
class Article(db.Model):
__tablename__ = "article"
id = db.Column(db.Integer,primary_key=True,autoincrement=True)
title = db.Column(db.String(200),nullable=False)
content = db.Column(db.Text,nullable=False)
author_id = db.Column(db.Integer,db.ForeignKey("user.id"))
author = db.relationship("User",backref="articles")
db.drop_all()
db.create_all()
@app.route("/otm")
def one_to_many():
article1 = Article(title="111",content="xxx")
article2 = Article(title="222", content="yyy")
user = User(username="zhiliao")
article1.author = user
article2.author = user
db.session.add(article1,article2)
db.session.commit()
print(user.articles)
return "one to many数据操作成功"
3、一对一
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
HOSTNAME = '127.0.0.1'
PORT = '3306'
DATABASE = 'zl_flask'
USERNAME = 'root'
PASSWORD = 'root'
DB_URI = 'mysql+pymysql://{}:{}@{}:{}/{}?charset=utf8'.format(USERNAME,PASSWORD,HOSTNAME,PORT,DATABASE)
app.config['SQLALCHEMY_DATABASE_URI'] = DB_URI
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
db = SQLAlchemy(app)
migrate = Migrate(app,db)
class User(db.Model):
__tablename__ = "user"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
username = db.Column(db.String(200),nullable=False)
class UserExtension(db.Model):
__tablename__ = "user_extension"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
school = db.Column(db.String(100))
user_id = db.Column(db.Integer,db.ForeignKey("user.id"))
user = db.relationship("User",backref=db.backref("extension",uselist=False))
class Article(db.Model):
__tablename__ = "article"
id = db.Column(db.Integer,primary_key=True,autoincrement=True)
title = db.Column(db.String(200),nullable=False)
content = db.Column(db.Text,nullable=False)
author_id = db.Column(db.Integer,db.ForeignKey("user.id"))
author = db.relationship("User",backref="articles")
@app.route("/oto")
def one_to_one():
user = User(username="zhiliao")
extension = UserExtension(school="清华大学")
user.extension = extension
db.session.add(user)
db.session.commit()
return "one to one"
五、ORM迁移
1、ORM迁移与表的映射
Flask-Migrate插件 在实际的开发环境中,经常会发生数据库修改的行为。一般我们修改数据库不会直接手动的去修改,而是去修改ORM对应的模型,然后再把模型映射到数据库中。这时候如果有一个工具能专门做这种事情,就显得非常有用了,而flask-migrate就是做这个事情的。flask-migrate是基于Alembic进行的一个封装,并集成到Flask中,而所有的迁移操作其实都是Alembic做的,他能跟踪模型的变化,并将变化映射到数据库中。
使用Flask-Migrate需要安装,命令如下:
pip install flask-migrate
初始化动作只需要做一次~
2、项目重构
如果将所有代码都写在一个文件中,这样会导致文件会越来越乱。所以需要进行一下项目重构,设置为以下的目录结构:
app.py
from flask import Flask
from flask_migrate import Migrate
from models import Article,User,UserExtension
import config
from exts import db
app = Flask(__name__)
app.config.from_object(config)
db.init_app(app)
migrate = Migrate(app,db)
@app.route("/otm")
def one_to_many():
article1 = Article(title="111",content="xxx")
article2 = Article(title="222", content="yyy")
user = User(username="zhiliao")
article1.author = user
article2.author = user
db.session.add(article1,article2)
db.session.commit()
print(user.articles)
return "one to many数据操作成功"
@app.route("/oto")
def one_to_one():
user = User(username="zhiliao")
extension = UserExtension(school="清华大学")
user.extension = extension
db.session.add(user)
db.session.commit()
return "one to one"
@app.route("/article")
def article_view():
Article.query.filter_by(id=1).delete()
db.session.commit()
return "数据操作成功"
@app.route('/')
def hello_world():
engine = db.get_engine()
with engine.connect() as conn:
result = conn.execute("select 1")
print(result.fetchone())
return 'Hello World!'
if __name__ == '__main__':
app.run()
config.py
HOSTNAME = '127.0.0.1'
PORT = '3306'
DATABASE = 'zl_flask'
USERNAME = 'root'
PASSWORD = 'root'
DB_URI = 'mysql+pymysql://{}:{}@{}:{}/{}?charset=utf8'.format(USERNAME,PASSWORD,HOSTNAME,PORT,DATABASE)
SQLALCHEMY_DATABASE_URI = DB_URI
SQLALCHEMY_TRACK_MODIFICATIONS = True
exts.py
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
models.py
from exts import db
class User(db.Model):
__tablename__ = "user"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
username = db.Column(db.String(200),nullable=False)
class UserExtension(db.Model):
__tablename__ = "user_extension"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
school = db.Column(db.String(100))
user_id = db.Column(db.Integer,db.ForeignKey("user.id"))
user = db.relationship("User",backref=db.backref("extension",uselist=False))
class Article(db.Model):
__tablename__ = "article"
id = db.Column(db.Integer,primary_key=True,autoincrement=True)
title = db.Column(db.String(200),nullable=False)
content = db.Column(db.Text,nullable=False)
author_id = db.Column(db.Integer,db.ForeignKey("user.id"))
author = db.relationship("User",backref="articles")
测试重构结果
测试ORM迁移结果
六、cookie和session
1、cookie和session概念
-
cookie:在网站中,http请求是无状态的。也就是说即使第一次和服务器连接后并且登录成功后,第二次请求服务器依然不能知道当前请求是哪个用户。cookie的出现就是为了解决这个问题,第一次登录后服务器返回一些数据(cookie)给浏览器,然后浏览器保存在本地,当该用户发送第二次请求的时候,就会自动的把上次请求存储的cookie数据自动的携带给服务器,服务器通过浏览器携带的数据就能判断当前用户是哪个了。cookie存储的数据量有限,不同的浏览器有不同的存储大小,但一般不超过4KB。因此使用cookie只能存储一些小量的数据。 -
session: session和cookie的作用有点类似,都是为了存储用户相关的信息。不同的是,cookie是存储在本地浏览器,而session存储在服务器,不同的服务器,不同的框架,不同的语言有不同的实现。虽然实现不一样,但是他们的目的都是服务器为了方便存储数据的。session的出现,是为了解决cookie存储数据不安全的问题的。 -
cookie和session结合使用:web开发发展至今,cookie和session的使用已经出现了一些非常成熟的方案。在如今的市场或者企业里,一般有两种存储方式:
1、存储在服务端:通过cookie存储一个session_id,然后具体的数据则是保存在session中。如果用户已经登录,则服务器会在cookie中保存一个session_id,下次再次请求的时候,会把该session_id携带上来,服务器根据session_id在session库中获取用户的session数据。就能知道该用户到底是谁,以及之前保存的一些状态信息。这种专业术语叫做server side session。存储在服务器的数据会更加的安全,不容易被窃取。但存储在服务器也有一定的弊端,就是会占用服务器的资源,但现在服务器已经发展至今,一些session信息还是绰绰有余的。
2、将session数据加密,然后存储在cookie中。这种专业术语叫做client side session。flask采用的就是这种方式,但是也可以替换成其他形式。
2、flask中使用cookie和session
1、cookies:在Flask中操作cookie,是通过response对象来操作,可以在response返回之前,通过response.set_cookie来设置,这个方法有以下几个参数需要注意:
- key:设置的cookie的key。
- value:key对应的value。
- max_age:改cookie的过期时间,如果不设置,则浏览器关闭后就会自动过期。
- expires:过期时间,应该是一个datetime类型。
- domain:该cookie在哪个域名中有效。一般设置子域名,比如cms.example.com。
- path:该cookie在哪个路径下有效。
2、session:Flask中的session是通过from flask import session。然后添加值key和value进去即可。并且,Flask中的session机制是将session信息加密,然后存储在cookie中。专业术语叫做client side session。
完整的测试代码:
3、set_cookie
from flask import Flask,Response
app = Flask(__name__)
@app.route("/set_cookie")
def set_cookie():
response = Response("cookie 设置")
response.set_cookie("user_id","xxx")
return response
@app.route('/')
def hello_world():
return 'Hello World!'
if __name__ == '__main__':
app.run()
4、get_cookie
5、set_session
from flask import Flask,Response,request,session
app = Flask(__name__)
app.config['SECRET_KEY'] = "12asdfadfdsfasd3"
@app.route("/set_cookie")
def set_cookie():
response = Response("cookie 设置")
response.set_cookie("user_id","xxx")
return response
@app.route("/get_cookie")
def get_cookie():
user_id = request.cookies.get("user_id")
print("user_id:",user_id)
return "获取cookie"
@app.route("/set_session")
def set_session():
session['username'] = "zhiliao"
return "session设置成功"
@app.route('/')
def hello_world():
return 'Hello World!'
if __name__ == '__main__':
app.run()
6、get_session
from flask import Flask,Response,request,session
app = Flask(__name__)
app.config['SECRET_KEY'] = "12asdfadfdsfasd3"
@app.route("/set_cookie")
def set_cookie():
response = Response("cookie 设置")
response.set_cookie("user_id","xxx")
return response
@app.route("/get_cookie")
def get_cookie():
user_id = request.cookies.get("user_id")
print("user_id:",user_id)
return "获取cookie"
@app.route("/set_session")
def set_session():
session['username'] = "zhiliao"
return "session设置成功"
@app.route("/get_session")
def get_session():
username = session.get('username')
print("username:",username)
return "get session"
@app.route('/')
def hello_world():
return 'Hello World!'
if __name__ == '__main__':
app.run()
七、Flask-WTF表单验证
Flask-WTF是简化了WTForms操作的一个第三方库。WTForms表单的两个主要功能是验证用户提交数据的合法性以及渲染模板。当然还包括一些其他的功能:CSRF保护,文件上传等。安装Flask-WTF默认也会安装WTForms,因此使用以下命令来安装Flask-WTF:
pip install flask-wtf
1、表单验证
代码框架预览:
app.py
from flask import Flask,request,render_template
from forms import LoginForm
app = Flask(__name__)
@app.route('/login',methods=['GET','POST'])
def login():
if request.method == 'GET':
return render_template("login.html")
else:
form = LoginForm(request.form)
if form.validate():
return "登录成功!"
else:
return "邮箱或密码错误!"
@app.route('/')
def hello_world():
return 'Hello World!'
if __name__ == '__main__':
app.run()
forms.py
import wtforms
from wtforms.validators impor length,email
class LoginForm(wtforms.Form):
email = wtforms.StringField(validators=[length(min=5,max=20),email()])
password = wtforms.StringField(validators=[length(min=6,max=20)])
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<form action="/login" method="post">
<table>
<tbody>
<tr>
<td>邮箱:</td>
<td>
<input type="text" name="email">
</td>
</tr>
<tr>
<td>密码:</td>
<td><input type="text" name="password"></td>
</tr>
<tr>
<td></td>
<td><button>登录</button></td>
</tr>
</tbody>
</table>
</form>
</body>
</html>
报错解决
pip install email_validator
正确访问界面
|