解决django使用logging时“character mapping must return integer, None or unicode”错误

今天调试Django项目时候,使用Logging记录异常并发送邮件给网站管理人员,测试时候始终报错:

Traceback (most recent call last):
  File "/usr/lib64/python2.7/wsgiref/handlers.py", line 85, in run
    self.result = application(self.environ, self.start_response)
  File "/home/xsy/.virtualenvs/dz_pro/lib/python2.7/site-packages/django/core/handlers/wsgi.py", line 177, in __call__
    response = self.get_response(request)
  File "/home/xsy/.virtualenvs/dz_pro/lib/python2.7/site-packages/django/core/handlers/base.py", line 230, in get_response
    response = self.handle_uncaught_exception(request, resolver, sys.exc_info())
  File "/home/xsy/.virtualenvs/dz_pro/lib/python2.7/site-packages/django/core/handlers/base.py", line 284, in handle_uncaught_exception
    'request': request
  File "/usr/lib64/python2.7/logging/__init__.py", line 1185, in error
    self._log(ERROR, msg, args, **kwargs)
  File "/usr/lib64/python2.7/logging/__init__.py", line 1278, in _log
    self.handle(record)
  File "/usr/lib64/python2.7/logging/__init__.py", line 1288, in handle
    self.callHandlers(record)
  File "/usr/lib64/python2.7/logging/__init__.py", line 1328, in callHandlers
    hdlr.handle(record)
  File "/usr/lib64/python2.7/logging/__init__.py", line 751, in handle
    self.emit(record)
  File "/home/xsy/.virtualenvs/dz_pro/lib/python2.7/site-packages/django/utils/log.py", line 117, in emit
    self.send_mail(subject, message, fail_silently=True, html_message=html_message)
  File "/home/xsy/.virtualenvs/dz_pro/lib/python2.7/site-packages/django/utils/log.py", line 120, in send_mail
    mail.mail_admins(subject, message, *args, connection=self.connection(), **kwargs)
  File "/home/xsy/.virtualenvs/dz_pro/lib/python2.7/site-packages/django/core/mail/__init__.py", line 97, in mail_admins
    mail.send(fail_silently=fail_silently)
  File "/home/xsy/.virtualenvs/dz_pro/lib/python2.7/site-packages/django/core/mail/message.py", line 292, in send
    return self.get_connection(fail_silently).send_messages([self])
  File "/home/xsy/.virtualenvs/dz_pro/lib/python2.7/site-packages/django/core/mail/backends/smtp.py", line 100, in send_messages
    new_conn_created = self.open()
  File "/home/xsy/.virtualenvs/dz_pro/lib/python2.7/site-packages/django/core/mail/backends/smtp.py", line 67, in open
    self.connection.login(self.username, self.password)
  File "/usr/lib64/python2.7/smtplib.py", line 607, in login
    (code, resp) = self.docmd(encode_cram_md5(resp, user, password))
  File "/usr/lib64/python2.7/smtplib.py", line 571, in encode_cram_md5
    response = user + " " + hmac.HMAC(password, challenge).hexdigest()
  File "/usr/lib64/python2.7/hmac.py", line 78, in __init__
    self.outer.update(key.translate(trans_5C))
TypeError: character mapping must return integer, None or unicode

根据错误提示,追进了hmac.py中看了源码,发现key其实就是一个字符串,是配置在settings中的EMAIL_HOST_USER以及EMAIL_HOST_PASSWORD,而translate函数则是根据给出的参数表将字符串进行映射加密的,而且是最简单的凯撒加密法。

这里看了半天也看不出问题在哪,纠结了好久后猛然发现问题的关键出自settings.py:

from __future__ import absolute_import, unicode_literals

...

EMAIL_HOST = 'mail.xxx.com'
EMAIL_HOST_USER = 'xxxx'
EMAIL_HOST_PASSWORD = b'xxxx' # or str('xxx')
...

就是因为引入了unicode_literals,默认字符都变成了unicode而不是py2的str了(如果你还没被py2中的str和unicode坑过,我只能恭喜你了)所以如果想使用str类型则需要使用’b’显示声明或者使用str函数进行转换。

这个__future__本意是让py2能够使用一些py3中的新特性,方便进行版本迁移的。比如在py2中默认除法是“地板除”,即只保留整数位;而py3中则变成了“精确除”,这种情况下我们可以引用division来保持语法一至:

from __future__ import division

print '10 / 3 =', 10 / 3
print '10.0 / 3 =', 10.0 / 3
print '10 // 3 =', 10 // 3

结果:

10 / 3 = 3.33333333333
10.0 / 3 = 3.33333333333
10 // 3 = 3

再比如absolute_import则是禁用隐式相对引用(implicit relative import)的,因为官方不推荐这种引用方式了已经。

最后打个广告:cookiecutter-simple-django-cn 是我写的一个生成django基础框架的程序,参考了cookiecutter-django移除修改了一些国内不好用的选项,欢迎各位试用并提供宝贵意见~