2010-04-30 9 views
6

Ho un progetto Django che utilizza un database SQLite che può essere scritto da uno strumento esterno. Il testo dovrebbe essere UTF-8, ma in alcuni casi ci saranno errori nella codifica. Il testo proviene da una fonte esterna, quindi non posso controllare la codifica. Sì, so che potrei scrivere un "layer wrapping" tra l'origine esterna e il database, ma preferisco non doverlo fare, soprattutto perché il database contiene già molti dati "cattivi".Cambia text_factory in Django/sqlite

La soluzione in SQLite è di cambiare il text_factory a qualcosa di simile: lambda x: unicode(x, "utf-8", "ignore")

Tuttavia, non so come dire al conducente modello di Django questo.

L'eccezione che ottengo è:

'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

In qualche modo ho bisogno di dire al driver SQLite non cercare di decodificare il testo UTF-8 (almeno non utilizzando l'algoritmo standard, ma ha bisogno usare la mia variante fail-safe).

risposta

9

La soluzione in SQLite è di cambiare il text_factory a qualcosa di simile: lambda x : unicode (x, "utf-8", "ignore")

Tuttavia, non so come dirlo al driver del modello Django.

Hai provato

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

prima di eseguire qualsiasi domanda?

+0

Grazie per l'input! Quanto sopra ha funzionato con alcune modifiche (in particolare, è necessario prima creare un cursore, altrimenti DatabaseWrapper.connection è None). Mi sono strappato i capelli per questo. – Krumelur

+0

@Krumelur puoi pubblicare la soluzione completa? – user985541

0

alimentare i dati con uno della magia str function da 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

mi dispiace se ti ho capito male, ma il problema è che il database contiene già dei dati 'cattivi', e voglio fare la conversione quando l'ho letto. La pagina a cui fai riferimento sembra occuparsi di inserire le stringhe nel database. Lo strumento che importa i dati non usa django, ma funziona con il modulo pysqlite. Consiste di codice legacy che sono riluttante a cambiare. Grazie per la risposta. – Krumelur

+0

hai provato a riempire il contenuto del DB "cattivo" nelle due funzioni precedenti? – maersu

+0

smart_str e smart_unicode possono servire allo scopo di filtrare se si stanno caricando i dati nel database o leggendo da esso. Farei entrambi per coerenza e integrità dei dati. –

0

Sembra che questo problema si presenti abbastanza spesso e che sia di grande interesse per molte persone. (Come questa domanda ha più di un migliaio di punti di vista e abbastanza alcuni upvotes)

Così qui è la risposta, che ho trovato per il problema, che mi appare come la più conveniente:

ho controllato il Django connettore sqlite3 e ha aggiunto la conversione str direttamente alla funzione 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 

sembra funzionare come dovrebbe e non c'è bisogno di controllare sul problema unicode in ogni richiesta individualmente. Non dovrebbe essere considerato di aggiungere questo codice Django (?), in quanto non vorrei suggerire a chiunque di modificare in realtà il suo codice di backend Django manualmente ...

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

Nel mio caso specifico ho bisogno di impostare la connessione .connection.text_factory = str

2

Ispirato alla risposta di Milla, si consideri la seguente patch di scimmia che installa una text_factory più tollerante nella connessione sqlite di django. Da utilizzare quando non è possibile controllare il modo in cui il testo viene aggiunto al database sqlite e potrebbe non essere in utf-8. Ovviamente, la codifica usata qui potrebbe non essere quella giusta, ma almeno la tua applicazione non andrà in crash.

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 dettaglio escluso. È necessario eseguire questa patch per accedere al database. Un buon posto potrebbe essere in "models.py". – EMS