Изменить текстовую фабрику в Django/sqlite
У меня есть проект Django, который использует базу данных sqlite, которая может быть записана с помощью внешнего инструмента. Предполагается, что текст будет UTF-8, но в некоторых случаях будут ошибки в кодировке. Текст взят из внешнего источника, поэтому я не могу контролировать кодировку. Да, я знаю, что мог бы написать "слой обертывания" между внешним источником и базой данных, но я предпочитаю не делать этого, тем более что база данных уже содержит много "плохих" данных.
Решение в sqlite состоит в том, чтобы изменить text_factory на что-то вроде:
lambda x: unicode(x, "utf-8", "ignore")
Тем не менее, я не знаю, как это сказать водителю модели Django.
Исключение, которое я получаю:
'Could not decode to UTF-8 column 'Text' with text'
in
/var/lib/python-support/python2.5/django/db/backends/sqlite3/base.py in execute
Каким-то образом мне нужно сказать драйверу sqlite не пытаться декодировать текст как UTF-8 (по крайней мере, не используя стандартный алгоритм, но он должен использовать мой отказоустойчивый вариант).
5 ответов
Решение в sqlite состоит в том, чтобы изменить text_factory на что-то вроде: lambda x: unicode(x, "utf-8", "ignore")
Тем не менее, я не знаю, как это сказать водителю модели Django.
Ты пытался
from django.db import connection
connection.connection.text_factory = lambda x: unicode(x, "utf-8", "ignore")
перед выполнением каких-либо запросов?
Вдохновленный ответом Миллы, рассмотрим следующий патч обезьяны, который устанавливает более толерантный text_factory в соединение 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
from django.db import connection
connection.cursor()
connection.connection.text_factory = lambda x: unicode(x, "utf-8", "ignore")
В моем конкретном случае мне нужно было установить connection.connection.text_factory = str
Подайте данные с помощью одной из магических функций от Django:
smart_str(s, encoding='utf-8', strings_only=False, errors='strict')
или же
smart_unicode(s, encoding='utf-8', strings_only=False, errors='strict')
Несовместимая версия Django. Сначала проверьте версию Django для решения этой ошибки. Я работал на Django==3.0.8, и он выдавал ошибку. Затем я запустил virtualenv, где у меня Django==3.1.2, и ошибка была удалена.
Кажется, что эта проблема возникает довольно часто и представляет большой интерес для многих людей. (Поскольку этот вопрос имеет более тысячи просмотров и довольно много голосов)
Итак, вот ответ, который я нашел для проблемы, которая представляется мне наиболее удобной:
Я проверил разъем django sqlite3 и добавил преобразование str непосредственно в get_new_connection(...)
функция:
def get_new_connection(self, conn_params):
conn = Database.connect(**conn_params)
conn.create_function("django_date_extract", 2, _sqlite_date_extract)
conn.create_function("django_date_trunc", 2, _sqlite_date_trunc)
conn.create_function("django_datetime_extract", 3, _sqlite_datetime_extract)
conn.create_function("django_datetime_trunc", 3, _sqlite_datetime_trunc)
conn.create_function("regexp", 2, _sqlite_regexp)
conn.create_function("django_format_dtdelta", 5, _sqlite_format_dtdelta)
conn.text_factory = str
return conn
Кажется, он работает так, как должен, и нет необходимости проверять проблему Unicode в каждом запросе в отдельности. Не следует ли считать это добавлением этого к коду django (?), Поскольку я бы не советовал никому на самом деле модифицировать его бэкэнд-код django вручную...