编写 TODO 应用【part001】
本书前面两个部分分别对 Flask 的基本知识、用法以及介绍了多种扩展以及扩展的通用使用方式,使用扩展过程中的一些细节进行了讲解。虽然过程中有一个 REST API 小例子描述,但是,毕竟是作为各个扩展使用讲解而编排在一块,所以缺乏系统性,全局性。
从本章开始,我将使用 Flask 围绕一个 TODO 应用提供 REST API 进行讲解,让大家有个对 Flask 应用有一个直观的认识。
TODO 应用讲解
我们需要编写的 TODO 应用主要功能有:
- 可以查询所有待办事项
- 可以查看指定待办事项的详情
- 可以增加一项待办事项
- 可以删除一项待办事项
- 可以修改一项待办事项,包括待办内容,添加标记
- 完成待办事项后可以标记为完成
这些就是我们应用的简略需求,然后再讲一下我们的项目结构,根据前面章节《更好得维护代码》中讲解的,我们将项目结构设计成如下:
.
├── README.md
├── application
│ ├── __init__.py
│ ├── controllers
│ │ ├── __init__.py
│ │ ├── auth.py
│ │ ├── todo.py
│ │ └── user.py
│ ├── extensions.py
│ └── models
│ ├── __init__.py
│ ├── todo.py
│ └── user.py
├── commands.py
├── config
│ ├── __init__.py
│ ├── default.py
│ ├── development.py
│ ├── development_sample.py
│ ├── production.py
│ ├── production_sample.py
│ └── testing.py
├── deploy
│ ├── flask_env.sh
│ ├── gunicorn.conf
│ ├── nginx.conf
│ └── supervisor.conf
├── manage.py
├── pylintrc
├── requirements.txt
├── tests
│ └── __init__.py
└── wsgi.py
设计 Models
Model 的话主要设计两个主要的模型,分别是 User 和 Item。User 表示用户的信息,除了表示TODO 所属人之外,还有登录的用处,而 Item 则是待办事项了,具体设计需要参考需求而定,关于 Model 的具体设计过程不是本章讨论的重点,所以直接给出 Models:
application/models/init.py
#!/usr/bin/env python
# encoding: utf-8
from user import *
from todo import *
def all():
result = []
models = [user, todo]
for m in models:
result += m.__all__
return result
__all__ = all()
application/models/user.py
#!/usr/bin/env python
# encoding: utf-8
from application.extensions import db
__all__ = ['Role', 'User']
class Permission:
READ = 0x01
CREATE = 0x02
UPDATE = 0x04
DELETE = 0x08
DEFAULT = READ
class Role(db.Document):
name = db.StringField()
permission = db.IntField()
class User(db.Document):
name = db.StringField()
password = db.StringField()
email = db.StringField()
role = db.ReferenceField('Role')
def to_json(self):
return {"name": self.name,
"email": self.email,
"role": self.role.name}
def is_authenticated(self):
return True
def is_active(self):
return True
def is_anonymous(self):
return False
def get_id(self):
return str(self.id)
application/models/todo.py
#!/usr/bin/env python
# encoding: utf-8
from application.extensions import db
__all__ = ['Item']
class Item(db.Document):
content = db.StringField(required=True)
created_date = db.DateTimeField()
completed = db.BooleanField(default=False)
completed_date = db.DateTimeField()
created_by = db.ReferenceField('User', required=True)
notes = db.ListField(db.StringField())
priority = db.IntField()
def __repr__(self):
return "<Item: {} Content: {}>".format(str(self.id),
self.content)
def to_json(self):
return {
'id': str(self.id),
'content': self.content,
'completed': self.completed,
'completed_at': self.completed_date.strftime("%Y-%m-%d %H:%M:%S") if self.completed else "",
'created_by': self.created_by.name,
'notes': self.notes,
'priority': self.priority
}
设计 views
根据我们在前面章节所学习的知识,我们这个应用的 views 就不是直接使用 app.route
来绑定 URL 了,而是使用 Blueprint 来设计,具体设计如下:
application/controller/init.py
#!/usr/bin/env python
# encoding: utf-8
import auth
import user
import todo
all_bp = [
auth.auth_bp,
user.user_bp,
todo.todo_bp
]
application/controller/auth.py
#!/usr/bin/env python
# encoding: utf-8
import json
from flask import Blueprint, request, jsonify
from flask.ext.login import login_user, logout_user
import application.models as Models
auth_bp = Blueprint('auth', __name__, url_prefix='/auth')
@auth_bp.route('/login', methods=['POST'])
def login():
info = json.loads(request.data)
username = info.get('username', 'guest')
password = info.get('password', '')
user = Models.User.objects(name=username,
password=password).first()
if user:
login_user(user)
return jsonify(user.to_json())
else:
return jsonify({"status": 401,
"reason": "Username or Password Error"})
@auth_bp.route('/logout', methods=['POST'])
def logout():
logout_user()
return jsonify(**{'result': 200,
'data': {'message': 'logout success'}})
application/controller/user.py
#!/usr/bin/env python
# encoding: utf-8
from flask.ext.login import current_user
from flask import Blueprint, jsonify
user_bp = Blueprint('users', __name__, url_prefix='')
@user_bp.route('/user_info', methods=['POST'])
def user_info():
if current_user.is_authenticated:
resp = {"result": 200,
"data": current_user.to_json()}
else:
resp = {"result": 401,
"data": {"message": "user no login"}}
return jsonify(**resp)
application/controller/todo.py
#!/usr/bin/env python
# encoding: utf-8
import json
from datetime import datetime
from flask import Blueprint, request, jsonify
from flask.ext.login import current_user, login_required
import application.models as Models
todo_bp = Blueprint('todos', __name__, url_prefix='/todo')
@todo_bp.route('/item', methods=['POST'])
@login_required
def create_todo_item():
data = json.loads(request.data)
content = data.get('content')
note = data.get('note', None)
priority = data.get('priority', 0)
if not content:
return jsonify({
'data': {},
'msg': 'no content',
'code': 1001,
'extra': {}})
item = Models.Item(content=content, created_date=datetime.now(),
completed=False, created_by=current_user.id,
notes=[note] if note else [],
priority=priority)
item.save()
return jsonify({
'data': item.to_json(),
'msg': 'create item success',
'code': 1000,
'extra': {}
})
@todo_bp.route('/item', methods=['DELETE'])
@login_required
def delete_todo_item():
data = json.loads(request.data)
id = data.get('id')
if not id:
return jsonify({
'data': {},
'msg': 'no id',
'code': 2001,
'extra': {}})
item = Models.Item.objects(id=id).first()
item.delete()
return jsonify({
'data': item.to_json(),
'msg': 'delete item success',
'code': 2000,
'extra': {}
})
@todo_bp.route('/item', methods=['PUT'])
@login_required
def update_todo_item():
data = json.loads(request.data)
id = data.get('id')
type = data.get('type')
if type == "update_content":
content = data.get('content')
Models.Item.objects(id=id).first().update(content=content)
elif type == "insert_notes":
note = data.get('note')
Models.Item.objects(id=id).first().update(push__notes=note)
elif type == "done":
Models.Item.objects(id=id).first().update(completed=True,
completed_date=datetime.now())
return jsonify({
'data': {'oper': type,
'id': id},
'msg': 'oper done',
'code': 3000,
'extra': {}
})
@todo_bp.route('/item', methods=['GET'])
@login_required
def get_todo_item():
query_string = request.args.get('q')
data = json.loads(query_string)
id = data.get('id')
item = Models.Item.objects(id=id).first()
return jsonify({
'data': item.to_json(),
'msg': 'query item success',
'code': 4000,
'extra': {}
})
@todo_bp.route('/items', methods=['GET'])
@login_required
def get_todo_items():
data = json.loads(request.args.get('q'))
page = data.get('page', 1)
page_size = data.get('page_size', 10)
begin = (page - 1) * page_size
end = begin + page_size
items = Models.Item.objects()[begin: end]
rsts = []
for item in items:
rsts.append(item.to_json())
return jsonify({
'data': rsts,
'msg': 'query items success',
'code': 5000,
'extra': {}
})
初始化扩展
扩展我们是统一放到 application/extensions.py 里面进行构建对象的,所以文件有:
application/extensions.py
#!/usr/bin/env python
# encoding: utf-8
from flask.ext.admin import Admin
from flask.ext.login import LoginManager
from flask.ext.mongoengine import MongoEngine
db = MongoEngine()
login_manager = LoginManager()
admin = Admin()
初始化应用
#!/usr/bin/env python
# encoding: utf-8
import sys
import logging
from flask import Flask
from flask_admin.contrib.mongoengine import ModelView
from config import load_config
from application.extensions import db, login_manager, admin
from application.models import User, Role
from application.controllers import all_bp
# convert python's encoding to utf8
try:
reload(sys)
sys.setdefaultencoding('utf8')
except (AttributeError, NameError):
pass
def create_app(mode):
"""Create Flask app."""
config = load_config(mode)
app = Flask(__name__)
app.config.from_object(config)
if not hasattr(app, 'production'):
app.production = not app.debug and not app.testing
if app.debug or app.testing:
# Log errors to stderr in production mode
app.logger.addHandler(logging.StreamHandler())
app.logger.setLevel(logging.ERROR)
# Register components
register_extensions(app)
register_blueprint(app)
return app
def register_extensions(app):
"""Register models."""
db.init_app(app)
login_manager.init_app(app)
# flask-admin configs
admin.init_app(app)
admin.add_view(ModelView(User))
admin.add_view(ModelView(Role))
login_manager.login_view = 'auth.login'
@login_manager.user_loader
def load_user(user_id):
return User.objects(id=user_id).first()
def register_blueprint(app):
for bp in all_bp:
app.register_blueprint(bp)