flask怎么实现python flask 教程功能函数的api

python相关(3)
找了一篇教程学习了一下,为了加深印象照着写了一遍存下来,原文链接如下:
REST的六个特性
Client-Server:服务器端与客户端分离。
Stateless(无状态):每次客户端请求必需包含完整的信息,换句话说,每一次请求都是独立的。
Cacheable(可缓存):服务器端必需指定哪些请求是可以缓存的。
Layered System(分层结构):服务器端与客户端通讯必需标准化,服务器的变更并不会影响客户端。
Uniform Interface(统一接口):客户端与服务器端的通讯方法必需是统一的。
Code on demand(按需执行代码?):服务器端可以在上下文中执行代码或者脚本?
HTTP请求方法
请求指定的页面信息,并返回实体主体。
类似于get请求,只不过返回的响应中没有具体的内容,用于获取报头
向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。
从客户端向服务器传送的数据取代指定的文档的内容。
请求服务器删除指定的页面。
HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。
允许客户端查看服务器的性能。
回显服务器收到的请求,主要用于测试或诊断。
规划资源的URL
检索任务清单
检索一个任务
创建一个新任务
更新一个已存在的任务
删除一个任务
我们定义任务清单有以下字段:
id:唯一标识。整型。
title:简短的任务描述。字符串型。
description:完整的任务描述。文本型。
done:任务完成状态。布尔值型。
代码实现以及注释
from flask import Flask, jsonify
from flask import make_response
from flask import request
from flask import abort
app = Flask(__name__)
'title': u'Buy groceries',
'description': u'Milk, Cheese, Pizza, Fruit, Tylenol',
'done': False
'title': u'Learn Python',
'description': u'Need to find a good Python tutorial on the web',
'done': False
@app.route('/todo/api/v1.0/tasks', methods=['GET'])
def get_tasks():
return jsonify({'tasks': tasks})
@app.route('/todo/api/v1.0/tasks/&int:task_id&', methods=['GET'])
def get_task(task_id):
task = filter(lambda t: t['id'] == task_id, tasks)
if len(task) == 0:
abort(404)
return jsonify({'task':task[0]})
@app.route('/todo/api/v1.0/tasks', methods=['POST'])
def create_task():
if not request.json or not 'title' in request.json:
abort(400)
'id': tasks[-1]['id'] + 1,
'title': request.json['title'],
'description': request.json.get('description', ""),
'done': False
tasks.append(task)
return jsonify({'task': task}), 201
@app.errorhandler(404)
def not_found(error):
return make_response(jsonify({'error': 'Not found'}), 404)
@app.route('/todo/api/v1.0/tasks/&int:task_id&', methods=['PUT'])
def update_task(task_id):
task = filter(lambda t: t['id'] == task_id, tasks)
if len(task) == 0:
abort(404)
if not request.json:
abort(400)
if 'title' in request.json and type(request.json['title']) != unicode:
abort(400)
if 'description' in request.json and type(request.json['description']) is not unicode:
abort(400)
if 'done' in request.json and type(request.json['done']) is not bool:
abort(400)
task[0]['title'] = request.json.get('title', task[0]['title'])
task[0]['description'] = request.json.get('description', task[0]['description'])
task[0]['done'] = request.json.get('done', task[0]['done'])
return jsonify({'task': task[0]})
@app.route('/todo/api/v1.0/tasks/&int:task_id&', methods=['DELETE'])
def delete_task(task_id):
task = filter(lambda t: t['id'] == task_id, tasks)
if len(task) == 0:
abort(404)
tasks.remove(task[0])
return jsonify({'result': True})
if __name__ == '__main__':
app.run(debug=True)
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:40669次
积分:2449
积分:2449
排名:第15011名
原创:214篇
(3)(35)(5)(20)(21)(7)(1)(9)(5)(6)(5)(42)(44)(15)(1)使用 Flask 实现 RESTful API - Python - 伯乐在线
& 使用 Flask 实现 RESTful API
首先,安装Flask
pip install flask
pip install flask
假设那你已经了解RESTful API的相关概念,如果不清楚,可以阅读我之前写的这篇博客.
Flask是一个使用Python开发的基于Werkzeug的Web框架。
Flask非常适合于开发RESTful API,因为它具有以下特点:
使用Python进行开发,Python简洁易懂
可以部署到不同的环境
支持RESTful请求分发
我一般是用curl命令进行测试,除此之外,还可以使用Chrome浏览器的postman扩展。
首先,我创建一个完整的应用,支持响应/, /articles以及/article/:id。
from flask import Flask, url_for
app = Flask(__name__)
@app.route('/')
def api_root():
return 'Welcome'
@app.route('/articles')
def api_articles():
return 'List of ' + url_for('api_articles')
@app.route('/articles/&articleid&')
def api_article(articleid):
return 'You are reading ' + articleid
if __name__ == '__main__':
1234567891011121314151617
from flask import Flask, url_forapp = Flask(__name__)&@app.route('/')def api_root():&&&&return 'Welcome'&@app.route('/articles')def api_articles():&&&&return 'List of ' + url_for('api_articles')&@app.route('/articles/&articleid&')def api_article(articleid):&&&&return 'You are reading ' + articleid&if __name__ == '__main__':&&&&app.run()
可以使用curl命令发送请求:
curl http://127.0.0.1:5000/
curl http://127.0.0.1:5000/
响应结果分别如下所示:
GET /articles
List of /articles
GET /articles/123
You are reading 123
GET /Welcome&GET /articlesList of /articles&GET /articles/123You are reading 123
路由中还可以使用类型定义:
@app.route('/articles/&articleid&')
@app.route('/articles/&articleid&')
上面的路由可以替换成下面的例子:
@app.route('/articles/&int:articleid&')
@app.route('/articles/&float:articleid&')
@app.route('/articles/&path:articleid&')
@app.route('/articles/&int:articleid&')@app.route('/articles/&float:articleid&')@app.route('/articles/&path:articleid&')
默认的类型为字符串。
请求REQUESTS
假设需要响应一个/hello请求,使用get方法,并传递参数name
from flask import request
@app.route('/hello')
def api_hello():
if 'name' in request.args:
return 'Hello ' + request.args['name']
return 'Hello John Doe'
from flask import request&@app.route('/hello')def api_hello():&&&&if 'name' in request.args:&&&&&&&&return 'Hello ' + request.args['name']&&&&else:&&&&&&&&return 'Hello John Doe'
服务器会返回如下响应信息:
GET /hello
Hello John Doe
GET /hello?name=Luis
Hello Luis
GET /helloHello John Doe&GET /hello?name=LuisHello Luis
Flask支持不同的请求方法:
@app.route('/echo', methods = ['GET', 'POST', 'PATCH', 'PUT', 'DELETE'])
def api_echo():
if request.method == 'GET':
return "ECHO: GET\n"
elif request.method == 'POST':
return "ECHO: POST\n"
elif request.method == 'PATCH':
return "ECHO: PACTH\n"
elif request.method == 'PUT':
return "ECHO: PUT\n"
elif request.method == 'DELETE':
return "ECHO: DELETE"
12345678910111213141516
@app.route('/echo', methods = ['GET', 'POST', 'PATCH', 'PUT', 'DELETE'])def api_echo():&&&&if request.method == 'GET':&&&&&&&&return "ECHO: GET\n"&&&&&elif request.method == 'POST':&&&&&&&&return "ECHO: POST\n"&&&&&elif request.method == 'PATCH':&&&&&&&&return "ECHO: PACTH\n"&&&&&elif request.method == 'PUT':&&&&&&&&return "ECHO: PUT\n"&&&&&elif request.method == 'DELETE':&&&&&&&&return "ECHO: DELETE"
可以使用如下命令进行测试:
curl -X PATCH http://127.0.0.1:5000/echo
curl -X PATCH http://127.0.0.1:5000/echo
不同请求方法的响应如下:
POST /ECHO
ECHO: POST
GET /echoECHO: GET&POST /ECHOECHO: POST...
请求数据和请求头
通常使用POST方法和PATCH方法的时候,都会发送附加的数据,这些数据的格式可能如下:普通文本(plain text), JSON,XML,二进制文件或者用户自定义格式。
Flask中使用request.headers类字典对象来获取请求头信息,使用request.data获取请求数据,如果发送类型是application/json,则可以使用request.get_json()来获取JSON数据。
from flask import json
@app.route('/messages', methods = ['POST'])
def api_message():
if request.headers['Content-Type'] == 'text/plain':
return "Text Message: " + request.data
elif request.headers['Content-Type'] == 'application/json':
return "JSON Message: " + json.dumps(request.json)
elif request.headers['Content-Type'] == 'application/octet-stream':
f = open('./binary', 'wb')
f.write(request.data)
return "Binary message written!"
return "415 Unsupported Media T)"
12345678910111213141516171819
from flask import json&@app.route('/messages', methods = ['POST'])def api_message():&&&&&if request.headers['Content-Type'] == 'text/plain':&&&&&&&&return "Text Message: " + request.data&&&&&elif request.headers['Content-Type'] == 'application/json':&&&&&&&&return "JSON Message: " + json.dumps(request.json)&&&&&elif request.headers['Content-Type'] == 'application/octet-stream':&&&&&&&&f = open('./binary', 'wb')&&&&&&&&f.write(request.data)&&&&&&&&&&&&&&&&f.close()&&&&&&&&return "Binary message written!"&&&&&else:&&&&&&&&return "415 Unsupported Media T)"
使用如下命令指定请求数据类型进行测试:
curl -H "Content-type: application/json" \
-X POST http://127.0.0.1:5000/messages -d '{"message":"Hello Data"}'
curl -H "Content-type: application/json" \-X POST http://127.0.0.1:5000/messages -d '{"message":"Hello Data"}'
使用下面的curl命令来发送一个文件:
curl -H "Content-type: application/octet-stream" \
-X POST http://127.0.0.1:5000/messages --data-binary @message.bin
curl -H "Content-type: application/octet-stream" \-X POST http://127.0.0.1:5000/messages --data-binary @message.bin
不同数据类型的响应结果如下所示:
POST /messages {"message": "Hello Data"}
Content-type: application/json
JSON Message: {"message": "Hello Data"}
POST /message &message.bin&
Content-type: application/octet-stream
Binary message written!
POST /messages {"message": "Hello Data"}Content-type: application/jsonJSON Message: {"message": "Hello Data"}&POST /message &message.bin&Content-type: application/octet-streamBinary message written!
注意Flask可以通过request.files获取上传的文件,curl可以使用-F选项模拟上传文件的过程。
响应RESPONSES
Flask使用Response类处理响应。
from flask import Response
@app.route('/hello', methods = ['GET'])
def api_hello():
: 'world',
'number' : 3
js = json.dumps(data)
resp = Response(js, status=200, mimetype='application/json')
resp.headers['Link'] = ''
return resp
1234567891011121314
from flask import Response&@app.route('/hello', methods = ['GET'])def api_hello():&&&&data = {&&&&&&&&'hello'&&: 'world',&&&&&&&&'number' : 3&&&&}&&&&js = json.dumps(data)&&&&&resp = Response(js, status=200, mimetype='application/json')&&&&resp.headers['Link'] = ''&&&&&return resp
使用-i选项可以获取响应信息:
curl -i http://127.0.0.1:5000/hello
curl -i http://127.0.0.1:5000/hello
返回的响应信息如下所示:
GET /hello
HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 31
Server: Werkzeug/0.8.2 Python/2.7.1
Date: Wed, 25 Apr :27 GMT
{"hello": "world", "number": 3}
GET /helloHTTP/1.0 200 OKContent-Type: application/jsonContent-Length: 31Link: http://Server: Werkzeug/0.8.2 Python/2.7.1Date: Wed, 25 Apr 2012 16:40:27 GMT{"hello": "world", "number": 3}
mimetype指定了响应数据的类型。
上面的过程可以使用Flask提供的一个简便方法实现:
from flask import jsonify
# 将下面的代码替换成
resp = Response(js, status=200, mimetype='application/json')
# 这里的代码
resp = jsonify(data)
resp.status_code = 200
from flask import jsonify...# 将下面的代码替换成resp = Response(js, status=200, mimetype='application/json')# 这里的代码resp = jsonify(data)resp.status_code = 200
状态码和错误处理
如果成功响应的话,状态码为200。对于404错误我们可以这样处理:
@app.errorhandler(404)
def not_found(error=None):
message = {
'status': 404,
'message': 'Not Found: ' + request.url,
resp = jsonify(message)
resp.status_code = 404
return resp
@app.route('/users/&userid&', methods = ['GET'])
def api_users(userid):
users = {'1':'john', '2':'steve', '3':'bill'}
if userid in users:
return jsonify({userid:users[userid]})
return not_found()
12345678910111213141516171819
@app.errorhandler(404)def not_found(error=None):&&&&message = {&&&&&&&&&&&&'status': 404,&&&&&&&&&&&&'message': 'Not Found: ' + request.url,&&&&}&&&&resp = jsonify(message)&&&&resp.status_code = 404&&&&&return resp&@app.route('/users/&userid&', methods = ['GET'])def api_users(userid):&&&&users = {'1':'john', '2':'steve', '3':'bill'}&&&&&if userid in users:&&&&&&&&return jsonify({userid:users[userid]})&&&&else:&&&&&&&&return not_found()
测试上面的两个URL,结果如下:
GET /users/2
HTTP/1.0 200 OK
"2": "steve"
GET /users/4
HTTP/1.0 404 NOT FOUND
"status": 404,
"message": "Not Found: http://127.0.0.1:5000/users/4"
123456789101112
GET /users/2HTTP/1.0 200 OK{&&&&"2": "steve"}&GET /users/4HTTP/1.0 404 NOT FOUND{"status": 404, "message": "Not Found: http://127.0.0.1:5000/users/4"}
默认的Flask错误处理可以使用@error_handler修饰器进行覆盖或者使用下面的方法:
app.error_handler_spec[None][404] = not_found
app.error_handler_spec[None][404] = not_found
即使API不需要自定义错误信息,最好还是像上面这样做,因为Flask默认返回的错误信息是HTML格式的。
使用下面的代码可以处理 HTTP Basic Authentication。
from functools import wraps
def check_auth(username, password):
return username == 'admin' and password == 'secret'
def authenticate():
message = {'message': "Authenticate."}
resp = jsonify(message)
resp.status_code = 401
resp.headers['WWW-Authenticate'] = 'Basic realm="Example"'
return resp
def requires_auth(f):
def decorated(*args, **kwargs):
auth = request.authorization
if not auth:
return authenticate()
elif not check_auth(auth.username, auth.password):
return authenticate()
return f(*args, **kwargs)
return decorated
1234567891011121314151617181920212223242526
from functools import wraps&def check_auth(username, password):&&&&return username == 'admin' and password == 'secret'&def authenticate():&&&&message = {'message': "Authenticate."}&&&&resp = jsonify(message)&&&&&resp.status_code = 401&&&&resp.headers['WWW-Authenticate'] = 'Basic realm="Example"'&&&&&return resp&def requires_auth(f):&&&&@wraps(f)&&&&def decorated(*args, **kwargs):&&&&&&&&auth = request.authorization&&&&&&&&if not auth: &&&&&&&&&&&&return authenticate()&&&&&&&&&elif not check_auth(auth.username, auth.password):&&&&&&&&&&&&return authenticate()&&&&&&&&return f(*args, **kwargs)&&&&&return decorated
接下来只需要给路由增加@require_auth修饰器就可以在请求之前进行认证了:
@app.route('/secrets')
@requires_auth
def api_hello():
return "Shhh this is top secret spy stuff!"
@app.route('/secrets')@requires_authdef api_hello():&&&&return "Shhh this is top secret spy stuff!"
现在,如果没有通过认证的话,响应如下所示:
GET /secrets
HTTP/1.0 401 UNAUTHORIZED
WWW-Authenticate: Basic realm="Example"
"message": "Authenticate."
GET /secretsHTTP/1.0 401 UNAUTHORIZEDWWW-Authenticate: Basic realm="Example"{&&"message": "Authenticate."}
curl通过-u选项来指定HTTP basic authentication,使用-v选项打印请求头:
curl -v -u "admin:secret" http://127.0.0.1:5000/secrets
curl -v -u "admin:secret" http://127.0.0.1:5000/secrets
响应结果如下:
GET /secrets Authorization: Basic YWRtaW46c2VjcmV0
Shhh this is top secret spy stuff!
GET /secrets Authorization: Basic YWRtaW46c2VjcmV0Shhh this is top secret spy stuff!
Flask使用MultiDict来存储头部信息,为了给客户端展示不同的认证机制,可以给header添加更多的WWW-Autheticate。
resp.headers['WWW-Authenticate'] = 'Basic realm="Example"'resp.headers.add('WWW-Authenticate', 'Bearer realm="Example"')
resp.headers['WWW-Authenticate'] = 'Basic realm="Example"'resp.headers.add('WWW-Authenticate', 'Bearer realm="Example"')
调试与日志
通过设置debug=True来开启调试信息:
app.run(debug=True)
app.run(debug=True)
使用Python的logging模块可以设置日志信息:
import logging
file_handler = logging.FileHandler('app.log')
app.logger.addHandler(file_handler)
app.logger.)
@app.route('/hello', methods = ['GET'])
def api_hello():
('informing')
app.logger.warning('warning')
app.logger.error('screaming bloody murder!')
return "check your logs\n"
123456789101112
import loggingfile_handler = logging.FileHandler('app.log')app.logger.addHandler(file_handler)app.logger.setLevel(logging.INFO)&@app.route('/hello', methods = ['GET'])def api_hello():&&&&app.logger.info('informing')&&&&app.logger.warning('warning')&&&&app.logger.error('screaming bloody murder!')&&&&&return "check your logs\n"
CURL 命令参考
指定HTTP请求方法,如POST,GET
指定请求头,例如Content-type:application/json
指定请求数据
–data-binary
指定发送的文件
显示响应头部信息
指定认证用户名与密码
输出请求头部信息
可能感兴趣的话题
o 240 回复
关于 Python 频道
Python频道分享 Python 开发技术、相关的行业动态。
新浪微博:
推荐微信号
(加好友请注明来意)
– 好的话题、有启发的回复、值得信赖的圈子
– 分享和发现有价值的内容与观点
– 为IT单身男女服务的征婚传播平台
– 优秀的工具资源导航
– 翻译传播优秀的外文文章
– 国内外的精选文章
– UI,网页,交互和用户体验
– 专注iOS技术分享
– 专注Android技术分享
– JavaScript, HTML5, CSS
– 专注Java技术分享
– 专注Python技术分享
& 2017 伯乐在线chapter 14 - 应用编程接口(API) - 知乎专栏
{"debug":false,"apiRoot":"","paySDK":"/api/js","wechatConfigAPI":"/api/wechat/jssdkconfig","name":"production","instance":"column","tokens":{"X-XSRF-TOKEN":null,"X-UDID":null,"Authorization":"oauth c3cef7c66aa9e6a1e3160e20"}}
{"database":{"Post":{"":{"title":"chapter 14 - 应用编程接口(API)","author":"xujingwuwei","content":"其实网站建设在功能上,截至前面一章就已经都实现了。这章及后面的几章,都是高级内容,对于像我这样的初学者来说,个人觉得其实并没那么重要了。所以本章我在这里的笔记也会比较简略。本章讲的是Web API,具体说就是为我们写好的网站内容开发出可供第三方使用的API接口。书中提到了一个概念:REST(Representational State Transfer)——表现层状态转移。这是一种Web服务架构。它具有6个特征:客户端-服务器无状态缓存接口统一系统分层按需代码这个概念其实个人觉得不好理解。目前我的理解是,因为目前的网站架构都提倡MVC结构,把前端、后台给明确区分开来,做前端的可以完全不管后台是如\n \n何实现的,它只需要后台所提供的数据,然后用于展示给网民。而这个前端,估计就与这里说到的“表现层”是差不多同个东西,于是这个表现层状态转移,也就是\n 把后台的东西“转移”给前端。可能我的理解会有错,各位看到的同行尽可以指出。Whatever,知道它是个什么东西有什么作用就好。想了解更多信息,可以到网上找找。本书中从后台“转移”到前端的数据是JSON数据14.2 使用Flask提供REST Web服务14.2.1 创建API蓝本为了更好地组织代码,API文件最好放到独立的蓝本中。目录结构如下:|-flasky\n
|-api_1_0\n
|-__init__.py\n
|-users.py\n
|-posts.py\n
|-comments.py\n
|-authentication.py\n
|-errors.py\n
|-decorators.pyapp/api_1_0/___init___.py API蓝本的构造文件from flask import Blueprint\n\napi = Blueprint('api', __name__)\n\nfrom .import authentication, posts, users, comments, errors\napp/___init___.py 注册API蓝本def create_app(config_name):\n
# 原有代码\n
from .api_1_0 import api as api_1_0_blueprint\n
app.register_blueprint(api_1_0_blueprint, url_prefix='/api/v1.0')\n
# 原有代码\n14.2.2 错误处理这里的错误处理其实和普通的HTTP错误处理没太大差别,主要是返回的错误提示内容需要自定义。app/main/errors.py 使用HTTP内容协商处理错误@main.app_errorhandler(404)\ndef page_not_found(e):\n
if request.accept_mimetypes.accept_json and \\\n
not request.accept_mimetypes.accept_html:\n
response = jsonify({'error': 'not found'})\n
response.status_code = 404\n
return response\n
return render_template('404.html'), 404\n以上是404错误的处理函数。这里函数中的if语句用于检查请求首部(这个请求有可能是浏览器,也有可能是其它客户端),判断客户端是否接受json和html,如果只接收json,那就返回json数据,否则返回HTML文档。500错误处理程序,可参考本书的。403错误的处理程序如下:app/main/errors.pydef forbidden(message):\n
response = jsonify({'error': 'forbiden', 'message': message})\n
response.status_code = 403\n
return response\n作者之所以要把404、500这两个错误的处理方法和403等其它HTTP错误分开说明,是因为通常404和500错误是由Flask自己生成的,并且这两个错误通常会返回HTML响应,而其它的则只需要返回个状态码和提示信息就行了。14.2.3 \n使用Flask-HTTPAuth认证用户跟许多公开的API一样,使用Flask程序的API,也应该要先认证才能使用。由于REST \nWeb服务的特征之一就是无状态,即服务器在两次请求之间不能记住客户端的任何信息。于是客户端每次发送请求时,都要包含认证信息。在这里使用HTTP认\n 证是一很好的选择,HTTP认证中,认证信息包含在请求的Authorization首部中。Flask-HTTPAuth扩展提供了一个便利的HTTP认证方式。可使用pip安装:(venv) $ pip install flask-httpauth\n安装完后,需要在程序中初始化Flask-HTTPAuthapp/api_1_0/authentication.pyfrom flask_httpauth import HTTPBasicAuth\nauth = HTTPBasicAuth()\n\n@auth.verify_password\ndef verify_password(email, password):\n
if email == '':\n
g.current_user = AnonymousUser()\n
return True\n
user = User.query.filter_by(email = email).first()\n
if not user:\n
return False\n
g.current_user = user\n
return user.verify_password(password)\n由以上代码可以看到,这个API其实是允许匿名使用的。在后面就可以看到,匿名用户与认证用户在API的使用权限上是不一样的,也就是说两者能够从API获得的功能是不同的。针对认证失败的情况,也需要做出处理:app/api_1_0/authentication.py@auth.error_handler\ndef auth_error():\n
return unauthorized('Invalid credentials')\n书中在这里,还有一段说明:为了保护路由,可以使用修饰器 auth.login_required:
@api.route('/posts/') @auth.login_required def get_posts():
pass这段代码是为了再下来的这一段说而展示的:不过,这个蓝本中的所有路由都要使用相同的方式进行保护,所以我们可以在beforerequest处理程序中使用一次loginrequired修饰器,应用到整个蓝本所以千万不要把说明中的那段代码也写到authentication.py文件中去,否则会出现路由错误:这个错误曾困扰了我两天,一直没法找到原因。接下来就是在before_request处理程序中进行认证的代码:app/api_1_0/authentication.pyfrom .errors import forbidden\n@api.before_request\n@auth.login_required\ndef before_request():\n
if not g.current_user.is_anonymous and not g.current_user.confirmed:\n
return forbidden('Unconfirmed account')\n应当注意的是,书中作者在些段代码开头处是from .errors import forbidden_error,这明显是错的,因为我们前面在修改error.py文件时,并没有定义forbidden_error函数或类,并且在上面的函数最后返回处使用的是forbidden而不是forbidden_error,所在开头处引入的应该是forbidden。从以上程序的if语句中可看出,这个函数还可以拒绝已通过认证但没有确认帐户的用户。14.2.4 基于令牌的认证基于令牌的认证其实在第8章用户认证里面已经讲过了,这里的需求和原理其实是一样的。具体可看回。 为了生成和验证令牌,需要在User模型中定义两个新方法:app/models.pyclass User(db.Model):\n
# 原有代码\n
def generate_auth_token(self, expiration):\n
s = Serializer(current_app.config['SECRET_KEY'], expires_in=expiration)\n
return s.dumps({'id': self.id})\n\n
@staticmethod\n
def verify_auth_token(token):\n
s = Serializer(current_app.config['SECRET_KEY'])\n
data = s.loads(token)\n
return None\n
return User.query.get(data['id'])\n以上代码和第8章的令牌认证相差不大,此处不再解释。由于这里使用了令牌,所以前面写的验证密码的verify_password函数要做出相应的修改,让它不仅可以验证密码,还可以验证令牌:app/api_1_0/authentication.py@auth.verify_password\ndef verify_password(email_or_token, password):\n
if email_or_token == '':\n
g.current_user = AnonymousUser()\n
return True\n
if password == '':\n
g.current_user = User.verify_auth_token(email_or_token)\n
g.token_used = True\n
return g.current_user is not None\n
user = User.query.filter_by(email=email_or_token).first()\n
if not user:\n
return False\n
g.current_user = user\n
g.token_used = False\n
return user.verify_password(password)\n这里增加了一个g.token_used变量,用于区分密码认证和令牌认证。服务器生成了认证令牌,需要将其发送给客户端,客户端后续可以用这个令牌来向服务器申请认证:app/api_1_0/authentication.py 生成认证令牌@api.route('/token')\ndef get_token():\n
if g.current_user.is_anonymous or g.token_used:\n
return unauthorized('Invalid credentials')\n
return jsonify({'token': g.current_user.generate_auth_token(expiration=3600), 'expiration': 3600})\n应当注意的是,在if条件中,应该使用g.current_user.is_anonymous ,不加括号的,而不是书中的g.current_user.is_anonymous() 为了避免客户端使用旧令牌申请新令牌,需要先检查g.token_used变量的值,如果已经使用了令牌进行认证就拒绝请求。14.2.5 资源和JSON的序列化转换所谓资源和JSON的转换,也就是把所请求的信息(在这里就是文章信息和评论等)转换成JSON格式。app/models.py 把文章转换成JSON格式class Post(db.Model):\n
# 原有代码\n
def to_json(self):\n
json_post = {\n
'url': url_for('api.get_post', id=self.id, _external=True),\n
'body': self.body,\n
'body_html': self.body_html,\n
'timestamp': self.timestamp,\n
'author': url_for('api.get_user', id=self.author_id, _external=True),\n
'comments': url_for('api.get_post_comments', id=self.id, _external=True)\n
'comment_count': ments.count()\n
return json_post\n如果有了解过JSON的朋友,理解以上函数应该不是问题,无非就是把已有的字段返回到一个JSON数据中,比较特殊的是url、comments、author,这三个字段返回的是各自对应的URL,在url_for方法中指定了参数_external=True是为了生成完整的URL,也就是绝对地址。类似地,可以把用户信息转换成JSON格式:app/models.py 把用户转换成JSON格式class User(UserMixin, db.Model):\n
# 原有代码\n
def to_json(self):\n
json_user = {\n
'url': url_for('api.get_post', id=self.id, _external=True),\n
'username': self.username,\n
'member_since': self.member_since,\n
'last_seen': self.last_seen,\n
'posts': url_for('api.get_user_posts', id=self.id, _external=True),\n
'followed_posts': url_for('api.get_user_followed_posts', id=self.id, _external=True),\n
'post_count': self.posts.count()\n
return json_user\n书中还强调了两次:提供给客户端的资源没必要和数据库模型的内部表示完全一致。其实道理也很简单,我们向第三方开放API,我们想提供什么数据让他们用,自然是由我们自己来决定的了。例如客户端请求文章信息,我们只返回文章正文给他也是可以的。然后是允许从请求客户端用JSON格式创建一篇博客文章app/models.py 从JSON格式数据创建一篇博客文章from app.exceptions import ValidationError\n\nclass Post(db.Model):\n
# 原有代码\n
@staticmethod\n
def from_json(json_post):\n
body = json_post.get('body')\n
if body is None or body == '':\n
raise validationError('post does not have a body')\n
return Post(body=body)\n由上函数可见,用JSON数据创建文章,唯一可自定义的字段是文章内容,文章作者自动选择为通过认证的用户,时间也自动生成。如果body是空,则抛出异常,这里的异常直接返回给调用者,由上层代码处理:app/exceptions.py ValidationError异常class ValidationError(ValueError):\n
pass\n注意,以上只是定义了异常,还没有进行调用和定义该怎么处理异常。处理异常的代码放在了一个全局的异常处理程序中:app/api_1_0/errors.py API中ValidationError异常的处理程序@api.errorhandler(ValidationError)\ndef validation_error(e):\n
return bad_request(e.args[0])\n这里的代码我并没有去细看它的实现,反正从代码表面看来,就是把异常交给另一个函数去处理了,我们并不需要去管它是怎么处理的。有兴趣的朋友可以根据书中的说明去研究。14.2.6 实现资源端点实现资源端点,其实就是实现路由函数,让API的使用者知道要把请求发送到哪里。app/api_1_0/posts.py 文章资源GET请求的处理程序@api.route('/posts/')\n@auth.login_required\ndef get_posts():\n
posts = Post.query.all()\n
return jsonify({'posts': [post.to_json() for post in posts]})\n\n@api.route('/posts/&int:id&')\n@auth.login_required\ndef get_post(id):\n
post = Post.query.get_or_404(id)\n
return jsonify(post.to_json())\n第一个路由处理获取文章集合的请求,第二个路由返回单篇文章,如果没有找到指定id对应的文章,则返回404错误。用JOSN数据发表文章的路由如下:app/api_1_0/posts.py 文章资源POST请求的处理程序@api.route('/posts/', methods=['POST'])\n@permission_required(Permission.WRITE_ARTICLES)\ndef new_post():\n
post = Post.from_json(request.json)\n
post.author = g.current_user\n
db.session.add(post)\n
return jsonify(post.to_json()), 201, {'Location': url_for('api.get_post', id=post.id, _external=True)}\n这里使用了permission_required修饰器,确保通过认证的用户才有写文章的权限。return 语句中,程序返回了所发表的文章,以及201状态码,同时把首部Location字段的值设置为所发表的文章的URL。所使用的修饰器的具体实现如下:app/api_1_0/decorators.py permission_required修饰器def permission_required(permission):\n
def decorator(f):\n
@wraps(f)\n
def decorated_function(*args, **kwargs):\n
if not g.current_user.can(permission);\n
return forbidden('Insufficient permissions')\n
return f(*args, **kwargs)\n
return decorated_function\n
return decoration\n博客文章PUT请求处理程序用于更新现有资源:app/api_1_0/posts.py@api.route('/posts/&int:id&', methods=['PUT'])\n@permission_required(Permission.WRITE_ARTICLES)\ndef edit_post(id):\n
post = Post.query.get_or_404(id)\n
if g.current_user != post.author and not g.current_user.can(Permission.ADMINISTER):\n
return forbidden('Insufficient permissions')\n
post.body = request.json.get('body', post.body)\n
db.session.add(post)\n
return jsonify(post.to_json())\n这里主要是先进行权限的检查,然后再决定是否允许修改。除了文章外,还需要实现用户信息和评论信息的处理,具体的代码需要查看本书的14.2.7 分页大型资源集合这节讲的是分页处理,让客户端可以通过API来获取多页内容。app/api_1_0/posts.py 分页文章资源@api.route('/posts/')\ndef get_posts():\n
page = request.args.get('page', 1, type=int)\n
pagination = Post.query.paginate(\n
page, per_page = current_app.config['FLASKY_POSTS_PER_PAGE'],\n
error_out=False)\n
posts = pagination.items\n
prev = None\n
if pagination.has_prev:\n
prev = url_for('api.get_posts', page=page-1, _external=True)\n
next = None\n
if pagination.has_next:\n
next = url_for('api.get_posts', page=page+1, _external=True)\n
return jsonify({\n
'posts': [post.to_json() for post in posts],\n
'prev': prev,\n
'next': next,\n
'count': pagination.total\n
})\n注意上面函数中的路由/posts/,这类似于一个文件夹的形式,所以其实这个路由返回的是一个资源集合,具体说就是以上路由函数会返回由多篇文章组成的资源集合。14.2.8 使用HTTPie测试Web服务测试Web服务需要使用HTTP客户端。常用的在命令行中使用(对,也有GUI软件可用于HTTP测试的,还有基于浏览器的,可以网上搜一下)的是crul和HTTPie。后者的命令行更简洁。可使用pip安装:(venv) $ pip install httpie\n安装完成后,在进行测试前,记得要先启动网站服务器 (venv) $ python manage.py runserver,不然测试的HTTP请求谁来处理。\n发送GET请求:\n(venv) $ http --json --auth &eamil&:&password& GET http://127.0.0.1:5000/api/v1.0/posts/\n服务器将返回第一页的内容,并且指出第二页的URL。注意GET链接,末尾处是有斜线的,因为前面定义的 \nposts路由中就是带有斜线的,如果像书中的那样不带斜线,那么服务器是会返回301跳转(指导跳转到带斜线的地址),如果是浏览器访问,得到301跳\n \n转时,浏览器会自动处理这个301跳转(跳转到服务器返回的地址)。但由于现在是API访问,这个跳转并没有被处理,所以如果访问末尾不带斜线的地址,将\n 会得到这样的结果:HTTP/1.0 301 MOVED PERMANENTLY\nContent-Length: 281\nContent-Type: text/ charset=utf-8\nDate: Wed, 16 Nov :47 GMT\nLocation: http://127.0.0.1:5000/api/v1.0/posts/\nServer: Werkzeug/0.11.11 Python/3.5.1\n\n&!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\"&\n&title&Redirecting...&/title&\n&h1&Redirecting...&/h1&\n&p&You should be redirected automatically to target URL: &a href=\"http://127.0.0.1:5000/api/v1.0/posts/\"&http://127.0.0.1:5000/api/v1.0/posts/&/a&.
If not click the link.\n\n匿名访问:\n(venv) $ http --json --auth : GET http://127.0.0.1:5000/api/v1.0/posts/\n其实就是把用户名和密码都留空,但是那个冒号还是要的。\n添加新文章:\n(venv) $ http --auth &email&:&password& --json POST \\ \n& http://127.0.0.1:5000/api/v1.0/posts/ \\\n& \"body=I'm adding a post from the *command line*.\"\nbody中的两个星号间的内容,会被系统自动识别进行Markdown渲染成斜体。\n使用认证令牌:\n(venv) $ http --auth &email&:&password& --json GET http://127.0.0.1:5000/api/v1.0/token\n在我进行测试时,使用上述命令获取令牌,得到的是出错信息:...\nFile \"/home/cavin/Code/Python/flask/app/api_1_0/authentication.py\", line 43, in get_token\n
return jsonify({'token': g.current_user.generate_auth_token(expiration=3600), 'expiration': 3600})\n...\nTypeError: b'eyJpYXQiOjE0NzkyOTU1NTcsImV4cCI6MTQ3OTI5OTE1NywiYWxnIjoiSFMyNTYifQ.eyJpZCI6MX0.6w2NeDDAnVe_Qmd2wi7PncJRR-AJl6ssdA46JGiAPdQ' is not JSON serializable\n在错误提示信息里,唯一与我们写的代码有关的,就是上面错误提示码中的开始两行。从末尾的错误提示那里可以看到,令牌是已经生成了的,但是它是一个bytes对象。我们可以把它解码成字符串:app/api_1_0/authentication.py...\nreturn jsonify({'token': g.current_user.generate_auth_token(expiration=3600).decode(), 'expiration': 3600})\n再进行测试时,就可以成功地获取返回了信息了。然后就可以使用返回的令牌来访问API:(venv) $ http --json --auth &长长的令牌串&: GET http://127.0.0.1:5000/api/v1.0/posts/\n注意,使用令牌访问时,密码是空的。而且,令牌的有效期(已经被我们设定)是1小时,过期之后,请求会返回401错误,表示需要重新获取令牌。本章到些结束,本章用到的各个文件程序,都是需要先导入各种包的,但是书中并没有讲到,需要读者自己去查看本书的Github仓库进行对比添加了。","updated":"T12:32:51.000Z","canComment":false,"commentPermission":"anyone","commentCount":0,"collapsedCount":0,"likeCount":1,"state":"published","isLiked":false,"slug":"","lastestTipjarors":[],"isTitleImageFullScreen":false,"rating":"none","titleImage":"/v2-46efbf2fd22e34c2463a1_r.png","links":{"comments":"/api/posts//comments"},"reviewers":[],"topics":[{"url":"/topic/","id":"","name":"Flask"},{"url":"/topic/","id":"","name":"Python"},{"url":"/topic/","id":"","name":"Python 框架"}],"adminClosedComment":false,"titleImageSize":{"width":1118,"height":664},"href":"/api/posts/","excerptTitle":"","column":{"slug":"python-flask-web-note","name":"Python Flask 学习笔记"},"tipjarState":"activated","tipjarTagLine":"真诚赞赏,手留余香","sourceUrl":"","pageCommentsCount":0,"tipjarorCount":0,"annotationAction":[],"hasPublishingDraft":false,"snapshotUrl":"","publishedTime":"T20:32:51+08:00","url":"/p/","lastestLikers":[{"bio":null,"isFollowing":false,"hash":"fb979a4adf1b","uid":128500,"isOrg":false,"slug":"zhong-pang-zi-46","isFollowed":false,"description":"","name":"钟胖子","profileUrl":"/people/zhong-pang-zi-46","avatar":{"id":"v2-c9c20f8be582bc2f01c8b32ff30a4260","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false}],"summary":"其实网站建设在功能上,截至前面一章就已经都实现了。这章及后面的几章,都是高级内容,对于像我这样的初学者来说,个人觉得其实并没那么重要了。所以本章我在这里的笔记也会比较简略。本章讲的是Web API,具体说就是为我们写好的网站内容开发出可供第三方…","reviewingCommentsCount":0,"meta":{"previous":{"isTitleImageFullScreen":false,"rating":"none","titleImage":"/v2-fab717e798109cfd47deb527a26bbad3_r.png","links":{"comments":"/api/posts//comments"},"topics":[{"url":"/topic/","id":"","name":"Python"},{"url":"/topic/","id":"","name":"Python 框架"},{"url":"/topic/","id":"","name":"Flask"}],"adminClosedComment":false,"href":"/api/posts/","excerptTitle":"","author":{"bio":"致虚极,守静笃","isFollowing":false,"hash":"c80c0c65c22ac4fe17d26ce","uid":92,"isOrg":false,"slug":"xujingwuwei","isFollowed":false,"description":"虚无 自然 纯粹 素朴 恬淡 平易 清静 无为 柔弱 不争","name":"东围居士","profileUrl":"/people/xujingwuwei","avatar":{"id":"a39c6db9c5b85d35732b8bc","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false},"column":{"slug":"python-flask-web-note","name":"Python Flask 学习笔记"},"content":"本章讨论的是用户评论,其实本章中的很多内容,包括功能的实现上,都和前一章的用户关注很像。13.1 评论在数据库中的表示评论属于某篇博客,所以需要定义一个从posts表到comments表的一对多关系;同时comments表与users表之间也有一对多关系,通过这个关系可以获取用户发表的所有评论。comments模型如下app/models.py Comment模型class Comment(db.Model):\n
__tablename__ = 'comments'\n
id = db.column(db.Integer, primary_key=True)\n
body = db.Column(db.Text)\n
body_html = db.Column(db.Text)\n
timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow)\n
disabled = db.Column(db.Boolean)\n
author_id = db.Column(db.Integer, db.ForeignKey('users.id'))\n
post_id = db.Column(db.Integer, db.ForeignKey('posts.id'))\n\n
@staticmethod\n
def on_changed_body(target, value, oldvalue, initiator):\n
allowed_tags = ['a', 'abbr', 'acronym', 'b', 'code', 'em', 'i', 'strong']\n
target.body_html = bleach.linkify(bleach.clean(markdown(value, output_format='html'), tags=allowed_tags, strip=True))\n\ndb.event.listen(Comment.body, 'set', Comment.on_change_body)\nComment模型的属性与Post模型几乎一样,不过多了个disabled字段,管理员可以通过设置这个字段来限制不当评论。此外还需要在数据库里面建立User、Post与comments表之间的一对多关系。app/models.py users和posts表与comments表之间的一对多关系class User(db.Model):\n
# 原有代码\n
comments = db.relationship('Comment', backref='author', lazy='dynamic')\n\nclass Post(db.Model):\n
# 原有代码\n
comments = db.relationship('Comment', backref='post', lazy='dynamic')\n13.2 提交和显示评论评论需要显示在每篇博客文章页面中,这个页面还需要有一个提交评论的表单。这个表单如下app/main/forms.py 评论输入表单class CommentForm(Form):\n
body = StringField('', validators=[Required()])\n
submit = SubmitField('Submit')\n还需要在视图函数中添加对评论的支持,就是处理提交的评论表单。app/main/views.py@main.route('/post/&int:id&', methods=['GET', 'POST'])\ndef post(id):\n
post = Post.query.get_or_404(id)\n
form = CommentForm()\n
if form.validate_on_submit():\n
comment = Comment(body=form.body.data, post=post, author=current_user._get_current_object())\n
db.session.add(comment)\n
flash('Your comment has been published.')\n
return redirect(url_for('.post', id=post.id, page=-1))\n
page = request.args.get('page', 1, type=int)\n
if page == -1:\n
page = (ments.count() - 1) / current_app.config['FLASKY_COMMENTS_PER_PAGE'] + 1\n
pagination = ments.order_by(Comment.timestamp.asc()).paginate(page, per_page=current_app.config['FLASKY_COMMENTS_PER_PAGE'], error_out=False)\n
comments = pagination.items\n
return render_template('post.html', posts=[post], form=form, comments=comments, pagination=pagination)\n注意,这里应该先在程序首部从app/main/forms.py中导入评论表单对象CommentForm,从app/models.py中导入Comment对象。同时,还需要在app/config.py文件中设置FLASKY_COMMENTS_PER_PAGE属性为其赋值。和Post模型一样,评论的author字段也不能直接设为currentuser,因为这个变量是上下文代理对象。真正的User对象要从currentuser.getcurrentobject()获取。评论按照时间戳顺序排列,新评论显示在列表的底部。提交评论后,请求结果是一个重定向,转回之前的URL,但是在urlfor()中把page设为-1,这样可以得到一个特殊的页数,用来请求评论的最后一页,于是刚提交的评论会出现在页面中。程序从查询中获取页数,发现值为-1时,会计算评论的总量和总页数,得出真正要显示的页数。评论的渲染过程在新模板_comments.html中进行。代码如下app/templates/_comments.html&ul class=\"comments\"&\n
{% for comment in comments %}\n
&li class=\"comment\"&\n
&div class=\"comment-thumbnail\"&\n
&a href=\"{{ url_for('.user', username=comment.author.username) }}\"&\n
&img class=\"img-rounded profile-thumbnail\" src=\"{{ comment.author.gravatar(size=40) }}\"&\n
&div class=\"comment-content\"&\n
&div class=\"comment-date\"&{{ moment(comment.timestamp).fromNow() }}&/div&\n
&div class=\"comment-author\"&&a href=\"{{ url_for('.user', username=comment.author.username) }}\"&{{ comment.author.username }}&/a&&/div&\n
&div class=\"comment-body\"&\n
{% if comment.body_html %}\n
{{ comment.body_html | safe }}\n
{% else %}\n
{{ comment.body }}\n
{% endif %}\n
{% endfor %}\n&/ul&\n最后,在首页和资料页中加上指向评论页面的链接。这里作者指的应当是在首页和资料页里出现的博客文章中添加评论链接。app/templates/_posts.html&a href=\"{{ url_for('.post', id=post.id) }}#comments\"&\n
&span class=\"label label-primary\"&{{ ments.count() }} Comments&/span&\n&/a&\n这个链接应放置在class为post-footer的div里面,位于最后。URL中的#comments称为URL\n片段,用于指定加载页面后滚动条所在的初始位置,这个comments就是页面中的id。此外,分页导航所使用的宏(存在于_macros.html文件\n中)也要做修改,为评论的分页导航链接加上#comments片段,因此在post.html模板中调用宏时,传入片段参数。具体的修改此处不方便陈述,\n可查看本书的到此为止,本章中修改到的文件有:app/main/forms.pyapp/main/views.pyapp/models.pyapp/static/styles.cssapp/templates/_comments.htmlapp/templates/_macros.htmlapp/templates/_posts.htmlapp/templates/post.htmlapp/templates/user.htmlconfig.pymanage.py可参考前面给出的链接此外还需要进行数据更新。如果不是从本书的github仓库checkout代码的话,需要先执行数据库迁移:(venv) $ python manage.py db migrate -m \"your comments\"\n(venv) $ python manage.py db upgrade\n13.3 管理评论在第9章定义用户角色时,程序给不同的用户分配了不同的权限,其中就有一个是Permission.MODERATE_COMMENTS。拥有此权\n限的用户可以管理其他用户的评论。为了让有这个权限的人使用这个权限,可以在导航?中添加一个链接,这个链接只有具有权限的用户才能看到app/templates/base.html 在导航条中加入管理评论链接{% if current_user.can(Permission.MODERATE_COMMENTS) %}\n&li&&a href=\"{{ url_for('main.moderate') }}\"&Moderate Comments&/a&&/li&\n{% endif %}\n/moderate路由的定义如下@main.route('/moderate')\n@login_required\n@permission_required(Permission.MODERATE_COMMENTS)\ndef moderate():\n
page = request.args.get('page', 1, type=int)\n
pagination = Comment.query.order_by(Comment.timestamp.desc()).paginate(page, per_page=current_app.config['FLASKY_COMMENTS_PER_PAGE'], error_out=False)\n
comments = pagination.items\n
return render_template('moderate.html', comments=comments, pagination=pagination, page=page)\n这个函数从数据库中读取一页评论并传入模板进行渲染,同时把分页对象和当前页数传入。moderate.html模板{% extends \"base.html\" %}\n{% import \"_macros.html\" as macros %}\n\n{% block title %}Flasky - Comment Moderation{% endblock %}\n\n{% block page_content %}\n&div class=\"page-header\"&\n
&h1&Comment Moderation&/h1&\n&/div&\n{% set moderate = True %}\n{% include '_commnets.html' %}\n{% if pagination %}\n&div class=\"pagination\"&\n
{{ macros.pagination_widget(pagination, '.moderate') }}\n&/div&\n{% endif %}\n{% endblock %}\n此模板中,在渲染评论之前,会使用Jinja2提供的set指令定义一个模板变量moderate,并将其值设为True,这个变量会用在\n_comments.html模板中,决定是否渲染评论管理功能。很明显,只有是管理员登录,才会把moderate设为True,由此不管评论是否有问\n题,都会被显示出来,并且在正文下方还要显示一个用来切换评论状态的按钮。修改如下:app/templates/_comments.html 渲染评论的正文...\n&div class=\"comment-body\"&\n
{% if comment.disabled %}\n
&p&&i&This comment has been disabled by a moderator.&/li&&/p&\n
{% endif %}\n
{% if moderate or not comment.disabled %}\n
{% if comment.body_html %}\n
{{ comment.body_html | safe }}\n
{% else %}\n
{{ comment.body }}\n
{% endif %}\n
{% endif %}\n&/div&\n{% if moderate %}\n
{% if comment.disabled %}\n
&a class=\"btn btn-default btn-xs\" href=\"{{ url_for('.moderate_enable', id=comment.id, page=page) }}\"&Enable&/a&\n
{% else %}\n
&a class=\"btn btn-danger btn-xs\" href=\"{{ url_for('.moderate_disable', id=comment.id, page=page) }}\"&Disable&/a&\n
{% endif %}\n...\n上面的代码中用到的两个管理评论的路由如下:app/main/views.py 评论管理路由@main.route('/moderate/enable/&int:id&')\n@login_required\n@permission_required(Permission.MODERATE_COMMENTS)\ndef moderate_enable(id):\n
comment = Comment.query.get_or_404(id)\n
comment.disabled = False\n
db.session.add(comment)\n
return redirect(url_for('.moderate', page=request.args.get('page', 1, type=int)))\n\n@main.route('/moderate/disable/&int:id&')\n@login_required\n@permission_required(Permission.MODERATE_COMMENTS)\ndef moderate_disable(id):\n
comment = Comment.query.get_or_404(id)\n
comment.disabled = True\n
db.session.add(comment)\n
return redirect(url_for('moderate', page=request.args.get('page', 1, type=int)))\n本章到此结束,到此又修改了以下4个文件:app/main/views.pyapp/templates/_comments.htmlapp/templates/base.htmlapp/templates/moderate.html具体可到本书的上查看","state":"published","sourceUrl":"","pageCommentsCount":0,"canComment":false,"snapshotUrl":"","slug":,"publishedTime":"T10:44:48+08:00","url":"/p/","title":"chapter 13 - 用户评论","summary":"本章讨论的是用户评论,其实本章中的很多内容,包括功能的实现上,都和前一章的用户关注很像。13.1 评论在数据库中的表示评论属于某篇博客,所以需要定义一个从posts表到comments表的一对多关系;同时comments表与users表之间也有一对多关系,通过这个关系…","reviewingCommentsCount":0,"meta":{"previous":null,"next":null},"commentPermission":"anyone","commentsCount":0,"likesCount":1},"next":{"isTitleImageFullScreen":false,"rating":"none","titleImage":"","links":{"comments":"/api/posts//comments"},"topics":[{"url":"/topic/","id":"","name":"Flask"},{"url":"/topic/","id":"","name":"Python"},{"url":"/topic/","id":"","name":"Python 框架"}],"adminClosedComment":false,"href":"/api/posts/","excerptTitle":"","author":{"bio":"致虚极,守静笃","isFollowing":false,"hash":"c80c0c65c22ac4fe17d26ce","uid":92,"isOrg":false,"slug":"xujingwuwei","isFollowed":false,"description":"虚无 自然 纯粹 素朴 恬淡 平易 清静 无为 柔弱 不争","name":"东围居士","profileUrl":"/people/xujingwuwei","avatar":{"id":"a39c6db9c5b85d35732b8bc","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false},"column":{"slug":"python-flask-web-note","name":"Python Flask 学习笔记"},"content":"本书共18章,我的笔记写到第14章。后面的4章分别是:测试、性能、部署、其他资源。这部分属于更高级的内容了。我自己也并没有按作者说的去操作,所以也就不打算要继续写出来分享了。我目前的工作是数据分析,工作内容和web开发并没有什么联系,而我因为是刚转行,所以数据分析方面也还有很多东西需要学习,所以预计会有相当长一段时间,不会更新这个专栏了。但如若没有记错,在开通专栏时,我是选择了接受他人投稿的,所以如果有哪位朋友想和别人分享自己的心得见解,本人欢迎之至。像我这专栏描述里面说的,我这专栏本来就是我自己为了加深记忆和理解而写的学习笔记,真的不曾想过会有人关注的。但目前为止也还是有19位朋友关注了。感谢各位的信任。也有几位朋友,是通过文章评论向我提问,或者直接私信我的,来往之中,应该也有帮到他们解决一些问题。于是这专栏,也是对他人有些许用处了。我使用的书本是《Flask Web开发 基于Python的Web应用开发实战》,作者Miguel Grinberg,译者为安道,人民邮电出版社出版,ISBN号为1这次是第二次刷这本书,第一次是在今年5月份,当时看得较快,半个月左右,便学完了,等于过了一遍,并没有学到太多东西,也就掌握了第一章第二章的内容。这次再刷,包括中间借不到书(对,我这书是从市里的图书馆借的)的那两个星期,我用了两个月。在上班之前,我通常是上午学习,晚上写笔记(下午看其它书去了)。找到工作上班后,就只能是晚上回来学习了,学完一章,就开始写笔记,然后再学下一章。我在学习这本书的时候,发现问题时,经常会和英文原版(电子版)对照,以便确认问题。有些问题的确是作者的失误,有个别的则是译者的错误。另外作者在本书中用到的第三方包,有的已经更新了,用法和作者在书中所讲的已经不一样了。我在笔记里面也有提到。后面如果有时间,我会继续去学习Flask的官方文档,到时会继续在这里更新文章。期待和大家再次见面!再会!","state":"published","sourceUrl":"","pageCommentsCount":0,"canComment":false,"snapshotUrl":"","slug":,"publishedTime":"T20:23:42+08:00","url":"/p/","title":"暂告一段落","summary":"本书共18章,我的笔记写到第14章。后面的4章分别是:测试、性能、部署、其他资源。这部分属于更高级的内容了。我自己也并没有按作者说的去操作,所以也就不打算要继续写出来分享了。我目前的工作是数据分析,工作内容和web开发并没有什么联系,而我因为是刚…","reviewingCommentsCount":0,"meta":{"previous":null,"next":null},"commentPermission":"anyone","commentsCount":1,"likesCount":1}},"annotationDetail":null,"commentsCount":0,"likesCount":1,"FULLINFO":true}},"User":{"xujingwuwei":{"isFollowed":false,"name":"东围居士","headline":"虚无 自然 纯粹 素朴 恬淡 平易 清静 无为 柔弱 不争","avatarUrl":"/a39c6db9c5b85d35732b8bc_s.jpg","isFollowing":false,"type":"people","slug":"xujingwuwei","bio":"致虚极,守静笃","hash":"c80c0c65c22ac4fe17d26ce","uid":92,"isOrg":false,"description":"虚无 自然 纯粹 素朴 恬淡 平易 清静 无为 柔弱 不争","profileUrl":"/people/xujingwuwei","avatar":{"id":"a39c6db9c5b85d35732b8bc","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false,"badge":{"identity":null,"bestAnswerer":null}}},"Comment":{},"favlists":{}},"me":{},"global":{},"columns":{"next":{},"python-flask-web-note":{"following":false,"canManage":false,"href":"/api/columns/python-flask-web-note","name":"Python Flask 学习笔记","creator":{"slug":"xujingwuwei"},"url":"/python-flask-web-note","slug":"python-flask-web-note","avatar":{"id":"v2-214bdfe998e1","template":"/{id}_{size}.jpg"}}},"columnPosts":{},"columnSettings":{"colomnAuthor":[],"uploadAvatarDetails":"","contributeRequests":[],"contributeRequestsTotalCount":0,"inviteAuthor":""},"postComments":{},"postReviewComments":{"comments":[],"newComments":[],"hasMore":true},"favlistsByUser":{},"favlistRelations":{},"promotions":{},"switches":{"couldAddVideo":false},"draft":{"titleImage":"","titleImageSize":{},"isTitleImageFullScreen":false,"canTitleImageFullScreen":false,"title":"","titleImageUploading":false,"error":"","content":"","draftLoading":false,"globalLoading":false,"pendingVideo":{"resource":null,"error":null}},"drafts":{"draftsList":[],"next":{}},"config":{"userNotBindPhoneTipString":{}},"recommendPosts":{"articleRecommendations":[],"columnRecommendations":[]},"env":{"isAppView":false,"appViewConfig":{"content_padding_top":128,"content_padding_bottom":56,"content_padding_left":16,"content_padding_right":16,"title_font_size":22,"body_font_size":16,"is_dark_theme":false,"can_auto_load_image":true,"app_info":"OS=iOS"},"isApp":false},"sys":{}}}

我要回帖

更多关于 flask python3 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信