Welcome to Todo for Flask’s documentation!¶
介绍 Todo¶
看着高手们用不同的Web框架实现了这个小应用,而我这样的新手正在学习Flask,就拿来练下手了,他们用不同的框架所实现的应用:
- web.py版 http://simple-is-better.com/news/309
- Uliweb版 http://limodou.github.com/uliweb-doc/basic.html
- Bottle版 https://bitbucket.org/ZoomQuiet/bottle-simple-tod
TODO具备的功能:
- 实现任务的追加,修改,删除功能。
- 完成或重置任务功能。
使用了 Flask-SQLAlchemy 扩展,数据库用的是 sqlite
,为因为他很小型,在做开发的时候用着不错。下面就开始一步步的构建这个程序。
初始准备:创建目录¶
web应用,多不是一个单一的程序就能解决问题的,创建一个目录,将所有内容放在这个目录中,方便管理。本应用目录为::
/todo
/static
/templates
static
目录用于存放如img、js、css等文件,这些文件能通过HTTP直接访问, templates
用于存放Flask应用所需要的 jinja2
模板文件。我们把所有的模板文件都放在此目录中。
构建应用程序代码¶
当我们准备好了应用的模式后,就可以创建应用的模块了。本例中,我把它叫做 todo.py
,并把它放在 todo 目录中。首先我们要导入一些Flash必用的模块和配置。如果是一个小应用的话,配置可直接放在模块中,如果应用比较大,可使用单独的文件进行配置,然后再导入主模块中即可。
导入模块¶
from flask import Flask
这里只导入了一个 Flask
模块,只需要这个一个模块,程序就能运行起来了,当然对于更复杂的应用,光这一个模块是不够的,待会遇到之时,我们再进行导入。
配置¶
DEBUG = True
SECRET_KEY = 'secret_key'
配置选项一,开启服务器的debug支持,每次修改服务器都会自动重启,如果出现问题的话,还会提供一个有用的调试器。Debug标志用来指示是否开启交互debugger。永远不要在生产环境下开始 debug标志,因为这样会允许用户在服务器上执行代码!
配置选项二,我们需要设置 secret_key 来确保客户端Session的安全。合理的设置这个值,而且越复杂越好。Python中的 os.urandom
可方便获得:
In [1]: import os
In [2]: os.urandom(24)
Out[2]: '7\xe9\xcf\x17\x11\x92I^"|\xbc\x85\xc8\xc1u\x18\xbb\xec\xc9\xe2\xbb,\x9fX'
将产生的字符串复制替换掉 secret_key
即可
配置初始化¶
由于只是一个小应用,初始化我们放在同一文件中完成
app = Flask(__name__)
app.config.from_object(__name__)
from_object
会识别给出的对象(如果是一个字符串,它会自动导入这个模块),然后查找所有已定义的大写变量。在我们这个例子里,配置在几行代码前。你也可以把它移动到一个单独的文件中。
开启服务器¶
要启动服务器,在最后加入这样的代码::
if __name__ == '__main__':
app.run()
现在就能启动服务器了::
python todo.py
启动后的提示是这样的::
* Running on http://127.0.0.1:5000/
* Restarting with reloader...
这时就能在浏览器中打开这个网址了,不过此时访问服务器,你会得到一个404页面没找到的错误。因为我们还没有创建视图。
第一步代码¶
第一步后,代码像这样:
1 2 3 4 5 6 7 8 9 10 11 12 | #!/usr/bin/env python
#coding:utf-8
from flask import Flask
DEBUG = True
SECRET_KEY = '7\xe9\xcf\x17\x11\x92I^"|\xbc\x85\xc8\xc1u\x18\xbb\xec\xc9\xe2\xbb,\x9fX'
app = Flask(__name__)
app.config.from_object(__name__)
if __name__ == '__main__':
app.run()
|
创建数据库¶
创建数据库,分为三步: 配置数据库,初始化数据库和数据库模型,这里以 Flask-SQLAlchemy 为例。
配置¶
配置数据库,只需要模块的配置部分加入下面这行代码::
SQLALCHEMY_DATABASE_URI = 'sqlite:///todo.sqlite'
这是一个关于 sqlite 的配置,其它配置,可参看 这里 。
创建数据库实例¶
SQLAlchemy
模块导入::
from flaskext.sqlalchemy import SQLAlchemy
在前面的 app
配置后面加入下面两行代码::
db = SQLAlchemy(app)
db.init_app(app)
创建数据模型¶
数据模型::
class Todo(db.Model):
'''数据模型'''
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(255), nullable=False)
posted_on = db.Column(db.Date, default=datetime.utcnow)
status = db.Column(db.Boolean(), default=False)
模型型初始化::
def __init__(self, *args, **kwargs):
super(Todo, self).__init__(*args, **kwargs)
def __repr__(self):
return "<Todo '%s'>" % self.title
模型增补数据操作功能::
def store_to_db(self):
'''保存数据到数据库'''
db.session.add(self)
db.session.commit()
def delete_todo(self):
'''删除数据'''
db.session.delete(self)
db.session.commit()
初始化数据库¶
进入python命令行,然后::
from todo import db
db.create_all()
如果没问题的话,你可以在当前目录中看到数据库文件了。如果需要重建数据表,可使用 db.drop_all()
清除数据。
第二步代码¶
第二步后,代码像这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | #!/usr/bin/env python
#coding:utf-8
from flask import Flask
from flaskext.sqlalchemy import SQLAlchemy
DEBUG = True
SECRET_KEY = '7\xe9\xcf\x17\x11\x92I^"|\xbc\x85\xc8\xc1u\x18\xbb\xec\xc9\xe2\xbb,\x9fX'
SQLALCHEMY_DATABASE_URI = 'sqlite:///todo.sqlite'
app = Flask(__name__)
app.config.from_object(__name__)
db = SQLAlchemy(app)
db.init_app(app)
class Todo(db.Model):
'''数据模型'''
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(255), nullable=False)
posted_on = db.Column(db.Date, default=datetime.utcnow)
status = db.Column(db.Boolean(), default=False)
def __init__(self, *args, **kwargs):
super(Todo, self).__init__(*args, **kwargs)
def __repr__(self):
return "<Todo '%s'>" % self.title
def store_to_db(self):
'''保存数据到数据库'''
db.session.add(self)
db.session.commit()
def delete_todo(self):
'''删除数据'''
db.session.delete(self)
db.session.commit()
if __name__ == '__main__':
app.run()
|
表单¶
表单引入模块::
from flaskext.wtf import Form, TextField, SubmitField, required, ValidationError
表单代码::
class TodoForm(Form):
'''表单'''
title = TextField(u"内容", validators=[required(message=u"任务内容")])
只是一个表单,当然也可以直接放到html中.定义好的表单,可用如下方式进行调用::
{{ form.title.label }} {{ form.title }}
将其写入HTML的 <form></form
即可。
视图¶
现在数据库连接已能正常工作了,这步就开始写视图函数。五个功能视图及一个404视图。
route¶
装饰器 flask.Flask.route
用于绑定一个函数到一个网址。但是它不仅仅只有这些!你可以构造动态的网址并给函数附加多个规则.
这里是一个例子::
@app.route('/')
def index():
return 'Index Page'
@app.route('/hello')
def hello():
return 'Hello World'
一个函数可对应我个视图。如 index
视图,也可以再加个 @app.route('/index')
,这样 /
和 /index
都会调用 index
函数。每个视力都必须 return
语句返回调用。
主视图¶
主视图代码如下:
@app.route('/', methods=['GET', 'POST'])
def index():
todo = Todo.query.order_by('-id')
form = TodoForm(request.form)
if request.method == 'POST' and form.validate_on_submit():
t = Todo(title=form.title.data)
try:
t.store_to_db()
flash(u"添加成功")
return redirect(request.args.get('next') or url_for('index'))
except:
flash(u'存储失败!')
return render_template('index.html', todo=todo, form=form)
编辑视图¶
@app.route('/<int:id>/edit', methods=['GET', 'POST'])
def edit(id):
todo = Todo.query.filter_by(id=id).first()
form = TodoForm(title=todo.title)
if request.method == 'POST' and form.validate_on_submit():
Todo.query.filter_by(id=id).update({Todo.title:request.form['title']})
db.session.commit()
flash(u"记录编辑成功")
return redirect(url_for('index'))
return render_template('edit.html', todo=todo, form=form)
删除视图¶
def tdel(id):
todo = Todo.query.filter_by(id=id).first()
if todo:
todo.delete_todo()
flash(u"记录删除成功")
return redirect(url_for('index'))
完成视图¶
@app.route('/<int:id>/done')
def done(id):
todo = Todo.query.filter_by(id=id).first()
if todo:
Todo.query.filter_by(id=id).update({Todo.status:True})
db.session.commit()
flash(u"任务完成")
return redirect(url_for('index'))
重置视图¶
@app.route('/<int:id>/redo')
def redo(id):
todo = Todo.query.filter_by(id=id).first()
if todo:
Todo.query.filter_by(id=id).update({Todo.status:False})
flash(u"记录重置成功")
db.session.commit()
return redirect(url_for('index'))
404视图¶
@app.errorhandler(404)
def page_not_found(error):
return render_template('page_404.html'), 404
整个代码¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 | #!/usr/bin/env python
#coding:utf-8
import os
from datetime import datetime
from flask import Flask, render_template, url_for, redirect, flash, request
from flaskext.sqlalchemy import SQLAlchemy
from flaskext.wtf import Form, TextField, SubmitField, required, ValidationError
DEBUG = True
SECRET_KEY = '7\xe9\xcf\x17\x11\x92I^"|\xbc\x85\xc8\xc1u\x18\xbb\xec\xc9\xe2\xbb,\x9fX'
SQLALCHEMY_DATABASE_URI = 'sqlite:///todo.sqlite'
app = Flask(__name__)
app.config.from_object(__name__)
db = SQLAlchemy(app)
db.init_app(app)
class Todo(db.Model):
'''数据模型'''
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(255), nullable=False)
posted_on = db.Column(db.Date, default=datetime.utcnow)
status = db.Column(db.Boolean(), default=False)
def __init__(self, *args, **kwargs):
super(Todo, self).__init__(*args, **kwargs)
def __repr__(self):
return "<Todo '%s'>" % self.title
def store_to_db(self):
'''保存数据到数据库'''
db.session.add(self)
db.session.commit()
def delete_todo(self):
'''删除数据'''
db.session.delete(self)
db.session.commit()
class TodoForm(Form):
'''表单'''
title = TextField(u"内容", validators=[required(message=u"任务内容")])
#submit = SubmitField(u"Add")
@app.route('/', methods=['GET', 'POST'])
def index():
todo = Todo.query.order_by('-id')
form = TodoForm(request.form)
if request.method == 'POST' and form.validate_on_submit():
t = Todo(title=form.title.data)
try:
t.store_to_db()
flash(u"添加成功")
return redirect(request.args.get('next') or url_for('index'))
except:
flash(u'存储失败!')
return render_template('index.html', todo=todo, form=form)
@app.route('/<int:id>/del')
def tdel(id):
todo = Todo.query.filter_by(id=id).first()
if todo:
todo.delete_todo()
flash(u"记录删除成功")
return redirect(url_for('index'))
@app.route('/<int:id>/edit', methods=['GET', 'POST'])
def edit(id):
todo = Todo.query.filter_by(id=id).first()
form = TodoForm(title=todo.title)
if request.method == 'POST' and form.validate_on_submit():
Todo.query.filter_by(id=id).update({Todo.title:request.form['title']})
db.session.commit()
flash(u"记录编辑成功")
return redirect(url_for('index'))
return render_template('edit.html', todo=todo, form=form)
@app.route('/<int:id>/done')
def done(id):
todo = Todo.query.filter_by(id=id).first()
if todo:
Todo.query.filter_by(id=id).update({Todo.status:True})
db.session.commit()
flash(u"任务完成")
return redirect(url_for('index'))
@app.route('/<int:id>/redo')
def redo(id):
todo = Todo.query.filter_by(id=id).first()
if todo:
Todo.query.filter_by(id=id).update({Todo.status:False})
flash(u"记录重置成功")
db.session.commit()
return redirect(url_for('index'))
@app.errorhandler(404)
def page_not_found(error):
return render_template('page_404.html'), 404
if __name__ == '__main__':
app.run()
|
模版¶
base.html¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>任务追踪 (Flask版) 简明TODO</title></title>
<script type="text/javascript" src="http://img3.douban.com/js/packed_jquery.min9556435062.js"></script>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}" type="text/css" />
<script type="text/javascript">
$(document).ready(function()
{
$("#divPop")
.animate(
{
"opacity": "hide",
"width": 800,
"height":30
},
1500
);
});
</script>
</head>
<body class="page">
<h1>= 任务追踪 (Flask版) = </h1>
<hr>
{% block content %} {% endblock %}
<br />
{% for message in get_flashed_messages() %}
<div id="divPop" style="background-color: #f0f0f0; border: solid 1px #000000;width: 800px; height: 30px;"><div style="text-align:center;">{{ message }}</div></div>
{% endfor %}
<!-- footer -->
<hr>
<div id="poweredby">
Copyright (c) 2011 wwq0327. powered by: <a href="http://python.org">python</a>, <a href="http://flask.pocoo.org/">flask</a> . <a href="https://bitbucket.org/wwq0327/flask-simple-todo">工程地址</a>
</div>
<br />
</body>
</html>
|
index.html¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | {% extends "base.html" %}
{% block content %}
<h2>== 待办 ==</h2>
{% if todo %}
<ol>
{% for t in todo %}
{% if t.status==False %}
<li>{{ t.title|safe }} <sup>:: <a href="/{{ t.id }}/del" >删除</a> | <a href="/{{ t.id }}/edit"> 修改</a> |
<a href="/{{ t.id }}/done" >完成</a></sup> </li>
{% endif %}
{% endfor %}
</ol>
{% else %}
<b>暂时无记录</b>
{% endif %}
<h2>== 完成 ==</h2>
{% if todo %}
<ol>
{% for t in todo %}
{% if t.status==True %}
<li><del>{{ t.title|safe }}</del> <sup>:: <a href="/{{ t.id }}/del" >删除</a> | <a href="/{{ t.id }}/redo" >重置</a></sup> </li>
{% endif %}
{% endfor %}
</ol>
{% else %}
<b>暂时无记录</b>
{% endif %}
<h2> == 追加 ==</h2>
<form action="." method="post">
{{ form.csrf }}
{{ form.title(size=100) }}
<button type="submit">更新</button>
</form>
{% endblock %}
|
edit.html¶
{% extends "base.html" %}
{% block content %}
<h2> == 修改 ==</h2>
<form action="/{{ todo.id }}/edit" method="post">
{{ form.csrf }}
{{ form.title(size=100) }}
<button type="submit">更新</button>
</form>
{% endblock %}
page_404.html¶
{% extends "base.html" %}
{% block content %} <p align="center"><b>页面未找到!</b>, 请<a href="{{url_for('index')}}" >返回</a></p> {% endblock %}
增加页面样式¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | body { background: #fefefe; color: #343434; margin: 1em; padding: 0; font-size:105%;}
img { border: none }
a {
color: #185DA0;
background: inherit;
}
a:hover {
color: #9EC068;
background: inherit;
}
h2 a {
text-decoration: none;
}
.page {
margin: 2em auto; width: 70em; border: 3px dashed #ccc;
padding: 1em; background: white;
}
hr {
width : 100%;
height : 2px;
background : #efefef;
border : 1px solid #ddd;
clear : both;
}
div#poweredby{
float : right;
font-size: 0.8em;
}
li {font-size:120%;
margin-left: 2em;
border-bottom:1px dotted #aaa;
width:90%;
padding-top: 10px;
}
del {font-size:95%; color:#555;}
sub,sup {font-size:10px;}
/*from 42qu.com*/
input{ font-size:18px;line-height:22px;height:26px;
padding:1px 3px;
border:1px solid #ddd;
width:37em;
}
button {
font-weight:normal;color:#444;
font-size:16px;line-height:22px;height:28px;
text-decoration:center;
width:50px;overflow:visible;
margin:0;
padding:1px 3px;
background-color:#feeefe;
border:1px solid #ddd;
cursor:pointer;
}
button:hover{
background-color:#fff;border:1px solid #aaa;color:#000
-moz-box-shadow:-2px 2px 2px #ccc;
box-shadow:-2px 2px 2px #ccc;
-webkit-box-shadow:-2px 2px 2px #ccc;
}
.flash
{ background: #fff; padding: 0.5em;
border: 1px solid #ccc;
}
|