flask笔记:8:修复BUG

寻找一下程序中的BUG

首先看一下数据库里拥有两个nickname,john,Susan
flask笔记:8:修复BUG_第1张图片

登入项目,用john用户进入,然后将john用户名修改成Susan,会发现报错
flask笔记:8:修复BUG_第2张图片
flask笔记:8:修复BUG_第3张图片
flask笔记:8:修复BUG_第4张图片
flask笔记:8:修复BUG_第5张图片
flask笔记:8:修复BUG_第6张图片

为什么会报错呢?
报错信息:
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自己处理
flask笔记:8:修复BUG_第7张图片
自定义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 中,这样数据就会出现在表单中
flask笔记:8:修复BUG_第8张图片

#判断form表单中nickname与g中的nickname是否相同,如果相同就说明用户没有修改用户名
#判断修改的用户名是否存在数据库中,因为每个用户名都是唯一的,所以不能相同
#初始化一个变量version,作为用户名标识,循环数据库里的用户名,如 john1,john2.....之类,找出没有的新用户名
#用flash的形式告诉用户他修改的用户名已经存在,推荐一个新用户名
#仍然跳转到编辑页面
flask笔记:8:修复BUG_第9张图片
flask笔记:8:修复BUG_第10张图片

单元测试框架
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()


flask笔记:8:修复BUG_第11张图片
#jc_nickname( )是自定义用来检测用户名唯一性和推荐用户名的方法
#setUp( )是框架自带函数,这个函数在测试前运行,这里用于连接数据库和建表
#tearDown( )是框架自带函数,这个函数在测试之后运行,这里用于清空数据库
#test_make_unique_nickname( )自定义函数,unittest中的函数都要以test开头,这个函数用来测试用户名是否可以入库

你可能感兴趣的:(python,flask)