寻找一下程序中的BUG
首先看一下数据库里拥有两个nickname,john,Susan
登入项目,用john用户进入,然后将john用户名修改成Susan,会发现报错
为什么会报错呢?
报错信息:
IntegrityError: (sqlite3.IntegrityError) column nickname is not unique [SQL: u'UPDATE user SET nickname=?, about_me=? WHERE user.id = ?'] [parameters: (u'Susan', u'12345\r\neee\r\nddd', 1)]
因为nickname 字段unique=True,是唯一的,不能重复
而且项目是debug模式下,所以会把错误信息显示在页面上
修改 mybolg/run.py ,将debug=True 改为
False
from app import app
app.run(debug=False)
然后我们再将nickname改成Susan,会发现出现了服务器内部错误码500
这个500的错误页面是Flask自己处理
自定义HTTP错误处理器
声明一个错误处理器需要使用装饰器 crrorhandler,修改文件 app/views.py
from app import app,db,lm,models
from flask import render_template,flash,redirect,url_for,session,request,g
from .forms import LoginForm, EditForm
from .models import User
from flask.ext.login import login_user,logout_user,current_user,login_required
from datetime import datetime
@lm.user_loader
def load_user(id):
return User.query.get(int(id))
@app.before_request
def before_request():
g.user = current_user
if g.user.is_authenticated:
g.user.last_seen=datetime.utcnow()
db.session.add(g.user)
db.session.commit()
@app.route('/')
@app.route('/index')
@login_required
def index ():
user=g.user
posts=[
{'author':{'nickname':'John'},
'body':'Beautiful day in Portland!'},
{'author':{'nickname':'Susan'},
'body':'The Avengers movie was so cool!'}
]
return render_template("index.html",
title="Home",
user=user,
posts=posts)
@app.route('/login', methods = ['GET', 'POST'])
def login():
if g.user is not None and g.user.is_authenticated:
return redirect(url_for('index'))
form = LoginForm()
if form.validate_on_submit():
if models.User.query.filter_by(nickname=form.openid.data).first():
user = User.query.filter_by(nickname=form.openid.data).first_or_404()
login_user(user)
return redirect(url_for('index'))
else:
return render_template('login.html',
title = 'Sign In',
error='[NO]',
form = form)
return render_template('login.html',
title = 'Sign In',
form = form)
@app.route('/logout')
@login_required
def logout():
logout_user()
return redirect(url_for('index'))
@app.route('/user/<nickname>')
@login_required
def user(nickname):
user=User.query.filter_by(nickname=nickname).first()
if user==None:
flash('user'+nickname+'not found!')
return redirect(url_for('index'))
posts=[
{'author':user,'body':'Test post #1 !!!'},
{'author':user,'body':'Test post #2 !!!'}
]
return render_template('user.html',
user=user,
posts=posts
)
@app.route('/edit',methods=["POST","GET"])
@login_required
def edit():
form=EditForm()
if form.validate_on_submit():
g.user.nickname=form.nickname.data
g.user.about_me=form.about_me.data
db.session.add(g.user)
db.session.commit()
flash('Your changes have been saved!')
return redirect(url_for('user',nickname=g.user.nickname))
else:
form.nickname.data=g.user.nickname
form.about_me.data=g.user.about_me
return render_template('edit.html',form=form)
@app.errorhandler(404)
def internal_error(error):
return render_template('404.html'),404
@app.errorhandler(500)
def internal_error(error):
db.session.rollback()
return render_template('500.html'),500
#errorhandler装饰器,处理错误
#session.rollback( )回滚数据,发现错误就将数据回滚到错误之前的状态
添加404,500错误模板,新增 app/templates/404.html;app/templates/500.html
404.html
{% extends "base.html" %}
{% block content %}
<h1>File Not Found</h1>
<p><<a href="{{url_for('index')}}">Back</a></p>
{% endblock %}
500.html
{% extends "base.html" %}
{% block content %}
<h1>An unexpected error has occurred</h1>
<p>The administrator has been notified. Sorry for the inconvenience!</p>
<p><a href="{{url_for('index')}}">Back</a></p>
{% endblock %}
因为debug模式关闭,所以需要重启服务器才会生效
记录日志到文件中
修改 app/__init__.py
from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy
import os
from flask.ext.login import LoginManager
app=Flask(__name__)
app.config.from_object("config")
db=SQLAlchemy(app)
lm=LoginManager()
lm.init_app(app)
lm.login_view='login'
if not app.debug:
import logging
from logging.handlers import RotatingFileHandler
file_handler = RotatingFileHandler('tmp/microblog.log', 'a', 1 * 1024*1024, 10)
file_handler.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'))
app.logger.setLevel(logging.INFO)
file_handler.setLevel(logging.INFO)
app.logger.addHandler(file_handler)
app.logger.info('microblog startup')
from app import views,models
#判断不是debug模式下执行
# logging.handlers.RotatingFileHandler('日志保存路径和文件名','写入模式[a=追加]',限制文件大小,保存文件数量 )
#RotatingFileHandler('tmp/microblog.log', 'a', 1 * 1024*1024, 10) 表示把日志以追加模式写入'tmp/microblog.log'中,限制大小1M,限制数量10,当microblog.log日志文件超过1M,就会出现microblog.log.1,这个也满了就会出现microblog.log.2,一直到到10,之后就会再将microblog.log覆盖掉,一直保持只有10个日志文件
#setFormatter( )是设置格式
#logging.Formatter( )是格式化
%(levelno)s: 打印日志级别的数值
%(levelname)s: 打印日志级别名称
%(pathname)s: 打印当前执行程序的路径,其实就是sys.argv[0]
%(filename)s: 打印当前执行程序名
%(funcName)s: 打印日志的当前函数
%(lineno)d: 打印日志的当前行号
%(asctime)s: 打印日志的时间
%(thread)d: 打印线程ID
%(threadName)s: 打印线程名称
%(process)d: 打印进程ID
%(message)s: 打印日志信息
#setLevel( )设置级别
DEBUG:详细的信息,通常只出现在诊断问题上
INFO:确认一切按预期运行
WARNING:一个迹象表明,一些意想不到的事情发生了,或表明一些问题在不久的将来(例如。磁盘空间低”)。这个软件还能按预期工作。
ERROR:个更严重的问题,软件没能执行一些功能
CRITICAL:一个严重的错误,这表明程序本身可能无法继续运行
#addHandler( ) 好像是添加处理程序
#logger.info( ) 可能是打印日志方式,记录文件,不过参数里的字符串不会记录在文件中
修复BUG
以下是我自己编写的修复BUG,与原版教程不同( 原版我没看明白 )
修改 app/views.py
from app import app,db,lm,models
from flask import render_template,flash,redirect,url_for,session,request,g
from .forms import LoginForm, EditForm
from .models import User
from flask.ext.login import login_user,logout_user,current_user,login_required
from datetime import datetime
@lm.user_loader
def load_user(id):
return User.query.get(int(id))
@app.before_request
def before_request():
g.user = current_user
if g.user.is_authenticated:
g.user.last_seen=datetime.utcnow()
db.session.add(g.user)
db.session.commit()
@app.route('/')
@app.route('/index')
@login_required
def index ():
user=g.user
posts=[
{'author':{'nickname':'John'},
'body':'Beautiful day in Portland!'},
{'author':{'nickname':'Susan'},
'body':'The Avengers movie was so cool!'}
]
return render_template("index.html",
title="Home",
user=user,
posts=posts)
@app.route('/login', methods = ['GET', 'POST'])
def login():
if g.user is not None and g.user.is_authenticated:
return redirect(url_for('index'))
form = LoginForm()
if form.validate_on_submit():
if models.User.query.filter_by(nickname=form.openid.data).first():
user = User.query.filter_by(nickname=form.openid.data).first_or_404()
login_user(user)
return redirect(url_for('index'))
else:
return render_template('login.html',
title = 'Sign In',
error='[NO]',
form = form)
return render_template('login.html',
title = 'Sign In',
form = form)
@app.route('/logout')
@login_required
def logout():
logout_user()
return redirect(url_for('index'))
@app.route('/user/<nickname>')
@login_required
def user(nickname):
user=User.query.filter_by(nickname=nickname).first()
if user==None:
flash('user'+nickname+'not found!')
return redirect(url_for('index'))
posts=[
{'author':user,'body':'Test post #1 !!!'},
{'author':user,'body':'Test post #2 !!!'}
]
return render_template('user.html',
user=user,
posts=posts
)
@app.route('/edit',methods=["POST","GET"])
@login_required
def edit():
form=EditForm()
if form.validate_on_submit():
if g.user.nickname!=form.nickname.data and User.query.filter_by(nickname = form.nickname.data).first() != None:
version = 2
while True:
new_nickname = form.nickname.data + str(version)
if User.query.filter_by(nickname = new_nickname).first() == None:
break
version += 1
flash('username already exists! Recommendation:'+form.nickname.data+str(version))
return redirect(url_for('edit'))
g.user.nickname=form.nickname.data
g.user.about_me=form.about_me.data
db.session.add(g.user)
db.session.commit()
flash('Your changes have been saved!')
return redirect(url_for('user',nickname=g.user.nickname))
else:
form.nickname.data=g.user.nickname
form.about_me.data=g.user.about_me
return render_template('edit.html',form=form)
@app.errorhandler(404)
def internal_error(error):
return render_template('404.html'),404
@app.errorhandler(500)
def internal_error(error):
db.session.rollback()
return render_template('500.html'),500
#在没有提交前全局变量中的 g.user.nickname 和 g.user.about_me 会传到表单的nickname 和 about_me 中,这样数据就会出现在表单中
#判断form表单中nickname与g中的nickname是否相同,如果相同就说明用户没有修改用户名
#判断修改的用户名是否存在数据库中,因为每个用户名都是唯一的,所以不能相同
#初始化一个变量version,作为用户名标识,循环数据库里的用户名,如 john1,john2.....之类,找出没有的新用户名
#用flash的形式告诉用户他修改的用户名已经存在,推荐一个新用户名
#仍然跳转到编辑页面
单元测试框架
Python 自带 unittest 模板用来测试
import os
import unittest
from config import basedir
from app import app, db
from app.models import User
def jc_nickname(nick):
if User.query.filter_by(nickname = nick).first() != None:
version = 2
while True:
new_nickname = nick + str(version)
if User.query.filter_by(nickname = new_nickname).first() == None:
break
version += 1
return new_nickname
else:
return nick
class TestCase(unittest.TestCase):
def setUp(self):
app.config['TESTING'] = True
app.config['WTF_CSRF_ENABLED'] = False
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir, 'test.db')
self.app = app.test_client()
db.create_all()
def tearDown(self):
db.session.remove()
db.drop_all()
def test_make_unique_nickname(self):
u = User(nickname='john', email='[email protected]')
db.session.add(u)
db.session.commit()
nickname=jc_nickname('john')
assert nickname != 'john'
u = User(nickname=nickname, email='[email protected]')
db.session.add(u)
db.session.commit()
nickname2=jc_nickname('john')
assert nickname2 != 'john'
assert nickname2 != nickname
if __name__ == '__main__':
unittest.main()
#jc_nickname( )是自定义用来检测用户名唯一性和推荐用户名的方法
#setUp( )是框架自带函数,这个函数在测试前运行,这里用于连接数据库和建表
#tearDown( )是框架自带函数,这个函数在测试之后运行,这里用于清空数据库
#test_make_unique_nickname( )自定义函数,unittest中的函数都要以test开头,这个函数用来测试用户名是否可以入库