2010-12-22 6 views
8

¿Cómo puedo asegurarme de que el método * to_python() * de mi campo personalizado solo se invoca cuando los datos en el campo se han cargado desde el DB?Campo personalizado de Django: ¿solo ejecuta to_python() en los valores de DB?

Estoy tratando de usar un campo personalizado para manejar la Codificación/Descodificación Base64 de una sola propiedad de modelo. Todo parecía funcionar correctamente hasta que instalé una nueva instancia del modelo y establecí esta propiedad con su valor de texto sin formato ... en ese punto, Django intentó decodificar el campo pero falló porque era texto sin formato.

El atractivo de la implementación del campo personalizado fue que pensé que podía manejar el 100% de la lógica de codificación/decodificación allí, de modo que ninguna otra parte de mi código alguna vez necesitó saberlo. ¿Qué estoy haciendo mal?

(NOTA: Esto es sólo un ejemplo para ilustrar mi problema, yo no necesito consejos sobre cómo debería o no debería estar utilizando la codificación Base64)

def encode(value): 
    return base64.b64encode(value) 

def decode(value): 
    return base64.b64decode(value) 


class EncodedField(models.CharField): 
    __metaclass__ = models.SubfieldBase 

    def __init__(self, max_length, *args, **kwargs): 
     super(EncodedField, self).__init__(*args, **kwargs) 

    def get_prep_value(self, value): 
     return encode(value) 

    def to_python(self, value): 
     return decode(value) 

class Person(models.Model): 
    internal_id = EncodedField(max_length=32) 

... y se rompe cuando hago esto en el shell interactivo. ¿Por qué está llamando a topypy() aquí?

>>> from myapp.models import * 
>>> Person(internal_id="foo") 
Traceback (most recent call last): 
    File "<console>", line 1, in <module> 
    File "/usr/local/lib/python2.6/dist-packages/django/db/models/base.py", line 330, in __init__ 
    setattr(self, field.attname, val) 
    File "/usr/local/lib/python2.6/dist-packages/django/db/models/fields/subclassing.py", line 98, in __set__ 
    obj.__dict__[self.field.name] = self.field.to_python(value) 
    File "../myapp/models.py", line 87, in to_python 
    return decode(value) 
    File "../myapp/models.py", line 74, in decode 
    return base64.b64decode(value) 
    File "/usr/lib/python2.6/base64.py", line 76, in b64decode 
    raise TypeError(msg) 
TypeError: Incorrect padding 

que había esperado que sería capaz de hacer algo como esto ...

>>> from myapp.models import * 
>>> obj = Person(internal_id="foo") 
>>> obj.internal_id 
'foo' 
>>> obj.save() 
>>> newObj = Person.objects.get(internal_id="foo") 
>>> newObj.internal_id 
'foo' 
>>> newObj.internal_id = "bar" 
>>> newObj.internal_id 
'bar' 
>>> newObj.save() 

... ¿qué estoy haciendo mal?

Respuesta

0

¿Obtiene el TypeError solo la primera vez que asigna un valor al campo? Se podía escribir un try/excepto alrededor de él:

def to_python(self, value): 
    try: 
    return decode(value) 
    except TypeError: 
    return value 

No es la solución más limpia, pero podría intentar eso y ver si se le permite trabajar con el campo de la forma en que está esperando.

+0

Sí, eso se me ocurrió también ... Esperaba una solución más limpia. Soy bastante nuevo para Django en general, y específicamente esta es la primera vez que uso un campo personalizado, así que mi suposición es que o me falta algo en la declaración de mi campo personalizado, o que hay un completamente diferente, más apropiado forma de satisfacer mis necesidades –

+1

Resulta que esta fue en realidad la única solución que pude encontrar. Decepcionado, no hay una mejor manera de hacer esto. –

+4

Esto fallará si la información que almacena es válida como entrada Base64 (por ejemplo, 'b'abcd''). Entonces no se genera 'TypeError', y el resultado es incorrecto. –

3

(de http://davidcramer.posterous.com/code/181/custom-fields-in-django.html
y https://docs.djangoproject.com/en/dev/howto/custom-model-fields/#converting-database-values-to-python-objects)

Parece que tiene que ser capaz de probar si se trata de una instancia y el problema con esto es que son del mismo tipo (cadena vs cadena B64 codificado) .Así a menos que pueda detirmine la diferencia que sugeriría asegurándose de que siempre:

Person(internal_id="foo".encode('base64', 'strict')) 

o

Person(internal_id=base64.b64encod("foo")) 

o alguna de esas codificaciones.

EDIT: Estaba mirando https://github.com/django-extensions/django-extensions/blob/f278a9d91501933c7d51dffc2ec30341a1872a18/django_extensions/db/fields/encrypted.py y pensé que podrías hacer algo similar.

+0

+1 para la cita de extensiones de banda: no se limita a los campos de Django, el problema puede surgir al escribir propiedades con el descriptor '__set__' /' __get__' métodos ... Ese truco de prefijo puede ser raro pero es bastante efectivo. – fish2000

3

Tengo exactamente el mismo problema, pero con los datos JSON. Quiero almacenar datos en la base de datos en formato JSON. Sin embargo, si intenta almacenar un objeto JSON ya serializado, se devolverá deserializado. Entonces, el problema es que lo que entra, no siempre es lo que sale. Especialmente si intenta almacenar un número como una cadena, se devolverá como un int o float, ya que es deserializado por to_python antes de ser almacenado.

La solución es simple, aunque no demasiado elegante. Simplemente asegúrese de almacenar el "tipo" de datos junto con los datos, en mi caso, son datos JSON, por lo que prefijo "json:", y por lo tanto siempre se sabe si los datos provienen de la base de datos.

def get_prep_value(self, value): 
    if value is not None: 
     value = "json:"+json.dumps(value) 
    return value 
def to_python(self, value): 
    if type(value) in (str, unicode) and value.startswith("json:"): 
     value = value[5:] 
     try: 
      return json.loads(value) 
     except: 
      # If an error was raised, just return the plain value 
      return value 
    else: 
     return value 

Dicho esto, es molesto que no se puede esperar un comportamiento consistente, o que no se puede decir si to_python está funcionando con un valor fijado por el usuario o un valor de la BD.

+1

¡maldición! esta es la única solución que estaba buscando. Resulta que 'to_python()' es endémico en el código fuente de django y se llama en todas partes como un desastre. ¿De qué manera llamar a la misma función en form-> db y db-> form? ¿Quién diseñó esto? – est

Cuestiones relacionadas