2010-04-30 12 views
6

Tengo un proyecto Django que utiliza una base de datos SQLite que se puede escribir a por una herramienta externa. Se supone que el texto es UTF-8, pero en algunos casos habrá errores en la codificación. El texto proviene de una fuente externa, por lo que no puedo controlar la codificación. Sí, sé que podría escribir una "capa de envoltura" entre la fuente externa y la base de datos, pero prefiero no tener que hacerlo, especialmente porque la base de datos ya contiene muchos datos "malos".Cambio text_factory en Django/sqlite

La solución en SQLite es cambiar el text_factory a algo como: lambda x: unicode(x, "utf-8", "ignore")

Sin embargo, no sé cómo decirle al conductor de este modelo de Django.

La excepción que recibo es:

'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

De alguna manera tengo que decirle al conductor sqlite no intentar decodificar el texto como UTF-8 (al menos no usando el algoritmo estándar, pero es necesario usar mi variante a prueba de fallas).

Respuesta

9

La solución en SQLite es cambiar la text_factory a algo como: lambda x : Unicode (x, "UTF-8", "ignorar")

Sin embargo, no sé cómo decirle esto al controlador de modelo de Django.

Ha intentado

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

antes de ejecutar cualquier consulta?

+0

¡Gracias por la entrada! Lo anterior funcionó con algunas modificaciones (es decir, uno tiene que crear primero un cursor, de lo contrario, DatabaseWrapper.connection es None). He estado rompiendo mi cabello sobre esto. – Krumelur

+0

@Krumelur ¿puede publicar una solución completa? – user985541

0

alimentar a los datos con uno de la magia str function de Django:

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

o

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

Lo siento si te malinterpreto, pero el problema es que la base de datos ya contiene datos "incorrectos", y quiero hacer la conversión cuando la leo. La página a la que se refiere parece ocuparse de ingresar cadenas en la base de datos. La herramienta que importa datos no usa django, pero funciona con el módulo pysqlite. Consiste en un código heredado que soy reacio a cambiar. Gracias por la respuesta. – Krumelur

+0

¿ha intentado completar el contenido de la base de datos "incorrecta" en las dos funciones anteriores? – maersu

+0

smart_str y smart_unicode pueden servir para filtrar si está cargando los datos en la base de datos o leyendo de ellos. Haría ambas cosas por coherencia e integridad de datos. –

0

Parece que este problema se presenta con bastante frecuencia y que es de gran interés para muchas personas. (Como esta pregunta tiene más de un millar de puntos de vista y bastante algunas upvotes)

Así que aquí está la respuesta, que he encontrado para el problema, lo que me aparece como la más conveniente uno:

he comprobado el Django conector sqlite3 y ha añadido la conversión str directamente a la función 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 

parece que funciona como debería y que no se tiene que comprobar el problema unicode en cada solicitud de forma individual. ¿No debería ser considerado para añadir este código a Django (?), ya que no sugeriría que cualquiera pueda modificar en realidad su código de fondo Django manualmente ...

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

En mi caso específico que necesitaba para establecer la conexión .connection.text_factory = str

2

Inspirado por la respuesta de Milla, considere el siguiente mono-parche que instala una text_factory más tolerante en la conexión django sqlite. Para usar cuando no se puede controlar cómo se agrega el texto a la base de datos sqlite y puede que no esté en utf-8. Por supuesto, la codificación utilizada aquí puede no ser la correcta, pero al menos su aplicación no se bloqueará.

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 
+0

Un detalle omitido. Necesita hacer este parche accediendo a la base de datos. Un buen lugar podría estar en "models.py". – EMS