Flask学习之七 单元测试
英文博客地址:http://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-vii-unit-testing
中文翻译地址:http://www.pythondoc.com/flask-mega-tutorial/testing.html
开源中国社区:http://www.oschina.net/translate/the-flask-mega-tutorial-part-vii-unit-testing
一、之前的bug
我们有一个数据库功能的应用程序,它能够注册用户,允许用户登录以及登出,查看以及编辑他们的用户信息。
Bug 就是没有有效地让我们用户的昵称保持唯一性。应用程序自动选择用户的初始昵称。如果 OpenID 提供商提供一个用户的昵称的话我们会使用这个昵称。如果没有提供话,应用程序会选择邮箱的用户名部分作为昵称。如果两个用户有着同样的昵称的话,第二个用户是不能够被注册的。更加糟糕的是,在编辑用户信息的时候,我们允许用户修改昵称,但是没有去限制昵称名称冲突。
二、Flask调试
注:要调试这部分的BUG需要两个OpenID帐号
先让我们创建一个新的数据库。在 Linux:
rm app.db
./db_create.py
我们需要两个OpenID的账号来重现这个bug。当然这两个账号最理想的状态是来自来个不同的拥有者,那样可以避免他们的cookie把情况搞的更复杂。通过如下步骤创建冲突的昵称:
- 用第一个账号登陆
- 进入用户信息属性编辑页面,将昵称改为“dup”
- 登出系统
- 用第二个账号登陆
- 修改第二个账号的用户信息属性,将昵称改为“dup”
sqlalchemy中抛出了一个异常,错误信息:
sqlalchemy.exc.IntegrityError
IntegrityError: (IntegrityError) column nickname is not unique u'UPDATE user SET nickname=?, about_me=? WHERE user.id = ?' (u'dup', u'', 2)
错误的后面是这个错误的堆栈信息,事实上,这是一个相当不错的错误提示,你可以转向任何框架检查代码或者在浏览器里执行正确的表达式。
错误是相当地明显的,我们试着在数据库中插入重复的昵称。数据库模型对 nickname 字段有着 unique 限制,因此这不是一个合法的操作。
除了上面的错误,我们还有另外一个错误。如果一个用户不小心在应用程序里引起了一个错误(这一个错误或者任何其他原因引起的异常),应用程序将向他/她显示错误信息和堆栈信息,而不是返回给我们。对于我们开发者来说这是个很好的特性,但是很多时候我们不想让用户看到这些信息。
这段时间内我们的应用程序以调试模式运行着。调试模式是在应用程序运行的时候通过在 run 方法中传入参数 debug = True。
当我们在开发的应用程序的时候这个功能很方便,但是我们必须在生产环境上确保这个功能被禁用。让我们创建另外一个调试模式禁用的启动脚本(文件 runp.py):
#!flask/bin/python
from app import app
app.run(debug=False)
现在重新启动应用,并且现在再尝试重命名第二个账号nickname成‘dup’,现在会得到一个 HTTP 错误 500,这是服务器内部错误。当调试关闭后出现一个未处理异常时,Flask会产生一个500页面。
虽然这样好些了,但现在仍存在两个问题。首先美化问题:默认的500页面很丑陋。第二个问题更重要些,当用户操作失败时,我们无法获取到错误信息了,因为错误在后台默默的处理了。幸运的是有个简单方式来处理这两个问题。
三、定制 HTTP 错误处理器
Flask 为应用程序提供了一种安装自己的错误页的机制。作为例子,让我们自定义 HTTP 404 以及 500 错误页,这是最常见的两个。定义其它错误的方式是一样的。
为了声明一个定制的错误处理器,需要使用装饰器 errorhandler (文件 app/views.py):
@app.errorhandler(404)
def not_found_error(error):
return render_template('404.html'), 404 @app.errorhandler(500)
def internal_error(error):
db.session.rollback()
return render_template('500.html'), 500
注意一下错误 500 处理器中的 rollback 声明,这是很有必要的因为这个函数是被作为异常的结果被调用。如果异常是被一个数据库错误触发,数据库的会话会处于一个不正常的状态,因此我们必须把会话回滚到正常工作状态在渲染 500 错误页模板之前。
404 错误的模板:
<!-- extend base layout -->
{% extends "base.html" %} {% block content %}
<h1>File Not Found</h1>
<p><a href="{{ url_for('index') }}">Back</a></p>
{% endblock %}
500错误模板:
<!-- extend base layout -->
{% 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 %}
上面两个模板中我们继续使用我们 base.html 布局,这是为了让错误页面和应用程序的外观是统一的。
aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAAAawAAAA3CAIAAADffOnPAAAAA3NCSVQICAjb4U/gAAAebUlEQVR4Xu2dCTxU3RvH7yz2NZEl7Zvq7y1vWlVv+6K3XfG2CKUVUZLSot2SSioVhaRCizbtos1SEopEFCMksguz/O+dGQxz77kzQ8xk5tPn877u2Z7ne5577jnn3vu7hKlDhlfLEysSiiDxT0xATEBMoOMRIEIEQsfzWuyxmICYgJgAmwCRwWCIDAyiwmBLR0e7/0mLjMUiZagYr0h1l9jY1iJAbI15IEFxwp5bJcmXdw2Wai27UOshKg+1XGk8XUsCNVXQgwS5bnqzpuiqkgStQJByEgOtb1YmHZmpxAP/9sdL1FocVJmewPp3f5qCIB6jl5Gbeja2xH+8Inpqs6O/zwyemuc/E3cvt02wtQTU77OQmwb/RH9LCWJr1EqU6d6/q4R0j0FqknxUR5AdON/xfmQMfGoVRfm4zdJupwme5EBzt5C9hj1bd2gFg5DsaTSve8Wz66/KeJiHC4YXbAB/qfTvD7YOn73YYJVfBn8FAbkJcv1mHwkMvjRRUsLAMy/50YsDk9RxrkO/wwyAhS1OQunltgm2loD6bRai0Ggx4dapgNwa1dAKgiznf+hZ9/FDOc/VkbrPd33qqhN12HFiVIHs0CUn3U8zchdvfVfFcw0inFGq9wyT7pUP9ySW8jAGQoLgbWU41FJKSilELv+71bpHTs8lYK95eZid5fY3RZBy1/66MlmlNByzW98MnAZblMxfL7eoqeaFhRBUO9JoTqf5301mgkRNQ5fEt2+Yq57Y9FBny8FyzHSpvzacTklEDqZdOXj6/K3v6fE517f+2wW+cLMn3mUJN5/dCL82tclCidjZwDUgNC0+llnhi2T/TfO61k8V5Yft3Da2yNfW4kxk3MfUyGCPXfFdVpjrysON6Vh9SH/hP0qWZariFJ/K9FCb7o2jtZ712bT3cIVx6cE7VwyQZa0nAaUAZsjp76Wkx0QtV4XUTKKSWcu9V0Fj5BCf8cwgqU088/RNWeT+uWr8TqcldebO7Fn+wi+Bdc0QEC/CR1Jj6pr9jyOeVyCE4wsiTzvosAgTOw1bctQ7IDH6RQlrGRvvZ9uHPdeV1By/8/S1XPj4p8jX3utmaDaZA6PibR42Tf8GVCipMc7pVEhOGsK2/N391/6bpqoguKS6GUxSg2K8PH0jkxKSk57ev3r8RtYvcDM4qUTF/xmfCb1XyPS3POlxzL4xnfA3G7BiHoLDxi0o7AsrKlIfv/RcOUmNHYSAJA4bm/UyBAg2uBS58zCbw4EZKbDxb77edts8shNzWgyIDZCFQFSYLgtkIdwUL+Sb0wBa2MaJcLc2Bgr95/s7+xxu5BZWEVUGm27fesynJmnyodhqsvoQvR55PgucMv495mpR4Wdsmr3w6G4vq3sRu5K+37PXeysjoW4Y5L+yme1E2Z4Tx/StveC8KLKQqK6/wck8yLto6MKA9DpI/q8Fs5Wz3ELSqpX+snF23DhloAa8GE7t1ZkUm49HgFCdfNLp9Cdqj0UbbU9dUvg53fHWDzqgEMCMqveeE2Zc1rPy9h8bY2Z2NrkGroZWmlsNqK0hSUrbYLo2iQSNmdpN8mYhP+evVJ///tUsfer8mj1vFhAvRFI3Php8fhr9+UWftTGZedUSalryn/OoTAtJ6qONV0+BAg4e2J7+o4wmoaxC/vitDk4gKI1wD/FcRXu40+5oCmHAcnurayEqs/49GFk/KeUXL6BCguJwt+DjlsSn+xy8Yn9AWpMczpqOHih//FExnVr67TsEDV9tYZh0Kjz7Fy8TYpxOkdE94Ou48Iu/jXnkp3KikmaPXrSvlfj1YsU8BIfNhBE9Ss9vXxX1U0p7xNrNVrdDVWfMcXtexgAkNRrZvJchQLAR5Ibsu+y7tjbMyeZofLnqxFWOe/0Ol05b7UsBxUYttoVAVpguC2QhDeKFPBcNoIVtnEhuEia/sl9czWZZkPyeMHbxGX0DDXJsFnKE8fNTXHw07X2Neae06JgXlW8cFul0kyckfS/P+1QOkasLsUaO3NiI8Ofw6R6bLDMmZdf0caqB6Xkk7b8HKpS+iy1UXnj09KFBkVYWLpCVzwk1STL+pRt66+t1/CEyfjxJIQ15aOMw41j4xW+42NDMoDN+FWV8rlD+WQfV/cz6nPmRn6GsKsl7xf7yqYRor0R+ikGQzIA5Rhql4aFN9g4EwCunt8ZtmnzM3rlzAim1qP5XZ1wNuRdRyZlG1Da0XqXxZb/hziPpcKEXTz9JDL6zZvfMc8+vsK8+qHhZIytaI4AKv2vOsFqlRXGf4+iSihioKGMKmSqzKqHlh1sfmHjXaXnIkyXZ0Te8zweei8rmYcxCM4F1TEJZWxH6mR7/JDqxAF5WJyZgZ22SghHzbI/z4l88fgUHW0zEB8brMHPn6f7TQgtY5VGTGhb03L2MHWxELUO7DV3j1kzcd/k7cjmP+8QY+8rNcrKGf0AJ/CdWbBQz7QCbgQoBy2WBLMyl8kCemwaqYe10EF6aNA6DpC6jzc4F385Jjq/48OzdoVGSkKScRJO1Hp3OgAjwHWUGnUqHiER+njGkFWXmVkHymvLwNF9CrbcKVEwpltdfNV7mzbFjF15nZMIjEZ+/urw3zwqhfvp83VHhNIPP9pplpxVFBXju8I9DTrn6H1l98rGbLyrTX2eFuTvPH9pLRWOSg++dDf057vlI6y6YolkSFZDUZHBiVcAPXrLW30NVoRT/+9/QR0B052T6jO4NFb5+Uj9s1mS/fFoIDRzZXYYrP294ARVK9xnTh1AUe/8LmoGM6vf+1gPGmVmeeFAwwOiQz82EI4bdWrJDXRbjuPcJ3cQr89WlS9uXGPZF4oyHH37Msyqpzox8XEgYPAYFFFoSqJe5rJLua9CXJDnC92U86/57RZzbRDKk1kuFc58CNzbQzOBqCjnAq8schYEW4pPniwaqzb/1IEfYSfQwueq/sfdLn81rnqWWEDTGbgreotqscQadRqWjrD3rB1LgRI5BoyMjKJyHICMvCdX9osurqxKrUnIrm2yIM+AGiJIkYFUsswjIUp79oCPvpRrNAJLlvcLGaqSGrLKe/tGp/4qsAXMt9uz02+IGQdVvnRZ9bZwryugsn6H649HNBLRbDHzhZcAXJIiBdy8Bzcem1y5M0Jx40appPIZeIYEkQYLodVTMNSnjV37iJc/ES94n5u05H2S0bV9olNkrlGsDuPH61Jq0K/a6jwYazv3XZP7aUHObeJ+N8z1ii4CAeIl5dv0MCI57AuplnzsJ2Mtc7hCQeUXxXVOL8x+QDRnWj1FX8u0X1Hh+YsVGY23cZnC1BB/gw+XG4gAL4Ux45PmjgWb07z3GMQhK9xqtS6QcdfUNTkMu3ZkKeb+gZoNg5VPrscz1DPuuRYNtjNpK+DxX6CxLgsqAUccqwaiprIOkFSSrs0shaRV52IjGaSC1NP8nJNOnbyfyy0rsVRhSj3SPsZPUGCnROchKnOdSDTbX/w+jpqoWkunUbOaAbwap8/ilS6cRY7wCGyaDNfEHFvyPSIcH+9wA54igw9rdVKj5lPzqxsuG/OC5czoX37qWWsFlBwTxhZea/y65CDI0nawZeiWX51l09efoTGjm8MndJOM+Ib0s1d1gghqUGpsNM2w2GjbBy7SWXlcDNySrKE2EyutdAlRYk5OUB037e3QX8ptsYE/W5t0LCP9iZNFLS5YIVaJcY5uyQjODnaO2KDXsfGqYn9egledfb3U0D110OAvUNA8xz65ZQlN/vBqU/obCvffBnYTdy6jBVp3x8jNjpp4+Of/6h6qml4yG8xMzNhrYcJuBCgrPZX4txCePTaNpv7bbX+TGa1vN1/hPkIGp9ZKkwPjsakLnv9R5f3CPXvY5Jg+ysVxnWRzxXVFL6u21y5/RFkFsP+u+pRVCU/prVd++9h7aZ26o8/wWoX7ZTfsRdyWJ4W63e3d5YEQ+VU1fjXOIhCvQGj5+akWxTPeRlrYr+n4Ltb7PXI/ilcImXJebmFG9ZuIem7muL36Q1XsovLsWlFGDa4bsX2sv7DRSg+YpJxlavW04NZARkP2jVlCymo11cnqLJqoWPgxMwdpBRTcTFW/FO5/dz6ee2Hfxto7fhZefC2rISuqa5PjrIZkA8nRKuNe59Wd2nN1P9bibQuq/bNOafvlXZ91DGLJOOHS8TLvo5VmJRZCZmcXykphyZQ3y2xtwW9gVMrJu+T+x2rP/1HbayadfydrjjQfCW1gsDyW0Zx1cqZX0/G1qQSVdtusE87k9oXS/hBLcERDLDEheb+f2iSXRsQnZJTXSGgZ6XSBadgHH5QeVLG7MD1tlbSMbnUHtbrTRZmDZ4yX38+ExlQUKNYnZCqCX0YMtN/zYqTXnbAJ9O528fDe1qE5GrZ9WaVhgZA5oAGc7hG0Gen/hucy/hTjkATRQ+6TtD5Lh5SR7DlCbFWRipXTE1vT8xY3wmx+MmvJvKc8zkKUqD48R16R5OPoOcVnhcWIOveTjOft7wcBBkBIdk7955oL+xXZbXMf5bYtPcoBdr4opr4WNoVF8rew0D9qvdvXcBB+tLc2Mf5BehZwd9Kqvz95QTJbtDzODsxUlPjw594D/M9bzxlTMUnhQGcWRbuuCDhxa5hxqAVGLU87Z372cUUPHq7CG8uohZb4J4dWjHMCg07Rx+cFmk5Xybt1+z98YiCw4uPHSqbn+640LVm7YsmiNz1Jkbl6Zn3xx54OrmbWAoYRRGme/2LZor+3mI8cUoIpPkb6Ldp9l3RoG4WX5Uf3+oNOlQftMTnmb0H6mnrdntoVdITXvjulyhaN7Vroen0+k/kj5guxA05jvaZJlFFSHGB1dtp65F0kvSnvmYuly/DNvM1o0MwhS0kTVUZtdljMf2K/9nhbjYe0Skg8ggTSMHfMshyG6nJ6tyyJ1EjUv4fp6Kw/O5xAwk0C9jBFs5W+3Ga3Mdtiwbt3e5fADWrSyjBf+Ty/xNAhimgF7gAYKz2W+LSSByYNosCG3938IU4foV8uT2lxFRrKP482rjmWuw5Zd+UyT7KyuJltb/K2omoeldHsTa0H7igYuaf56gQvnOCQ17v20oD7RKkrqbX45eevPpaPWhpU0rPng1+YiQiW3dDd7ViZk3pC7LX0VYV+8YfwM5qMInD9AEpytzXoZbIaQ4GwzGi3wF34mBXNjvAXV4hatzTzh6LMgZOu9o2RLlxsv84oInaXIhGoa5iY6bo3Cn0FhpPFYxbzrl9M6zAgoq2O+RLc842t+JUGl3z8b7fpVRG2NLv+T+xjeGO9wvQw680SCBhm+uUqCH/lt8x+jIvH0NOMyLzfr8KebkdZ/hk35Z080v+vENrdb4AYJSkPM/5HLuRSe2mHGQLJyn3Hz1s8foAyvUOnllJc3D8x0f8z5RBG8gn+0eiT70UGByQpTwQ7YywD8IkIDXg4Pp6lIF8eyN6wBHv2mJKK8mqaGLK04/3txDc4Ozm+yQFytmICYQAcmgNwdbpeZYANzekVhbkYH7gGx62ICYgLtSgB5MIXcHsvhdvVa3LiYgJiAmACbADIItsNMkKiou3aHk70u98tawtIzQiKzLPygWqXDpHrOct61Zopqk3c0W6VmuJIOwrDluDoqKCLEIJDILXlnUyD2RKW/LFYYTdHEaphfeduWSOmiO/B7VKzR2wIcFX5QiPEt1r6W6jtvy/Ipg+AXRn7DTzQYtr7j/J5EUEcFRWQQuGeCBFldizMF6QlJNn0a9PKJSkM3eoZQPiEidHHHV4xR5opXtFKC9iy/8rYtkdIV1EahKCccoNpf+7olnSEcDFviAXpZfv1Cr4XjKL8VispZiUzFmu0JSg5Y5vHAfkATJCQts5NnDup+8Nxm+xL623aX7R2vcj2z618bH25GK4VLtfUyCKGUbus515o1/RZQQqB93ZqM8Or6LQzxGhXFdBEB1XxPkKgydst1e6VTq3bfKW3ELj3A2GEk4+H2LTuuRd295rnM6RVj1CrL/g1fFEEvhdttYw/cLkJ0gF+9D7CbU//5JGx5W5DKLm5bgmXAkFnG1OYFq+xiqAfjmybEoASVFocI8v3mHr/y4CccAKmPHtnCrxU3/jBAkbTnHstLf+o3nfVRLKL6dFdK+v3jY5S4ViUoSIWYIY5GNLZwN6akM/ZJhEKm2SFhBoVvvSA5mg6CZPXp3kcmJzrZuSQ0yITAtRK76I3oBn0Mfiu/5uLzgqsrVN89SIY0JwxRYT1kjVEK3xzKk9OWlhuMtl76qmt6ycuoN3OHkClvu8QMHoNLHpjNWzhsJvzvP/sE+CnqepVdk50Pu85crvnYwnTfYy0TL6vBzTVt8FvmNQdTZtl2oe2JZ53mn7rkPJu9c8/S5t0wxch02uqjEcpzj/nYDGfd42Gp7NLubzQ3+8fIYp7DCd9nbH1jlnrwHp2MIzYrJy3b7p2vD6sHm2vz9Jy6EIOiI9LiMxeOMDuXzgWVrekddnCRxQbjbSGUIcuDvP/rx3wTnaQ66fRlZ3OV1862NsbbLkRXNY5j2KBolNu7l12pWezhsrqXJFnT8JTr1CJfh+3Rpbw8XyrEDOtVrC9tn7ti3eLdt4pHwSrWm8cpIu9ysYS7HXUyPOysF24O/DRo9bUQhwmsjxRiBxv2ScTVSVwHhBkUl7GtcgAZd+rvDpM0/ju0TT/KaWR4AVWqK0f1ZBVtZagyKb9Wbmg3eXkpbcWa1G/V0FBtRQkon4ZZCt++LxF3r0ciotOpCuM/7DAcqxKc+R1H7RlLZbeZAhF+27zlwJJZxtLmhTBVdgHqwbm4WiHCDIoqiLQ4obuh5Xyl9O1Gzp5fYO+fP6sYb+nNenMECIpeGnHQ7uDQi4d99gwumTg1x9vgeFIFb6/hCTNDVjCiaUQXamErgcMfTcAS08bWiMaPe+EHhe8DfzmQj6+z9gSJXSbbHxry1sHtJeYXO6pTdsyeOXC6S3SjICgPpfANov3IhL/roaitxNO0CK4PV2UXv00+czSVWcbW5sVU2QVq8/JqjAiAwnaFU9NbuueI7lBhwitEmKrZDwcUozrV3fpwnPYM8yF5LpsDkvl+z1IEGHJoRAOEuyEIM9iaMxXobxEAJZBf3IWYM0HkERmC0gTzyZ0UoQuv4i80ZLO++n38/v8Z3yimlEBy6hqysB5Sfja8PFbroiUD/aSU1YFKXcvhXROGVkeDiCRU2V5um2E9JgyBa7S8rXSMQ2YZqM2LpbIL1ubl2UhhBVU/GwOqcXBoeiOy2AT4+wzcjuOCIncdOW4QCZbU7mm8YJCX6zu+BRmElWEji2Ya0ejC3XB2rGDjpirQEeEHJZBbXIUaXptjlN6znz9Mpn5rRmbwvot7h4TZLTgbn0ejk9/F5UDGi4crh9wrhrXvVYdP14XyvBOLaQwqdqn6tlAUmLnMaH4AVd62IROmyi6qlG7zugX6m1NmWRZPghtN3xigHvwngOJTWrz686vP0MxRhr2lYjkU5ZkgwKAI8rqWV/YMSzpgbP1r8/N9h91jjNZHckix/hHBxqERDRDuZocNWrCx5tfAk+iPACXQqcxdqHFPkF6e++VjQwYZ5ZI66FdRTnpuOUyUmhbiHmty/JCbs8yFaGjoxl1jCLH7fZj67IBSrMowFJi5TeE8gipvCy6CpKJKH+MXw86BKrMM0ubFVNml46oHizQoVO1rbK6wwPUJ3/Vn7P2Pkz2CIyk1KsO7wAqnzPwgUASFv529Vnd74jgM/mQhtGvDhOt+7lvvzHC6U8S+NSLSDNE0okFK4Hhi2qgnEVvCSKRBYceVYCmwsjSdh3eHYRFjq7UKe7c7uHpuhspT7nvO3nmD4yFBUNuCKDBDqPK23PtHXO2iSenycuuQqyKQijVAmxegssvAUw8WUVBsdGja19xUG44wyuK2LNqQt9NmrbOHLbwVTC3NenPvE/P7IpigaHIjbPatk3/43+5HechOy/frew+bP9p9bGNo1O63rEWxSDNE1YgGKIEDgo3JGfUkYis1iTQoQFwJlIRIaf01ceSTs/cFKi4uJCYgJtBiAiKhEd1iL4W2AiK8o90OAgpCy0NsmJiAmEAHI8B8WJrM66MpHQyO2F0xATGBP58AyrvDf77TYg/FBISKADUnaES/IKEyqSMZA88EYWVpLEWrjkRC7KuYgJhAhySAPK8q3hPskF0vdlpMQEwAIdBUQKHNmPxJGrZiVeQ2CxvBGvqTgk0wAjyW6qigGt8d5hFU62T7kzRsm6sig2SW/zyx39bX9EYiDMSQ7wj8k4KNb+f5KdBRQTWdCUoN2PAeEfhr+Oe3qBP79U6AsjQgiZ8u4Mwryhq2IJllfv3C5cdvha0u9tvqFTJdBjHEZcJvhnZnyK/B7ZX/TwXFfHe44REZkoySLJR9ZJVjcAHz9Qx6VXYZ89V4gLI0IKkNO0uINGyFW2a51UG1eoVI1HQwhm14orRpU78lNlrfg6YzQaKcijytMP5d6vuP6ci/T7llTCUYgLI0IAnXWiHXsMXQN4bdwlJFBsksd0yxX2xVZEhSY5zTqZCcNGTZUf7u/mv/TVNV4HAEMSR2NnANCE2Lj2UuVl4k+2+a17VB3hwn3IQ52KR0rD6kv/AfxVYHVpziU5keatOd9dAGpnw07DB2iBI7DVty1DsgMfpFCWthF+9n24epZ4v3E2ZQeLYLls4aBNmPyBBkOqvJ1FZJd1FVkuAUOQIoSwOS8A0SZg1bbH1jgCoySGa5A4r9AlSRCYrD3YKPb9elnHSw+dfMZu21ukEGowfKw+EIYgiQqsaNNmEONpDx2PLRgBCF127qo41XT1F+efbAUvPVM0w3mOzyu/utDtRQfZqoguLFN/Q85AZRVTidrKT861ut/tnwRySIRokL3bXDMyTrFwMCKEsXg0Sn0ZtsPCrEGrYAfeP8bpiqyBBAZrnjif0StTFVkb9rzrBapUVxn+PokoqIxyjKmEKmLGVpEENW6OTGRoQ/RwTJk2XGpOyaPk4VlpTBV8kQ4mADnieY8tGAEK3XKq/OuBpyL6ISWD9XoqiC4nKE5wNEZE+QxH5tri7jwpoB+mMUdQwGmhx4omp8/vK2iayvGbDqQ1GWrm8IkIRvixBq2AL0jQGqyPiutiyHEIICOARQRZbuM6YPoSj2/heWfBagEkASp1Q1IFuzJNFiCJCPBoQo7zQAOUUNFMAVnCTOb4w0ZKVVZcff2LJtsGHwjBWDXCOif2EqS0NU7CSclpskC52GLUDfmIytisz2iSeZZX7wNOYVOlB4bqCrIhNIEiSIXkfF/jwITww5pKrx7OBIF0KGDDqsVSxJQhHaxpaPBoQoHzBAWYUQFMhcgdOwH5YmNGqf078jytI6sLI0kr1eWfoprCwN7+BgJtXbBGvYmtrsNxuhzodMA1AUF2IqS//34AfXKcShLC0wEVZBpr6xip4+OT8tI/Mj+1/W5x81DIipiqyKqCJjtcEhs9wsC9AvkQTFdhCNPFMVWXX45G7sexdS3Q0mqEGpsdnVUE1OUh6k9vfoLpgvbGIzxKLOPC6aDKml+T8hmT59O2HhYMpHu5vMmTbcNXOYpaM5cs8EEKJARKxE0QTFg2MCZGEKKLAfkZEdZL1+dPn7j1/LGErd9VfYLFAruhWQgnzJ5he2sjQgiWWOaGrYAvSNAarI7A7Alln+Y8V+UTW9KeFe59af2XF2P9Xjbgqp/7JNa/rlX511r4AGMbJu+T+x2rP/1HbayadfydrjjeHvDudxxi82Q1CUi2awQbQfcVeSGO52u3eXB0bkU9X01SCo/iZGC7TKAaREFBTAoxYkccwESbLK6gNn7nI/cSfgZNCO2ervzi01Pvi0lDndYipLOz1TWuXqedXVqPNzz9lW9crSgCSmXUwNWxot99WjHN43gFiiuKldlzmHnj8RuGXWME1p5iQU78dUlo5TWXzK+5i/w9wx2rI8lUKrlaVvvDW8ety6vVfOnbzmuWmVQS/W5/BYqsj7YpWWOnuE+Z04b6qU9eYNSxWZXRNTZjmKPNPjxLEAhzkjG80A+SWioNguo5FnqSK7pffbfORYiLupTprvImO3SGZEUfPumC4/fJ02wfX48RA3i0md4Y5i0Bgcc3tMhmi9VX9MVBlSKb5WdkcSNFa7esJnn99y5cz4t+lVyN2eevnoEw+uXoy8eHBjjw8e1k4h+Y0S3KghCmLETBNVULiOCZKBMG3oiDXbNu4z3y1IaXEZMYHWIEDqbX45eevPpaPWhpVw7XG0RgPiOsQEsAlgbUJglxCniAm0AgFZHfMluuUZX/MrCSr9/tlo168iams031/PbAVDxFV0eALiQbDDh0C7ACAr9xk3b/38AcrwTRN6OeXlzQMz3R/Dm4Xin5hAmxMQD4JtjlzcIEyA+u2uxb93LfhjAb8qh1VAT64fVpL4uJgAmADyoSXxT0xATEBMoMMS+D/+bbY1RpyreAAAAABJRU5ErkJggg==" alt="" />
四、通过email发送错误日志
为了处理第二个问题(当用户操作失败时,无法获取到错误信息)我们需要配置应用的错误报告机制。
第一个是每当有错误发生时把错误日志通过邮件发送给我们。
在开始之前我们先在应用程序中配置邮件服务器以及管理员邮箱地址(文件 config.py):
# mail server settings
MAIL_SERVER = 'localhost'
MAIL_PORT = 25
MAIL_USERNAME = None
MAIL_PASSWORD = None # administrator list
ADMINS = ['you@example.com']
Flask 使用 Python logging 模块,因此当发生异常的时候发送邮件是十分简单(文件 app/__init__.py):
from config import basedir, ADMINS, MAIL_SERVER, MAIL_PORT, MAIL_USERNAME, MAIL_PASSWORD if not app.debug:
import logging
from logging.handlers import SMTPHandler
credentials = None
if MAIL_USERNAME or MAIL_PASSWORD:
credentials = (MAIL_USERNAME, MAIL_PASSWORD)
mail_handler = SMTPHandler((MAIL_SERVER, MAIL_PORT), 'no-reply@' + MAIL_SERVER, ADMINS, 'microblog failure', credentials)
mail_handler.setLevel(logging.ERROR)
app.logger.addHandler(mail_handler)
Note that we are only enabling the emails when we run without debugging.
注意,我们要的非dubug模式下开启邮件功能.
在没有邮件服务器的pc上测试邮件功能也很容易,幸好Python有SMTP的测试排错的服务器(SMTP debugging server)。打开一个控制台窗口,并且运行下面的命令:
python -m smtpd -n -c DebuggingServer localhost:25
当程序运行的时候,应用接收和发送邮件会在控制台窗口中显示出来。
PS. 我到这部分的时候不知到为什么收发邮件这里没有成功。
五、打印日志到文件
通过邮件接收错误日志非常不错,但是,这是不够的。有些导致失败的条件不会触发异常并且不是主要的问题,所以我们需要将日志保存到log文件中,在某些情况下,需要日志来进行排错。
出于这个原因,我们的应用需要一个日志文件。
启用日志记录类似于电子邮件发送错误(文件 app/__init__.py):
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')
日志文件将会在 tmp 目录,名称为 microblog.log。我们使用的RotatingFileHandler方法中有个限制日志数量的参数。在这种情况下,我们限制了一个日志文件大小为1M,并且把最后的十个文件作为备份。
logging.Formatter类提供了日志信息的定义格式,由于这些信息将写到一个文件中,我们想获取到尽可能多的信息,因此除了日志信息和堆栈信息,我们还写了个时间戳,日志级别和文件名、信息的行号。
为了使日志更有用,我们降低了应用程序日志和文件日志处理程序的日志级别,因为这样我们将有机会在没有错误情况下把有用信息写入到日志中。作为一个例子,我们启动时将日志的级别设置为信息级别。从现在开始,每次你启动应用程序将记录你的调试信息。
当我们没有使用日志时,调试一个在线和使用中的web服务时是件非常困难的事,把日志信息写入到文件中,将是我们诊断和解决问题的一个有用工具。
当你运行runp.py然后打开一个页面:
aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAAAb4AAAAlCAIAAACyORuKAAAAA3NCSVQICAjb4U/gAAAYf0lEQVR4Xu1dB1wTSRffTU8IoROaAmIBERV77xU9e+EsCKecnmI7PeXk7Oihfvbz7Iq99ztETymKAioiRRBCEaT3kIQQSDbfbkJnd1MEFcz+/KnZmXnz5v/evJ03u/tf8Na1S9vdNgOaQ4OABgENAhoElEaAoHRNTUUNAhoENAhoEKhCoOWEToK2vbun5+ouNI3tmgMBDbzNgapGZutF4PNDJ8gatvV+ScyVTfbUZoWJoNvdfeHssWbkJu0F1GrjOGGUgyGxSaUqEEa2W35PEL1vvA6ooCJc/PXhJZjNuiTgRMr/+I/RVqyzsjW0Rp8IL/EdwlKqfjOp0XwOoIqVlUJAU+mbQuDzQyeB3rajOZlm2dmIosLIQIbdVE//oDB4QhYGn9w9weIrLSYpdm67r29zsmragIwPBMVqxpS2/Ge3X5ZK8SsiperBq1iu8jWgvEfre/8wa+Cis0nKN1JQE9Tq8MO+C9cuD6eQBx7MjvkvZMcItoKrV3OoAWvZbA6gkpUVwKUp/gYRIH22TpLcS+5T31tVfnjPU1oWse3UXYG7bIP/5zk8OJfRfc6RPcekmbPWvytTWkILrkhtN865reDx1iiuEpETUAfeJgZHzM2I4wIkXo8mM4+Wo8+5bW68u6vdN7wpBHTNOzrQU7kSBWo3vRoKOvysYtWs/FldaRp/FQRqV50EUyefqLdvZHlZOOfGFnd7LVkhteuyY3FRyMmEqzuPnbmfx4n4dHv9RGN4kVCVQ5VG3nt2x+/W6HqpHMFg4K5zNxIiwmUCQ2J8f51iXr0sZfbc+PugwlOrfjoe9OpDfNC1vZsijBe4OTDhzmw93nNCfPsx5GCwRp0UcG6saFsb4B2Xn0iIhQW+4lzbuKATQ57x4rTCUUOr17YMTljwfEPAyDk4Rp6Qvrw0QAsZsyI1iEbDjwe+KQ3ynmyk6rqdYjt5vBUv5Gyk/EqjJrwIPhST0Yu9nwQ85yMIR+QGHVtnK0eYoNdzzv6j56JCQ0rkiXbE2VU2VetqiumQjcduZcLnE4NeH/1lnGm99TYqvPiOiSOQYjLY6+/rnxIQbHnv/F/7/jpaH4GL2mbgCCMg7PDBU0HRkTHRgf43D91JLcfvRkEpgdVl9vEbD/Nl4+VFPwnbPkBP0XYItgPgGAWAPWr3pbsf5Q4T/+TFwYUjjBovQBpYWYH2muIWiECt0aHi2H+2r7uTmV9G0Ld32bD+wElR9Mg/w4UkdjdHy+yT07ySJh7Y9RP/7GyX9On7Nx/2eBiwKTrv4VrHt3Qy2+mS78IGgycwrIYPaF9xfsvMoHwCu9cyL7dLRwu7Tz/HqQSYXaf9oJu6+3qCUKfrii2eK0fZmcDpery1ATE8RxGEoDDmiNexRLHlzJWr/r6sXTzW834BhNMIR42y2IPDxl1x9DjqOyjM1fVEjAgWI+FmCnGk1RRRLQaOtSASgQGj21Du5asy66k2P0405QZueV21RlcTXoDInr3/2pkx0POLJ5eEpWQLyUZmzORssUxDIrv/7J9HAed27tjAKSiVkHX1SR+yKuECUKfPnusHF0keb1y9Pw7sNH+tx63r+hMm7gyqXgCrCi+OQJDVe/e1Q+6EwO3rDocXAGYj1p1w6W/HPPRfESTmZuUBQO+ff3KK/tsvvVyZxbcCo9AddpzynP7Rd4VbUCKPoGNqaS1JEyiSi+0AdByfr2BYDetjyT2zYVFwMdWiz5I1Hg9uGI6btPt53e2XhlZWoL6muAUiUOd6WZ4ecjNdPoSYWHDQrOO9BpqQwlORM9LixFcRoZJYkZteQmhYiODNupm2bZhgdB4vO5EHkIT5WPEmMzzA7zkcJMJj6APiNo0dbHiBk0206GGnzX0Xnq87ff+xPzsHefzkA3ic/MuIQlK0TIA1eXvq8KHHSNR5Gkfs9njFunEH/C5mKcQdTQ1IWl6YlMzXLa4EKotTk1M+qBIAy6KPLvDmjQZDD0ep0gwA6J0mzTDh+t2ot7uhBrxajot3j2GGbZs86UJGBer4hUk3rz8MENQtI1g4LV9k8tHbaeM+DtwoJDCRbP/P4s3jTz+/WnXNQoVXHo/ROsERmGc6zmORWcaeSZ4+8YiCLLoL4KIrFyLJ8Vu+Y/i/XvOvP52THnrn6JkLp4PTFUY6NAWqz5F1LVhAMSfiaWhULpz4R0XiVa4uw3cALKMUyZpnR4Q8eQn7YVjAe+nru25bxvqOuYH0LD9QrayMSpo6LQeB2nSTaNzf9fS1B59iIvjvn737sx8FoGiR62WjECQFQAIISCExBBAIoBKRrhoISWFKZhnANGXCaT7ZqJ0+UJRRxOy1aAj9zYED518npcDxS8WjMvvNs3ygQy+V7i/VVUPF/hpUlxQGnzv4h++r2ukCACT2yAP3QgSc16l392yZ2t1a32TEulP/LOtY5w4YzWHaKNOS4HPR9UKaXLYq8JLMenQ3BOJ8/bPQ4yb64Og2/dsB+a+fVgdbUfqLwHzArm9beqP6ysGLI5BmM8AGLAz3/4imoFQY67u802BX978e5Xaa8efJe5H7nNo0znobaYV5ojTMc9tTyPlwysvLlzfMcWqP+FmTHAqNIkwJepIP2g+oiyGelZtEK42QbwCB6thItnS+6btyrODh2sULBsxctvh0TGOXl0ISMYSSHVcnRrihVCqBkLgL1wHpTApQWQ4x2YaEsvRMQb3bA1K4AwKFiCtKDhsIwJWkUlnnyreqVQMXfOUF1oqhdlu0fOwHr469py+8Ixi58Wxs+MMH88AnT9Jq16V02/njDAv+uxeJdsNFJXil8GUMkCq6s4I2xvpXPEyg68KLJqb2HLpAkEgmAlClGDNrlpbnRF0+6DVssNPcmznmE3/f3gfZZVb3ECVcXevQb87c45GE/ktuPHwSuK6vQVOETyyj1OopBeApAdYFAdfK6g5Q0+5bQ6A6dNKs+zsQMk7sOnXtZWx0XMzLqOxGiaggcPkg3R8fFTSaDNIKAVxZ24ChnK9KRYJKgKZNERZyAZo+s95iQ8zNKQboNu31FC5BaJaDRhhJ40I/wXsFyrdqBL9UVFYB0PUarFIUCyQaDHFZ4e3ap84jNaKIHdO6/B6cWZIecG7L4D6DO42ZbNPX/UACsocqP5j2kycZFN2/Fc9vpAcAqASvOOddTCFg6zKy/l0eFLF1TwmTQ1MAw94j21Tdr6O2HTjMCIgPT2+831IXXrkIqFIEpwYMFq1OJoIjUPQpOhsw6tHfWJElK7IfnvP7CDCtzRjK3HFDU6NqjBWF8XfP7HGeNKb3rpSe7p5ude4uYgOD7gDV9TGNUiOQbNpriBHAeZNRM19wrYytiKakhSFQ7diitIhEYKDL8jnRFyLShaBBV7byD1pCpclh2cAK91/ciwLyWGbUt7euJDdes9YAU5mVkA+M6mgmfHArFtju5mT7/D5YPWkkBa+uRkv3rN68mXchIEds1MsIAOrl8ma9h4zmF9Hb9nVftaB91o3l/rKMWVErbKNUZkYlCRcP37pi8q6QAhLbUvvdrUtJIoVqMLouOb9xhhEwRTfayeNtzayB4KV11SHmZ6Q2iJBajjOHG+Y/vhDXOFJhKwgAqPDy353c/Hz0X9svPrA9e/5Fcq6IpMM2JUXcvp6CgzyU4Xf49NLjf5zwFu/9N47Ycd6vizvk3JzwEMFQ7gjo8Mq0g3ipUYWAq+tP80vCeLompLd34L6wBUpT7/s+9djq/fcGyZHANJLFkNl28A6hfJxkiwk7F5pFP38bnyuAGObD3CZbAZyzkSUoGU0jYFDVAJiOGzcMLwkNj0wvEdFMBjoaA5L0XKEy8lAdoFGvjU70XLR8BSM0Sdx2xsoVdqVP5vjnVO8Iq2nlRj1oTnzjCFSHzorUS84eOvtWuZy5uBJ+K0gq4mXFPU9CkmklHhYXJez1PNXNZ8HevyZBJR9Or314DTd0ZoSG5awZP61j0erfdg0++3tE9DoYpLIwXgW8npVknPJYbbpz7c+7Dv4Kn63gpkQ84pQhcwAqS3v2JsN5nvddV7haYdTjI5N3+D6T39YUY7ZSBL+0KGj3L5d2/Dlvy42fAHFR3Om1/15JEkGKBIoyXj7OmOoMvvzvE06oqt850951pE72/QexqkVOAECDFxJn+i6dnbtw2W8zF5+cizzLJciJubjx0c2UCpyAIeW+WjtrVeG2VWv2HdAG+IlBp2ZuPiG/vY4Hr3wcwtidXpc7b3f++6izpDj+zFpZX9gCxdn/uMzX3r914a5DUwnigriP8OVRKpFtsJDo2obdZuyft1S2xwoVJjzzcfc5lKzcfjeaGiCVRjDst8Znvuy1jIq8hLC9y32u5+AgUWMaVAfAvjFW3Q7SclzlM5NNFGdH3l7qsbf2MQ+1rVyjkeY/LQMB8GswJ1FsPO/d9Czd1XPe1WQJxYBtxKgoyioUqrNz1zJQRrRkDfRJ8HW8MH3SuujaFL7lqP+ZmhLbuV2JWV88t9+SuyU1Oz7wi5gBNyi/tXV9VvqZ4r9gc1KbuS8D1hYtGzJO9qRHg+P7tnJDNFr1b0U7Uc0y+IqUvzxPTru+/uF+krvPnRfZhaABlQQKJY12UZul968jVLvv7EGs7NtX6mx9fh1FvlivDFu3OQ68pLQcAajfYejK1R34wetDea3ZxvCG/3dn5S/mTt9cR18ldAJSftSxMbNLD+9e7he4BsGk+O6ooVtDVc1kvzk0MRUCdbq5DdX6dNkv/rtZcZJ0bQZPWTq1ky6cQ0O8jBf3dozf86Tus1zwHsN/P/etetQTE7mWVPAdWrklmaeJdf0qCXvtGAhMI1MThqQoJ69IpMzOVBOPXiNOg4AGAQ0CaiHwdVadNapC/PzMpuPjUQsBTSMNAhoENAiojIAyj9KpLFTTQIOABgENAq0bAU3obN32VXp0GpZ4paHSVNQgACPwDYZOCnP01sFHLvf7sQdFJe1AEknXQpsNU9c1ONQV2FDOV/ndmlniGwPaTFTwjTv6rs9gzpTvGhVVB69ScFJVuFr1QaZ2985kgMzo2oOuwk4sWXvK4SE79vf4wbphIzUFqqV80zdqnSzxWDg1ExU8Vnff5XnsmfJdwqH2oBvGGbUFNVlDaUne5SOMwdYV7+6UKv2mDtI7FpOF2gKbbEifI6hVssRjA9KyqOCxx/FNl2DNlG9a6W9OuZrQSaA4LOk+t7+WNg0GFipJyg84nxAYD7PLwQfIaG8+1aVNTzs68uU2UUVW5MeThzLyZOS52EUwGbjJxAVWA7oxaICkIDbb3zc5LK36sXcyzW689djRRu1NEJbOyhLe24uxl55LenoPWtChCqTBlsI1WwpqWUiwNCRQ+tS2IjpuGnJELiA+9rctJV1wBCJ05egakszM5nm07dSGzpKhwU3Oe3Qi8VmKnAYIb8iq2xcmJD949ee+loyKjIingWVdpg02F8Vc/2XJ//7Jk5rNusDZ0Vku83nj11dglng3j/Wzh/aDqVMBiJ/5eu+SFbs/wJcbmCXeedOisSO621gaaiEv0pa+85r18wHZm44wqfv6zSuXjGynK+XGPb22cdsp/+zaNyARlvh9thbUyqy3D7w37T2fUKbwEXYcgTBL/G+bli8Z2QHmhocEuR/ePd7w6wGY6lh1lKpafLG+cB1APbeBX2lG8/lg5CU6MttovKv1oB5M+MMMgk8FL65x/MKFMqs0qbPhzJRNeTCZF7oaRFrvxfYTe7MMmSBUVJpYRGnfngYWFN7fHfs0VUI0M3dZaWlrTtNCQoOkID73yfmkkCRswiy1bf/NNawJnZBEKAbKC/hFQinVmGXSnj3Ni1i0LCaSKwW1DeZu6tidDlTm8z+WAAwTpqkpCMnemsQpIrLNFvvYdoKfh+aXFwI0wy4W83yYtDXvArMg+B3mwev7OHdDiJbEXGGhAGDo06g8MfyCc3FcQUI5kWKia23U6NKIpWEpJGtFMXFg6QBAeVpxmuy1PhHM4iGVF6ELxNGQqKNr34HBgC8hWUKJHsPAxmSWlzTLI54jxBuyWsb9Hlni1QIKj+Ieh5Fevb5wHEA9t8H0efhCp2vs4tOlBxMoz+GlVVLbtjEcs1ab/EfYzQQJzvxSZ1xV0wFtpuCokUIy66JjyBTnpEEmlixbfVFmeqV5W4Mf5ui/3Jkv0dGxa0eDZ0pBmkCip822M3PewSSvfhsAT/NWftQm7JKkE6+2gCBVi0ylMUZv7zHCULe7BSGSKyHqsywQngbe/Z0RgRkITySFTqiUIYNdRLSebgPHTehD4vZtGXkQbYBXv7kOuk7TdV4eLiY42EyF4ybE99/27t/3VXQVCOGhFOBcjOYABOOpfTbPqfo8UR38MTR8L0ZakbWnHeo90lASfy7mVEwtfQO2QDwNqzrlZp9el5BKN1lxvHNHlkFPCwKHA2EPWX1PwSIkxyHhb9Es8WohpSYjvVp9VTdCcQAQx7Fx3IaO5fMAwXxSeyRuvorf51tcJiWYTOzuMYHWf7zOg4QiCfbUU2dcUpyZQmiDpcYR2bYZP/+Kd86gg469edm+20unHO1qb8yAqRq5cj242ee8ElKkzDE7+0y2ZI2drP3iKLe1vzdXEzrJ9F4L7GeMZdX5NhtRlp8D4uy88I+WE6y0p+8fOvxD3pvgrOfPiuWfGcAsIlLbdUIyxfSneflI4iGKDeYDDixGex1dEpfalQULlkSnPq2Om3ANOWex3BDof2NriF4f/yyehsUlddpKBYL0IqCjIUkPTotx0cDvUGEpCiE5ZrZcxRL/hzos8f/WZ4lf/CPMEn81pwE9q5wlfi5Mwn8xC41dVD4aOUs8qsBSGUu8PzpLvEIsUCp8yb7qdV/PAdR0G4IRls8TKO0cEH5HWh+7DX1q+6UZU6gEoBR76qEg9Dmn8NSouuMglUgEcDikkCiQmA+7C5HY8Is4FYK3gfzJrkxmO20mgdvaXw+sCp2g/jB717EskF/05FpOOo/ksKBjb71qU1TwH/4RljrcvN8Ao2527DG27DET0w94JXHgj0RgFcHI1p/2IFjnN/yRDqQcKzBIq+vWpd7G1VCB06AKxNWw/typRHYnqonAsYaM8skMBWo1KMYiJK+Gqd4ORstniVcNnKraajLSq9VXbSNpPQfAcWwct8H0eVD+SQTus8Qb4bXRRsItRagWm83ZGiGCo0YN7aRUAi+DQJxFDvzpHRk6sk8MtPaj6uEkUMcKuSFSFpHm558TEVqQXFw7cJBKNTOoTPRPOrspdO2SGORTjuYWw9ojQRezSCJKSURWmxZDDfWRzxHRHUex4J9lHG6JGCqI4cEXMlJX6+F2JNSHo0SlsoxbT4sli+xkCjxj8DREKkkhEdKIqMeWywSpdJm3yg4UgXgaVjdD+xdzyGiVVTmHSUiORsLfKljiUZj2qwBDo4JvCkZ6VeyBXldNt8H2eagiXXb7TsdWS5xUGPUqH/4Tn1DK4SDu3CzOhjpTcNWogqKCd3tZwLKlnDSUVFw21Qi0Dn2Rr6SUZ5TxWv1WZxU5OCAtiONXjtFnDO32u3FxNh80blPrOZR21iu3mWkJy3NzKiqotDZISl+eVYxgg10Epd5K4Qzs1KGL7daTVsUg/CEN5Eu9frdlOyBRyf/E60+z03baNmQMV1gkAEhMsjjk3Z9n5U8jSflJRbkAi23WbsPfJnBbWmzU5oMlOBoiuopFqR/FgAnJanE/7ynllVSa9qf3G7fny76ziCqwGEdD2UYF+oE9ZPT6n3+29bHEyzHBYNpHClGp4NVjpP98/OtLkKjnNmXYPp96KzlugG1nY/Mlx025uSJIi6bHFFxf+jo4X9oszoYxUzDVqLt7hYWmjtmqv/QKICobfpYCEIX6tfqNThiIqoRdyguNO2rSabqTobmdAfxRC0AsLskoTZOx0kJc7qu32o722mxreFMG4n4sCL/F+e8TEjpxiiS5mUc3QFPcrPra0/UBqCg+55Fv0gv5fbdKQYB3eN4P1iOHGtiY0o3h++Ii0UeIULMCFX9KP3Oa7jKbba7HMOQLYjMkcL6Po6HMoOKEcx8CddoPsqPpsBlSkehTDoDsxcgSCDSBAJ6GMomoB86QUes3wclWxxIvxwSPaR+NCl49RvomwL++CPXcBsD2+Yq8rBNrKkb9aNW/J8uADd+QhUqS+RVIyiRtHmdDnykSTDWUgbCyoBJ+tIkAFXNDLsTfT2zdrOVyQL4y6ZwyVtHUaQUIoLLEN9O4vmRfzTSEFiOWatfZe5sJg5u5d1lCCkoi32IGorqitQ8nqd5W00KDAA4CX5Il/kv2hTNkTdF3hIAmdH5Hxv6iQ1WCJb7J9PmSfTWZ0hpBLRsBTcLesu33/WgfKeBgDdZRq/rlXawamvMaBJoaAdSHg5q6E408DQIaBDQItC4E/g/tiKYYaMR50wAAAABJRU5ErkJggg==" alt="" />
aaarticlea/png;base64,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" alt="" />
六、修复 bug
让我们解决 nickname 重复的问题。
有两个地方目前还没有处理重复。
首先是Flask-Login的after_login处理,这个方法将在用户成功登陆到系统后调用,我们需要创建一个新的User实例。
这里是修复后的受影响的代码块(文件 app/views.py):
if user is None:
nickname = resp.nickname
if nickname is None or nickname == "":
nickname = resp.email.split('@')[0]
nickname = User.make_unique_nickname(nickname)
user = User(nickname = nickname, email = resp.email)
db.session.add(user)
db.session.commit()
我们解决这个问题的方法是让User类选择一个唯一的名字给我们,这就是新的 make_unique_nickname 方法所做的(文件 app/models.py):
class User(db.Model):
# ...
@staticmethod
def make_unique_nickname(nickname):
if User.query.filter_by(nickname=nickname).first() is None:
return nickname
version = 2
while True:
new_nickname = nickname + str(version)
if User.query.filter_by(nickname=new_nickname).first() is None:
break
version += 1
return new_nickname
# ...
这个方法简单的添加一个计数器来生成一个唯一的昵称名。例如,如果用户名“miguel”存在,这个方法将建议你使用“miguel2”,但是如果它也存在就会生成“miguel3”···。注意我们把这个方法设定为静态方法,因为这个操作不适用于任何类的实例。
第二个存在重复昵称问题的地方就是编辑用户信息的视图函数。这个稍微有些难处理,因为这是用户自己选择的昵称。正确的做法就是不接受一个重复的昵称,让用户重新输入一个。我们通过添加form表单验证来解决这个问题,如果用户输入一个无效的昵称,将会得到一个字段验证失败信息,用户将会返回到编辑用户信息页。为了添加验证,我们只需覆盖表单的 validate 方法(文件 app/forms.py):
from app.models import User class EditForm(Form):
nickname = TextField('nickname', validators=[DataRequired()])
about_me = TextAreaField('about_me', validators=[Length(min=0, max=140)]) def __init__(self, original_nickname, *args, **kwargs):
Form.__init__(self, *args, **kwargs)
self.original_nickname = original_nickname def validate(self):
if not Form.validate(self):
return False
if self.nickname.data == self.original_nickname:
return True
user = User.query.filter_by(nickname=self.nickname.data).first()
if user != None:
self.nickname.errors.append('This nickname is already in use. Please choose another one.')
return False
return True
表单的构造函数增加了一个新的参数original_nickname,验证方法validate使用这个参数来判断昵称是否修改了,如果没有修改就直接返回它,如果已经修改了,方法会确认下新的昵称在数据库是否已经存在。
接下来我们在视图函数中添加新的构造器参数:
@app.route('/edit', methods=['GET', 'POST'])
@login_required
def edit():
form = EditForm(g.user.nickname)
# ...
我们还必须在表单模板中使得字段错误信息会显示(文件 app/templates/edit.html):
<td>Your nickname:</td>
<td>
{{ form.nickname(size=24) }}
{% for error in form.errors.nickname %}
<br><span style="color: red;">[{{ error }}]</span>
{% endfor %}
</td>
现在这个bug已经修复了,阻止了重复数据的出现···除非这些验证方法不能正常工作了。在两个或者多个线程/进程并行存取数据库时,这仍然存在一个潜在的问题,但这些都是以后我们文章讨论的主题。
在这里你可以尝试选择一个重复的名称来看看表单如何处理这些错误的。
七、单元测试框架
随着应用程序的规模变得越大就越难保证代码的修改不会影响到现有的功能。
传统的方法防止回归是一个很好的方式,你通过编写单元测试来测试应用程序所有不同功能,每一个测试集中于一个点来验证结果是否和预期的一致。测试程序通过 定期的执行来确认应用程序是否在正常工作。当测试覆盖率变大时,你就可以自信的修改和添加新功能,只需通过测试程序来验证下是否影响到了应用程序现有功 能。
现在我们使用python的unittest测试组件来创建个简单的测试框架 (tests.py):
#!flask/bin/python
import os
import unittest from config import basedir
from app import app, db
from app.models import User 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_avatar(self):
u = User(nickname='john', email='john@example.com')
avatar = u.avatar(128)
expected = 'http://gravatar.duoshuo.com/avatar/d4c74594d841139328695756648b6bd6'
assert avatar[0:len(expected)] == expected def test_make_unique_nickname(self):
u = User(nickname='john', email='john@example.com')
db.session.add(u)
db.session.commit()
nickname = User.make_unique_nickname('john')
assert nickname != 'john'
u = User(nickname=nickname, email='susan@example.com')
db.session.add(u)
db.session.commit()
nickname2 = User.make_unique_nickname('john')
assert nickname2 != 'john'
assert nickname2 != nickname if __name__ == '__main__':
unittest.main()
Flask学习之七 单元测试的更多相关文章
- Flask 备注一(单元测试,Debugger, Logger)
Flask 备注一(单元测试,Debugger, Logger) Flask是一个使用python开发Web程序的框架.依赖于Werkzeug提供完整的WSGI支持,以及Jinja2提供templat ...
- [ZHUAN]Flask学习记录之Flask-SQLAlchemy
From: http://www.cnblogs.com/agmcs/p/4445583.html 各种查询方式:http://www.360doc.com/content/12/0608/11/93 ...
- Flask 学习目录
Flask 学习目录 Flask 的学习过程是一个了解如何从单个模块搭建一个 Web 框架的过程. Python.Flask 的安装和设置 简单的 Hello World 程序 使用 Jinjia2 ...
- Python Flask学习笔记之模板
Python Flask学习笔记之模板 Jinja2模板引擎 默认情况下,Flask在程序文件夹中的templates子文件夹中寻找模板.Flask提供的render_template函数把Jinja ...
- Python Flask学习笔记之Hello World
Python Flask学习笔记之Hello World 安装virtualenv,配置Flask开发环境 virtualenv 虚拟环境是Python解释器的一个私有副本,在这个环境中可以安装私有包 ...
- Flask学习-Wsgiref库
一.前言 前面在Flask学习-Flask基础之WSGI中提到了WerkZeug,我们知道,WerkZeug是一个支持WSGI协议的Server,其实还有很多其他支持WSGI协议的Server.htt ...
- Flask 学习篇二:学习Flask过程中的记录
Flask学习笔记: GitHub上面的Flask实践项目 https://github.com/SilentCC/FlaskWeb 1.Application and Request Context ...
- Flask 学习(四)静态文件
Flask 学习(四)静态文件 动态 web 应用也需要静态文件,一般是 CSS 和 JavaScript 文件.理想情况下你的服务器已经配置好提供静态文件的服务. 在开发过程中, Flask 也能做 ...
- Flask 学习(三)模板
Flask 学习(三)模板 Flask 为你配置 Jinja2 模板引擎.使用 render_template() 方法可以渲染模板,只需提供模板名称和需要作为参数传递给模板的变量就可简单执行. 至于 ...
随机推荐
- android的AIDL
一.AIDL的意义: AIDL全称是Android Interface Definition Language,是android接口定义语言.AIDL就是为了避免我们一遍遍的写一些 ...
- LINQ(语言集成查询)
LINQ,语言集成查询(Language Integrated Query)是一组用于c#和Visual Basic语言的扩展.它允许编写C#或者Visual Basic代码以查询数据库相同的方式操作 ...
- 使用Jedis操作Redis-使用Java语言在客户端操作---对key的操作
//添加String类型的模拟数据. jedis.set("mykey", "2"); jedis.set("mykey2", " ...
- 高效整洁CSS代码原则(上)
CSS学起来并不难,但在大型项目中,就变得难以管理,特别是不同的人在CSS书写风格上稍有不同,团队上就更加难以沟通,为此总结了一些如何实现高效整洁的CSS代码原则: 1. 使用Reset但并非全局Re ...
- Hibernate QBC 简单收集
Hibernate QBC 介绍: QBC(Query By Criteria)通过 API 来检索对象 主要由 Criteria 接口.Criterion 接口和 Exception 类组成,支持在 ...
- 虚幻UE4的后处理特效介绍 http://www.52vr.com/thread-31215-1-1.html
转载 虚幻UE4提供了后处理特效的功能,可以实现景深,光溢出,色调调整,饱和度等等.要使用虚幻4的后处理,就一定要用到PostProcessVolumn,这是一种特殊的体积,可以放置在场景中的任何位置 ...
- Bnd教程(1):如何用命令行安装bnd
1. 如果你用的是MacOS,请运行: brew install bnd 然后运行bnd version看是否安装成功: $ bnd version 4.0.0.201801191620-SNAPSH ...
- 自定义注解--Annotation
Annotation 概念:注解 原理 是一种接口,通过反射机制中的相关API来访问annotation信息 常见的标准Annotation @Override 方法重写 @Deprecated ...
- oracle dbms_repcat_admin能带来什么安全隐患
如果一个用户能执行dbms_repcat_admin包,将获得极大的系统权限. 以下情况可能获得该包的执行权限: 1.在sys下grant execute on dbms_repcat_admin t ...
- initwithcoder和 initwithframe 区别?
每个ios开发者对loadView和viewDidLoad肯定都很熟悉,虽然这两个函数使用上真的是非常简单,但是和类似的initWithNibName/awakeFromNib/initWithCod ...