学习笔记
HelloFlask
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基本语法
- {{ … }} 用来标记变量。
- {% … %} 用来标记语句,比如 if 语句,for 语句等。
- {# … #} 用来写注释。
变量
Jinja2还支持列表、字典和对象
{{ mydict['key'] }}
{{ mylist[3] }}
{{ mylist[myintvar] }}
{{ myobj.somemethod() }}
获取变量的属性有以下两种方式
{{ foo.bar }}
{{ foo['bar'] }}
Filter过滤器()
使用格式变量名|函数
,将变量传给函数,再把函数值作为代码块的值,可以通过|
调用多个函数
例子:
<!-- 带参数的 -->
{{变量 | 函数名(*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
For(Else)循环
例子
<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>
循环中的索引:
- loop.index: 循环当前迭代(从1开始)。
- loop.index0: 循环当前迭代(从0开始)。
- loop.revindex: 循环迭代的数量(从1开始)。
- loop.revindex0: 循环迭代的数量(从0开始)。
- loop.first: 是否为迭代的第一步。
- loop.last: 是否为迭代的最后一步。
- loop.length: 序列中元素的数量。
if语句
{% 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把一些常用的函数和对象添加到了模板的上下文中
添加Favcion
<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') }}">
添加ccs
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.Model。
- 每一个类属性(字段)要实例化 db.Column,传入的参数为字段的类型,下面的表格列出了常用的字段类。
- 在 db.Column() 中添加额外的选项(参数)可以对字段进行设置。比如,primary_key 设置当前字段是否为主键。除此之外,常用的选项还有 nullable(布尔值,是否允许为空值)、index(布尔值,是否设置索引)、unique(布尔值,是否允许重复值)、default(设置默认值)等。
字段类 | 说明 |
---|---|
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>
- 在 标签里使用 method 属性将提交表单数据的 HTTP 请求方法指定为 POST。如果不指定,则会默认使用 GET 方法,这会将表单数据通过 URL 提交,容易导致数据泄露,而且不适用于包含大量数据的情况。
- 元素必须要指定 name 属性,否则无法提交数据,在服务器端,我们也需要通过这个 name 属性值来获取对应字段的数据。
创建新条目
<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;
}