Пользовательское поле Django: запускать только в python () по значениям из БД?
как я могу гарантировать, что метод *to_python()* моего настраиваемого поля вызывается только тогда, когда данные в поле загружены из БД?
Я пытаюсь использовать настраиваемое поле для обработки кодирования/декодирования Base64 одного свойства модели. Казалось, все работает правильно, пока я не создал новый экземпляр модели и не установил это свойство с его значением открытого текста...в этот момент Django попытался декодировать поле, но не смог, потому что это было открытый текст.
прелесть реализации настраиваемого поля заключалась в том, что я думал, что могу обрабатывать 100% логики кодирования/декодирования там, так что никакая другая часть моего кода никогда не должна была знать об этом. Что я делаю не так?
(Примечание: это просто пример, чтобы проиллюстрировать мою проблему, мне не нужен совет о том, как я должен или не должен использовать кодировку 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)
...и он ломается, когда я делаю это в интерактивной оболочке. Зачем быть он зовет to_python() здесь?
>>> 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
Я ожидал, что смогу сделать что-то подобное...
>>> 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()
...что я делаю не так?
3 ответов
вы получаете только TypeError
когда вы впервые назначаете значение полю? Вы можете просто написать try / except вокруг него:
def to_python(self, value):
try:
return decode(value)
except TypeError:
return value
это не самое чистое решение, но вы можете попробовать это и посмотреть, позволяет ли оно работать с полем так, как вы ожидаете.
(от http://davidcramer.posterous.com/code/181/custom-fields-in-django.html
и https://docs.djangoproject.com/en/dev/howto/custom-model-fields/#converting-database-values-to-python-objects)
Кажется, что вам нужно иметь возможность проверить, является ли это экземпляром, и проблема с этим заключается в том, что они одного типа (string vs b64 encoded string).Поэтому, если вы не можете detirmine разницу, я бы предложил убедиться, что вы всегда:
Person(internal_id="foo".encode('base64', 'strict'))
или
Person(internal_id=base64.b64encod("foo"))
или какая-то такая кодировка.
EDIT: - Я смотрел на https://github.com/django-extensions/django-extensions/blob/f278a9d91501933c7d51dffc2ec30341a1872a18/django_extensions/db/fields/encrypted.py и подумал, что вы могли бы сделать что-то подобное.
У меня такая же проблема, но с данными JSON. Я хочу хранить данные в базе данных в формате JSON. Однако если вы попытаетесь сохранить уже сериализованный объект JSON, он будет возвращен десериализации. Проблема в том, что то, что приходит, не всегда то, что выходит. Особенно, если вы пытаетесь сохранить число как строку, оно будет возвращено как int или float, так как оно десериализуется to_python перед сохранением.
решение простое, хотя и не слишком элегантно. Просто не забудьте сохранить "тип" данных вместе с данными, в моем случае это данные JSON, поэтому я префиксую его "json:", и, таким образом, вы всегда знаете, поступают ли данные из базы данных.
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
это, как говорится, раздражает, что вы не можете ожидать последовательного поведения или что вы не можете сказать, работает ли to_python на пользовательском значении или значении из БД.