tutorial - django unique_together
Campo personalizado de Django: ¿solo ejecuta to_python() en los valores de DB? (3)
¿Obtiene el TypeError
cuando asigna un valor al campo por primera vez? Podrías simplemente 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ías intentarlo y ver si te permite trabajar con el campo de la manera que estás esperando.
¿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?
Estoy tratando de usar un campo personalizado para manejar la codificación / decodificació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 de 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ó saber al respecto. ¿Qué estoy haciendo mal?
(NOTA: Esto es solo un ejemplo para ilustrar mi problema, no necesito consejos sobre cómo debería o no debería estar usando Base64 Encoding)
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 caparazón 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
Esperaba poder 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?
(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 necesitas poder probar si se trata de una instancia y el problema es que son del mismo tipo (cadena versus cadena codificada en b64). Así que, a menos que puedas desviar la diferencia, te sugiero que siempre lo hagas:
Person(internal_id="foo".encode(''base64'', ''strict''))
o
Person(internal_id=base64.b64encod("foo"))
o alguna codificación de ese tipo.
EDITAR: - Estaba viendo https://github.com/django-extensions/django-extensions/blob/f278a9d91501933c7d51dffc2ec30341a1872a18/django_extensions/db/fields/encrypted.py y pensé que podría hacer algo similar.
Tengo exactamente el mismo problema, pero con 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, se trata de datos JSON, por lo que prefijo "json:", y así 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 pueda esperar un comportamiento constante, o que no se pueda decir si to_python está operando en un valor establecido por el usuario o en un valor del DB.