在Django/sqlite中更改text_factory

6 投票
6 回答
5280 浏览
提问于 2025-04-15 22:12

我有一个使用sqlite数据库的django项目,这个数据库可以被外部工具写入数据。理论上,这些文本应该是UTF-8编码的,但有时候会出现编码错误。因为这些文本来自外部来源,所以我无法控制它们的编码。是的,我知道我可以在外部来源和数据库之间写一个“包装层”,但我不想这么做,尤其是因为数据库里已经有很多“坏数据”了。

在sqlite中,解决这个问题的方法是把text_factory改成类似这样的东西:

lambda x: unicode(x, "utf-8", "ignore")

但是,我不知道怎么告诉Django的模型驱动这个设置。

我遇到的错误是:

'无法将文本解码为UTF-8列'Text'中的文本' 在 /var/lib/python-support/python2.5/django/db/backends/sqlite3/base.py中执行

我需要以某种方式告诉sqlite驱动,不要尝试将文本解码为UTF-8(至少不要使用标准算法,而是需要使用我自己的安全变体)。

6 个回答

0

使用Django中的一种神奇的 str函数 来处理数据:

smart_str(s, encoding='utf-8', strings_only=False, errors='strict')

或者

smart_unicode(s, encoding='utf-8', strings_only=False, errors='strict')
2

受到Milla回答的启发,下面是一个猴子补丁的例子,它会在Django的SQLite连接中安装一个更宽容的文本处理方式。这个方法适合在你无法控制文本是如何添加到SQLite数据库时使用,因为这些文本可能不是UTF-8格式。当然,这里使用的编码方式可能不是最合适的,但至少你的应用程序不会崩溃。

import types
from django.db.backends.sqlite3.base import DatabaseWrapper

def to_unicode( s ):
    ''' Try a number of encodings in an attempt to convert the text to unicode. '''
    if isinstance( s, unicode ):
        return s
    if not isinstance( s, str ):
        return unicode(s)

    # Put the encodings you expect here in sequence.
    # Right-to-left charsets are not included in the following list.
    # Not all of these may be necessary - don't know.
    encodings = (
        'utf-8',
        'iso-8859-1', 'iso-8859-2', 'iso-8859-3',
        'iso-8859-4', 'iso-8859-5',
        'iso-8859-7', 'iso-8859-8', 'iso-8859-9',
        'iso-8859-10', 'iso-8859-11',
        'iso-8859-13', 'iso-8859-14', 'iso-8859-15',
        'windows-1250', 'windows-1251', 'windows-1252',
        'windows-1253', 'windows-1254', 'windows-1255',
        'windows-1257', 'windows-1258',
        'utf-8',     # Include utf8 again for the final exception.
    )
    for encoding in encodings:
        try:
            return unicode( s, encoding )
        except UnicodeDecodeError as e:
            pass
    raise e

if not hasattr(DatabaseWrapper, 'get_new_connection_is_patched'):
    _get_new_connection = DatabaseWrapper.get_new_connection
    def _get_new_connection_tolerant(self, conn_params):
        conn = _get_new_connection( self, conn_params )
        conn.text_factory = to_unicode
        return conn

    DatabaseWrapper.get_new_connection = types.MethodType( _get_new_connection_tolerant, None, DatabaseWrapper )
    DatabaseWrapper.get_new_connection_is_patched = True
9

在sqlite中,解决办法是把文本工厂改成类似这样的东西: lambda x: unicode(x, "utf-8", "ignore")

不过,我不知道怎么把这个告诉Django模型驱动。

你有没有在运行任何查询之前试过

from django.db import connection
connection.connection.text_factory = lambda x: unicode(x, "utf-8", "ignore")

撰写回答