Пользовательское поле Django: запускать to_python () только для значений из БД? - PullRequest
8 голосов
/ 22 декабря 2010

Как я могу убедиться, что метод * to_python () * моего настраиваемого поля вызывается только тогда, когда данные в поле были загружены из БД?

Я пытаюсьиспользуйте настраиваемое поле для обработки кодирования / декодирования Base64 одного свойства модели.Казалось, что все работает правильно, пока я не создал экземпляр нового экземпляра модели и не установил это свойство с его значением в виде открытого текста ... В этот момент Django попытался декодировать поле, но потерпел неудачу, потому что это был открытый текст.

Привлекательность реализации Custom Field заключалась в том, что я думал, что смогу обработать 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 ]

3 голосов
/ 09 августа 2011

(от 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)

Похоже, вам нужно иметь возможность проверить, является ли это экземпляром, и проблема в том, что они одного типа (строка против строки в кодировке b64). всегда:

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

или

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

или некоторая такая кодировка.

РЕДАКТИРОВАТЬ: - Я смотрел на https://github.com/django-extensions/django-extensions/blob/f278a9d91501933c7d51dffc2ec30341a1872a18/django_extensions/db/fields/encrypted.py и думал, что вы могли бы сделать что-то подобное.

3 голосов
/ 06 октября 2011

У меня точно такая же проблема, но с данными 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 над заданным пользователем значением или значением из БД.

0 голосов
/ 22 декабря 2010

Вы получаете TypeError только когда впервые присваиваете значение полю? Вы можете просто написать попытку / кроме этого:

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

Это не самое чистое решение, но вы можете попробовать это и посмотреть, позволяет ли оно работать с полем так, как вы ожидаете.

...