赞
踩
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return 'Welcome to My Watchlist!'
默认是app.py,通过设置系统环境变量FLASK_APP设置启动程序,可以利用python-dotenv读取环境变量,.flaskenv
用来存储Flask命令行系统相关的公开环境变量;而.env
则用来存储敏感数据,不应该提交进Git仓库
用户输入的数据会包含恶意代码,所以不能直接作为响应返回,需要使用MarkupSafe(Flask的依赖之一)提供的escape()函数对name变量进行转义处理,比如把<转换成 <.这样在返回响应时浏览器就不会把它们当做代码执行.
from markupsafe import escape
@app.route('/user/<name>')
def user_page(name):
return f'User: {escape(name)}'
from flask import url_for from markupsafe import escape # ... @app.route('/') def hello(): return 'Hello' @app.route('/user/<name>') def user_page(name): return f'User: {escape(name)}' @app.route('/test') def test_url_for(): # 下面是一些调用示例(请访问 http://localhost:5000/test 后在命令行窗口查看输出的 URL): print(url_for('hello')) # 生成 hello 视图函数对应的 URL,将会输出:/ # 注意下面两个调用是如何生成包含 URL 变量的 URL 的 print(url_for('user_page', name='greyli')) # 输出:/user/greyli print(url_for('user_page', name='peter')) # 输出:/user/peter print(url_for('test_url_for')) # 输出:/test # 下面这个调用传入了多余的关键字参数,它们会被作为查询字符串附加到 URL 后面。 print(url_for('test_url_for', num=2)) # 输出:/test?num=2 return 'Test page'
Flask会从程序实例所在模块同级目录的templates文件夹中寻找模板
Jinja2还支持列表、字典和对象
{{ mydict['key'] }}
{{ mylist[3] }}
{{ mylist[myintvar] }}
{{ myobj.somemethod() }}
获取变量的属性有以下两种方式
{{ foo.bar }}
{{ foo['bar'] }}
使用格式变量名|函数
,将变量传给函数,再把函数值作为代码块的值,可以通过|
调用多个函数
例子:
<!-- 带参数的 -->
{{变量 | 函数名(*args)}}
<!-- 不带参数可以省略括号 -->
{{变量 | 函数名}}
字符串操作:
safe:禁用转义 <p>{{ '<em>hello</em>' | safe }}</p> capitalize:把变量值的首字母转成大写,其余字母转小写 <p>{{ 'hello' | capitalize }}</p> lower:把值转成小写 <p>{{ 'HELLO' | lower }}</p> upper:把值转成大写 <p>{{ 'hello' | upper }}</p> title:把值中的每个单词的首字母都转成大写 <p>{{ 'hello' | title }}</p> reverse:字符串反转 <p>{{ 'olleh' | reverse }}</p> format:格式化输出 <p>{{ '%s is %d' | format('name',17) }}</p> striptags:渲染之前把值中所有的HTML标签都删掉 <p>{{ '<em>hello</em>' | striptags }}</p> truncate: 字符串截断 <p>{{ 'hello every one' | truncate(9)}}</p>
列表操作:
first:取第一个元素
<p>{{ [1,2,3,4,5,6] | first }}</p>
last:取最后一个元素
<p>{{ [1,2,3,4,5,6] | last }}</p>
length:获取列表长度
<p>{{ [1,2,3,4,5,6] | length }}</p>
sum:列表求和
<p>{{ [1,2,3,4,5,6] | sum }}</p>
sort:列表排序
<p>{{ [6,2,3,1,5,4] | sort }}</p>
{% if user.age is equalto 42 %}
Ha, you are 42!
{% endif %}
如果要传入参数,可以在test后增加括号,也可以直接写在后面
常用的test
boolean,defined,equalto
escaped,none,sequence
string,number,reverse
replace
例子
<h1>Members</h1>
<ul>
{% for user in users %}
<li>{{ user.username|e }}</li>
{% endfor %}
</ul>
<dl>
{% for key, value in my_dict.items() %}
<dt>{{ key|e }}</dt>
<dd>{{ value|e }}</dd>
{% endfor %}
</dl>
循环中的索引:
{% if users %}
<ul>
{% for user in users %}
<li>{{ user.username|e }}</li>
{% endfor %}
</ul>
{% endif %}
{% if kenny.sick %}
Kenny is sick.
{% elif kenny.dead %}
You killed Kenny! You bastard!!!
{% else %}
Kenny looks okay --- so far
{% endif %}
在templates目录下创建一个index.html文件作为主页模板
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>{{ name }}'s Watchlist</title> </head> <body> <h2>{{ name }}'s Watchlist</h2> {# 使用 length 过滤器获取 movies 变量的长度 #} <p>{{ movies|length }} Titles</p> <ul> {% for movie in movies %} {# 迭代 movies 变量 #} <li>{{ movie.title }} - {{ movie.year }}</li> {# 等同于 movie['title'] #} {% endfor %} {# 使用 endfor 标签结束 for 语句 #} </ul> <footer> <small>© 2018 <a href="http://helloflask.com/book/3">HelloFlask</a></small> </footer> </body> </html>
然后准备虚拟数据并利用flask中的render_template()渲染模板
name = 'Grey Li' movies = [ {'title': 'My Neighbor Totoro', 'year': '1988'}, {'title': 'Dead Poets Society', 'year': '1989'}, {'title': 'A Perfect World', 'year': '1993'}, {'title': 'Leon', 'year': '1994'}, {'title': 'Mahjong', 'year': '1996'}, {'title': 'Swallowtail Butterfly', 'year': '1996'}, {'title': 'King of Comedy', 'year': '1999'}, {'title': 'Devils on the Doorstep', 'year': '1999'}, {'title': 'WALL-E', 'year': '2008'}, {'title': 'The Pork of Music', 'year': '2012'}, ] from flask import Flask, render_template # ... @app.route('/') def index(): return render_template('index.html', name=name, movies=movies)
图片、css文件和JavaScript脚本文件存储在静态文件中,可以利用url_for文件获取文件的url,例如:
<img src="{{ url_for('static', filename='foo.jpg') }}">
在python脚本中,url_for()需要从flask包中导入,而在模板中可以直接使用,因为flask把一些常用的函数和对象添加到了模板的上下文中
<head>
...
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}">
</head>
<h2>
<img alt="Avatar" src="{{ url_for('static', filename='images/avatar.png') }}">
{{ name }}'s Watchlist
</h2>
...
<img alt="Walking Totoro" src="{{ url_for('static', filename='images/totoro.gif') }}">
static/style.css:定义页面样式
/* 页面整体 */ body { margin: auto; max-width: 580px; font-size: 14px; font-family: Helvetica, Arial, sans-serif; } /* 页脚 */ footer { color: #888; margin-top: 15px; text-align: center; padding: 10px; } /* 头像 */ .avatar { width: 40px; } /* 电影列表 */ .movie-list { list-style-type: none; padding: 0; margin-bottom: 10px; box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12); } .movie-list li { padding: 12px 24px; border-bottom: 1px solid #ddd; } .movie-list li:last-child { border-bottom:none; } .movie-list li:hover { background-color: #f8f9fa; } /* 龙猫图片 */ .totoro { display: block; margin: 0 auto; height: 100px; }
引用css文件
<head>
...
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}" type="text/css">
</head>
添加class属性
<h2>
<img alt="Avatar" class="avatar" src="{{ url_for('static', filename='images/avatar.png') }}">
{{ name }}'s Watchlist
</h2>
...
<ul class="movie-list">
...
</ul>
<img alt="Walking Totoro" class="totoro" src="{{ url_for('static', filename='images/totoro.gif') }}">
使用一个叫做Flask-SQLAlchemy的扩展来集成SQLAlchemy,利用pip3分别安装这两个包
例子:
import os import sys from flask import Flask from flask_sqlalchemy import SQLAlchemy WIN = sys.platform.startswith('win') if WIN: # 如果是 Windows 系统,使用三个斜线 prefix = 'sqlite:///' else: # 否则使用四个斜线 prefix = 'sqlite:' app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI'] = prefix + os.path.join(app.root_path, 'data.db') app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False # 关闭对模型修改的监控 # 在扩展类实例化前加载配置 db = SQLAlchemy(app)
class User(db.Model): # 表名将会是 user(自动生成,小写处理)
id = db.Column(db.Integer, primary_key=True) # 主键
name = db.Column(db.String(20)) # 名字
class Movie(db.Model): # 表名将会是 movie
id = db.Column(db.Integer, primary_key=True) # 主键
title = db.Column(db.String(60)) # 电影标题
year = db.Column(db.String(4)) # 电影年份
模型的编写需要有限制:
字段类 | 说明 |
---|---|
db.Integer | 整型 |
db.String (size) | 字符串,size 为最大长度 |
db.Text | 长文本 |
db.DateTime | 时间日期,Python datetime对象 |
db.Float | 浮点数 |
db.Boolean | 布尔值 |
接下来要在python shell中创建这些文件
(env) $ flask shell
>>> from app import db
>>> db.create_all()
>>> db.drop_all()
>>> db.create_all()
和 lask shell类似,我们可以编写一个自定义命令来自动执行创建数据库表操作:
import click
@app.cli.command() # 注册为命令,可以传入 name 参数来自定义命令
@click.option('--drop', is_flag=True, help='Create after drop.') # 设置选项
def initdb(drop):
"""Initialize the database."""
if drop: # 判断是否输入了选项
db.drop_all()
db.create_all()
click.echo('Initialized database.') # 输出提示信息
之后便可用命令行重建数据库
(env) $ flask initdb
(env) $ flask initdb --drop
# 使用 --drop 选项可以删除表后重新创建
>>> from app import User, Movie # 导入模型类
>>> user = User(name='Grey Li') # 创建一个 User 记录
>>> m1 = Movie(title='Leon', year='1994') # 创建一个 Movie 记录
>>> m2 = Movie(title='Mahjong', year='1996') # 再创建一个 Movie 记录
>>> db.session.add(user) # 把新创建的记录添加到数据库会话
>>> db.session.add(m1)
>>> db.session.add(m2)
>>> db.session.commit() # 提交数据库会话,只需要在最后调用一次即可
通过对模型类query属性调用可选过滤方法和查询方法.
<模型类>.query.<过滤方法(可选)>.<查询方法>
常用的过滤方法:
过滤方法 | 说明 |
---|---|
filter() | 使用指定的规则过滤记录,返回新产生的查询对象 |
filter_by() | 使用指定规则过滤记录(以关键字表达式的形式),返回新产生的查询对象 |
order_by() | 根据指定条件对记录进行排序,返回新产生的查询对象 |
group_by() | 根据指定条件对记录进行分组,返回新产生的查询对象 |
常用查询的方法:
查询方法 | 说明 |
---|---|
all() | 返回包含所有查询记录的列表 |
first() | 返回查询的第一条记录,如果未找到,则返回 None |
get(id) | 传入主键值作为参数,返回指定主键值的记录,如果未找到,则返回 None |
count() | 返回查询结果的数量 |
first_or_404() | 返回查询的第一条记录,如果未找到,则返回 404 错误响应 |
get_or_404(id) | 传入主键值作为参数,返回指定主键值的记录,如果未找到,则返回 404 错误响应 |
paginate() | 返回一个 Pagination 对象,可以对记录进行分页处理 |
例子:
>>> from app import Movie # 导入模型类 >>> movie = Movie.query.first() # 获取 Movie 模型的第一个记录(返回模型类实例) >>> movie.title # 对返回的模型类实例调用属性即可获取记录的各字段数据 'Leon' >>> movie.year '1994' >>> Movie.query.all() # 获取 Movie 模型的所有记录,返回包含多个模型类实例的列表 [<Movie 1>, <Movie 2>] >>> Movie.query.count() # 获取 Movie 模型所有记录的数量 2 >>> Movie.query.get(1) # 获取主键值为 1 的记录 <Movie 1> >>> Movie.query.filter_by(title='Mahjong').first() # 获取 title 字段值为 Mahjong 的记录 <Movie 2> >>> Movie.query.filter(Movie.title=='Mahjong').first() # 等同于上面的查询,但使用不同的过滤方法 <Movie 2>
提示:我们在说 Movie 模型的时候,实际指的是数据库中的 movie 表。表的实际名称是模型类的小写形式(自动生成),如果你想自己指定表名,可以定义
__tablename__
属性。
>>> movie = Movie.query.get(2)
>>> movie.title = 'WALL-E' # 直接对实例属性赋予新的值即可
>>> movie.year = '2008'
>>> db.session.commit() # 注意仍然需要调用这一行来提交改动
>>> movie = Movie.query.get(1)
>>> db.session.delete(movie) # 使用 db.session.delete() 方法删除记录,传入模型实例
>>> db.session.commit() # 提交改动
@app.route('/')
def index():
user = User.query.first() # 读取用户记录
movies = Movie.query.all() # 读取所有电影记录
return render_template('index.html', user=user, movies=movies)
#同时index.html中的两处name变量也要相应的更新为user.name属性
import click @app.cli.command() def forge(): """Generate fake data.""" db.create_all() # 全局的两个变量移动到这个函数内 name = 'Grey Li' movies = [ {'title': 'My Neighbor Totoro', 'year': '1988'}, {'title': 'Dead Poets Society', 'year': '1989'}, {'title': 'A Perfect World', 'year': '1993'}, {'title': 'Leon', 'year': '1994'}, {'title': 'Mahjong', 'year': '1996'}, {'title': 'Swallowtail Butterfly', 'year': '1996'}, {'title': 'King of Comedy', 'year': '1999'}, {'title': 'Devils on the Doorstep', 'year': '1999'}, {'title': 'WALL-E', 'year': '2008'}, {'title': 'The Pork of Music', 'year': '2012'}, ] user = User(name=name) db.session.add(user) for m in movies: movie = Movie(title=m['title'], year=m['year']) db.session.add(movie) db.session.commit() click.echo('Done.')
如此执行flask forge命令可以将虚拟数据添加到数据库里
首先编写错误界面模板404.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>{{ user.name }}'s Watchlist</title> <link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}" type="text/css"> </head> <body> <h2> <img alt="Avatar" class="avatar" src="{{ url_for('static', filename='images/avatar.png') }}"> {{ user.name }}'s Watchlist </h2> <ul class="movie-list"> <li> Page Not Found - 404 <span class="float-right"> <a href="{{ url_for('index') }}">Go Back</a> </span> </li> </ul> <footer> <small>© 2018 <a href="http://helloflask.com/book/3">HelloFlask</a></small> </footer> </body> </html>
接着使用app.errorhandler()装饰器注册错误处理函数
@app.errorhandler(404) # 传入要处理的错误代码
def page_not_found(e): # 接受异常对象作为参数
user = User.query.first()
return render_template('404.html', user=user), 404 # 返回模板和状态码
使用app.context_processor装饰器注册一个模板上下文处理函数,这个函数返回的变量(以字典键值对的形式)将会统一注入到每一个模板的上下文环境中,因此可以直接在模板中使用,如此便可简化上个404代码撰写
@app.context_processor
def inject_user():
user = User.query.first()
return dict(user=user)
@app.errorhandler(404)
def page_not_found(e):
return render_template('404.html'), 404
@app.route('/')
def index():
movies = Movie.query.all()
return render_template('index.html', movies=movies)
编写基础模板base.html
<!DOCTYPE html> <html lang="en"> <head> {% block head %} <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>{{ user.name }}'s Watchlist</title> <link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}" type="text/css"> {% endblock %} </head> <body> <h2> <img alt="Avatar" class="avatar" src="{{ url_for('static', filename='images/avatar.png') }}"> {{ user.name }}'s Watchlist </h2> <nav> <ul> <li><a href="{{ url_for('index') }}">Home</a></li> </ul> </nav> {% block content %}{% endblock %} <footer> <small>© 2018 <a href="http://helloflask.com/book/3">HelloFlask</a></small> </footer> </body> </html>
导航栏对应的CSS代码如下所示:
nav ul { list-style-type: none; margin: 0; padding: 0; overflow: hidden; background-color: #333; } nav li { float: left; } nav li a { display: block; color: white; text-align: center; padding: 8px 12px; text-decoration: none; } nav li a:hover { background-color: #111; }
如此,子模板的编写将会变的非常的简单,如下:
{% extends 'base.html' %}
{% block content %}
<p>{{ movies|length }} Titles</p>
<ul class="movie-list">
{% for movie in movies %}
<li>{{ movie.title }} - {{ movie.year }}
<span class="float-right">
<a class="imdb" href="https://www.imdb.com/find?q={{ movie.title }}" target="_blank" title="Find this movie on IMDb">IMDb</a>
</span>
</li>
{% endfor %}
</ul>
<img alt="Walking Totoro" class="totoro" src="{{ url_for('static', filename='images/totoro.gif') }}" title="to~to~ro~">
{% endblock %}
{% extends 'base.html' %}
{% block content %}
<ul class="movie-list">
<li>
Page Not Found - 404
<span class="float-right">
<a href="{{ url_for('index') }}">Go Back</a>
</span>
</li>
</ul>
{% endblock %}
<span class="float-right">
<a class="imdb" href="https://www.imdb.com/find?q={{ movie.title }}" target="_blank" title="Find this movie on IMDb">IMDb</a>
</span>
对应css如下:
.float-right {
float: right;
}
.imdb {
font-size: 12px;
font-weight: bold;
color: black;
text-decoration: none;
background: #F5C518;
border-radius: 5px;
padding: 3px 5px;
}
在html里,用户编写表单获取用户输入,一个典型的表单如下所示:
<form method="post"> <!-- 指定提交方法为 POST -->
<label for="name">名字</label>
<input type="text" name="name" id="name"><br> <!-- 文本输入框 -->
<label for="occupation">职业</label>
<input type="text" name="occupation" id="occupation"><br> <!-- 文本输入框 -->
<input type="submit" name="submit" value="登录"> <!-- 提交按钮 -->
</form>
<p>{{ movies|length }} Titles</p>
<form method="post">
Name <input type="text" name="title" autocomplete="off" required>
Year <input type="text" name="year" autocomplete="off" required>
<input class="btn" type="submit" name="submit" value="Add">
</form>
css如下:
/* 覆盖某些浏览器对 input 元素定义的字体 */ input[type=submit] { font-family: inherit; } input[type=text] { border: 1px solid #ddd; } input[name=year] { width: 50px; } .btn { font-size: 12px; padding: 3px 5px; text-decoration: none; cursor: pointer; background-color: white; color: black; border: 1px solid #555555; border-radius: 5px; } .btn:hover { text-decoration: none; background-color: black; color: white; border: 1px solid black; }
在 HTTP 中,GET 和 POST 是两种最常见的请求方法,其中 GET 请求用来获取资源,而 POST 则用来创建 / 更新资源。我们访问一个链接时会发送 GET 请求,而提交表单通常会发送 POST 请求.为了能够处理 POST 请求,我们需要修改一下视图函数:
from flask import request, url_for, redirect, flash # ... @app.route('/', methods=['GET', 'POST']) def index(): if request.method == 'POST': # 判断是否是 POST 请求 # 获取表单数据 title = request.form.get('title') # 传入表单对应输入字段的 name 值 year = request.form.get('year') # 验证数据 if not title or not year or len(year) > 4 or len(title) > 60: flash('Invalid input.') # 显示错误提示 return redirect(url_for('index')) # 重定向回主页 # 保存表单数据到数据库 movie = Movie(title=title, year=year) # 创建记录 db.session.add(movie) # 添加到数据库会话 db.session.commit() # 提交数据库会话 flash('Item created.') # 显示成功创建的提示 return redirect(url_for('index')) # 重定向回主页 movies = Movie.query.all() return render_template('index.html', movies=movies) @app.route('/movie/edit/<int:movie_id>', methods=['GET', 'POST']) def edit(movie_id): movie = Movie.query.get_or_404(movie_id) if request.method == 'POST': # 处理编辑表单的提交请求 title = request.form['title'] year = request.form['year'] if not title or not year or len(year) != 4 or len(title) > 60: flash('Invalid input.') return redirect(url_for('edit', movie_id=movie_id)) # 重定向回对应的编辑页面 movie.title = title # 更新标题 movie.year = year # 更新年份 db.session.commit() # 提交数据库会话 flash('Item updated.') return redirect(url_for('index')) # 重定向回主页 return render_template('edit.html', movie=movie) # 传入被编辑的电影记录 @app.route('/movie/delete/<int:movie_id>', methods=['POST']) # 限定只接受 POST 请求 def delete(movie_id): movie = Movie.query.get_or_404(movie_id) # 获取电影记录 db.session.delete(movie) # 删除对应的记录 db.session.commit() # 提交数据库会话 flash('Item deleted.') return redirect(url_for('index')) # 重定向回主页
下面在基模板(base.html)里使用 get_flashed_messages() 函数获取提示消息并显示:
<!-- 插入到页面标题上方 -->
{% for message in get_flashed_messages() %}
<div class="alert">{{ message }}</div>
{% endfor %}
<h2>...</h2>
emplates/edit.html:编辑页面模板
{% extends 'base.html' %}
{% block content %}
<h3>Edit item</h3>
<form method="post">
Name <input type="text" name="title" autocomplete="off" required value="{{ movie.title }}">
Year <input type="text" name="year" autocomplete="off" required value="{{ movie.year }}">
<input class="btn" type="submit" name="submit" value="Update">
</form>
{% endblock %}
index.html
<span class="float-right">
<a class="btn" href="{{ url_for('edit', movie_id=movie.id) }}">Edit</a>
...
</span>
<span class="float-right">
...
<form class="inline-form" method="post" action="{{ url_for('delete', movie_id=movie.id) }}">
<input class="btn" type="submit" name="delete" value="Delete" onclick="return confirm('Are you sure?')">
</form>
...
</span>
样式如下:
.alert {
position: relative;
padding: 7px;
margin: 7px 0;
border: 1px solid transparent;
color: #004085;
background-color: #cce5ff;
border-color: #b8daff;
border-radius: 5px;
}
.inline-form {
display: inline;
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。