Django 过滤 MySQL 警告
在你们把我指向这里和这里之前,我想说我的情况有点不同。最近我在把项目转到生产服务器后,开始遇到一个著名的错误。
django/db/backends/mysql/base.py:86: 警告:第1行的' slug '列数据被截断
我解决了问题后,第一件事就是开始在网上搜索这个错误。为了修复它,我把模型的最大长度调整为128,然后更新了SQL表以匹配这个长度。但是问题依然存在……我有点自信自己确实解决了问题,于是决定开始过滤这些警告。所以在我的脚本顶部加了这个。
# Get rid of the MySQLdb warnings
import warnings
import MySQLdb
with warnings.catch_warnings():
warnings.filterwarnings("ignore", category=MySQLdb.Warning)
然后我高高兴兴地把这个推到生产环境。结果你猜怎么着——问题还是没解决。那么接下来该怎么办呢?我开始怀疑自己是否真的解决了问题,但仔细检查后发现所有的slug列都是128个字符长。此外,我还对sluggify进行了包装,如果长度超过128就报错,但还是没用。所以我有两个问题:
我怎么才能找到是哪个操作引发了这个警告?也就是说,我的代码中哪里出现了这个警告?
我该如何真正过滤掉这些警告?我的修复方法没有效果?这真的是MySQLdb的警告,还是django.db.mysql.base的警告?
谢谢,祝大家Django编程愉快!
对于那些对结构有疑问的人……
CREATE TABLE `people_employee` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) DEFAULT NULL,
`email` varchar(75) DEFAULT NULL,
`location_id` varchar(100) DEFAULT NULL,
`jpeg` longtext,
`first_name` varchar(100) DEFAULT NULL,
`last_name` varchar(100) DEFAULT NULL,
`maildomain` varchar(32) DEFAULT NULL,
`mailserver` varchar(32) DEFAULT NULL,
`mailfile` varchar(64) DEFAULT NULL,
`contractor` tinyint(1) NOT NULL,
`temporary` tinyint(1) NOT NULL,
`formal_name` varchar(100) DEFAULT NULL,
`nickname` varchar(32) DEFAULT NULL,
`cell_phone` varchar(32) DEFAULT NULL,
`office_phone` varchar(32) DEFAULT NULL,
`other_phone` varchar(32) DEFAULT NULL,
`fax` varchar(32) DEFAULT NULL,
`assistant_id` int(11) DEFAULT NULL,
`supervisor_id` int(11) DEFAULT NULL,
`is_supervisor` tinyint(1) NOT NULL,
`department_id` varchar(100) DEFAULT NULL,
`division_id` varchar(100) DEFAULT NULL,
`section_id` varchar(100) DEFAULT NULL,
`job_classification_id` varchar(100) DEFAULT NULL,
`functional_area_id` varchar(100) DEFAULT NULL,
`position_id` varchar(100) DEFAULT NULL,
`notes_url` varchar(200) DEFAULT NULL,
`ldap_active` tinyint(1) NOT NULL,
`notes_active` tinyint(1) NOT NULL,
`created_at` datetime NOT NULL,
`last_update` datetime NOT NULL,
`is_active` tinyint(1) NOT NULL,
`site_id` int(11) NOT NULL,
`slug` varchar(128) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `slug` (`slug`),
KEY `people_employee_location_id` (`location_id`),
KEY `people_employee_assistant_id` (`assistant_id`),
KEY `people_employee_supervisor_id` (`supervisor_id`),
KEY `people_employee_department_id` (`department_id`),
KEY `people_employee_division_id` (`division_id`),
KEY `people_employee_section_id` (`section_id`),
KEY `people_employee_job_classification_id` (`job_classification_id`),
KEY `people_employee_functional_area_id` (`functional_area_id`),
KEY `people_employee_position_id` (`position_id`),
KEY `people_employee_site_id` (`site_id`)
) ENGINE=MyISAM AUTO_INCREMENT=1429 DEFAULT CHARSET=latin1;
还有相关的models.py。
slug = models.SlugField(max_length=128, editable=False, unique=True)
希望这能帮到你……
3 个回答
我遇到过类似的问题,不过是不同的模块(比如在导入floppyforms时找不到gis模块)。这些模块对我们的应用来说其实并不重要,所以我在设置文件里加了以下内容:
# Set up warnings filters
# (action, re-pattern, category) See warnings module docu. Python std lib 28.6)
WARNINGS_FILTERS = [
("ignore", "Unable to import floppyforms.gis, geometry widgets not available", UserWarning),
("ignore", "There are known rendering problems with Cairo <= 1.14.0", UserWarning),
("ignore", "@font-face support needs Pango >= 1.38", UserWarning),
]
import warnings
for wf in WARNINGS_FILTERS:
warnings.filterwarnings(*wf)
像这样遍历一个列表,让我在将来添加、修改或删除警告时变得更简单、更清晰。
注意:每次第一次导入设置时会有一点点“额外”的处理(比如新工作进程启动),不过,如果append=False
,也就是默认设置,警告模块会替换掉已有的警告。换句话说,多次导入设置不会导致注册多个相同的过滤器。
我会编辑我项目中的 settings.py
文件,这样可以让整个 Django 项目都能实现这种行为。否则,我也可以简单地在我想要这种行为的脚本部分加入相关代码。
将 MySQL 警告当作错误处理:
import warnings, MySQLdb
warnings.filterwarnings('error', category=MySQLdb.Warning)
如果你想忽略这些警告而不是抛出错误,可以把 "error"
替换成 "ignore"
。
你可以像这样在一个 try-except 块中处理它们:
try:
# a MySQL DB operation that raises a warning
# for example: a data truncated warning
except Warning as a_warning:
# do something here
首先,我强烈建议不要像这样过滤警告:这个错误是MySQL生成的,绝对意味着你正在丢失数据。
第一步应该使用MySQL的描述命令,确保你的数据库列的定义大小和你预期的一样:如果你改变了列的长度,Django是没有支持数据库迁移的,所以如果你的slug字段曾经比现在短,你需要手动修改表格来设置新的长度:
mysql> DESCRIBE my_table slug;
+-------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+-------+
| slug | varchar(255) | NO | UNI | NULL | |
+-------+--------------+------+-----+---------+-------+
1 row in set (0.00 sec)
如果那个字段不是你预期的,你可以通过更新列的长度来简单解决这个问题:
mysql> ALTER TABLE my_table MODIFY slug VARCHAR(255) NOT NULL;
如果你允许在slug中使用Unicode字符,这也可能是原因,因为你上面的转储似乎使用的是latin1
字符集,而不是UTF-8——一个UTF-8字符可以占用最多4个字节的数据,这意味着一个短到17个字节的值可能会溢出VARCHAR(64)
。
接下来的调试步骤是对你调用的过滤警告进行简单的变更,以找出错误发生的确切位置:
warnings.simplefilter("error", category=MySQLdb.Warning)
这将使警告变得致命,这会停止你的程序,但更重要的是,它还会生成一个堆栈跟踪。通过这样的方式,你会看到下面的输出:
#!/usr/bin/env python
import warnings
def foo():
warnings.warn("uhoh")
def bar():
foo()
def main():
warnings.simplefilter("error", UserWarning)
bar()
if __name__ == "__main__":
main()
没有simplefilter调用的情况:
cadams@Io:~ $ python test_warnings.py
test_warnings.py:5: UserWarning: uhoh
warnings.warn("uhoh")
有simplefilter调用的情况:
cadams@Io:~ $ python test_warnings.py
Traceback (most recent call last):
File "test_warnings.py", line 15, in <module>
main()
File "test_warnings.py", line 12, in main
bar()
File "test_warnings.py", line 8, in bar
foo()
File "test_warnings.py", line 5, in foo
warnings.warn("uhoh")
UserWarning: uhoh