Django自定义字段:只对DB上的值运行to_python()?

时间:2010-12-22 14:28:33

标签: django field django-models

如何确保仅在从数据库加载字段中的数据时调用自定义字段的* 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)

...当我在交互式shell中执行此操作时,它会中断。为什么在这里调用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 个答案:

答案 0 :(得分:3)

(来自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

似乎你需要能够测试它是否是一个实例,问题是它们是相同的类型(字符串vs 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并认为你可以做类似的事情。

答案 1 :(得分:3)

我有完全相同的问题,但使用JSON数据。我想以JSON格式将数据存储在数据库中。但是,如果您尝试存储已经序列化的JSON对象,则将返回反序列化的对象。所以问题是,进来的东西并不总是出来的。特别是如果您尝试将数字存储为字符串,它将作为int或float返回,因为它在被存储之前由to_python反序列化。

解决方案很简单,虽然不太优雅。只需确保存储数据的“类型”和数据,在我的例子中,它是JSON数据,所以我用“json:”作为前缀,因此你总是知道数据是否来自数据库。 / p>

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是在用户设定值还是DB的值上运行。

答案 2 :(得分:0)

首次为字段分配值时,您是否只获得TypeError?你可以在它周围写一个try / except:

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

这不是最干净的解决方案,但您可以尝试一下,看看它是否能让您按照预期的方式使用该字段。