to_python() 从未被调用(即使在 __metaclass__ = models.SubfieldBase 的情况下)

1 投票
1 回答
547 浏览
提问于 2025-04-16 13:46

不久前,在学习Python和Django的过程中,我决定为MySQL的BIT列类型写一个自定义的模型字段。不过,我遇到了一些问题。

项目情况:这个项目只有一个“主”应用。

“主”应用:里面包含了通过“python manage.py startapp”创建的所有标准文件,还有一个extfields.py文件。

extfields.py的内容如下:

from django.db import models
import re
import bitstring

class BitField(models.Field):
    description = 'A class representing a field of type "BIT" (MySQL-specific)'
    __metaclass__ = models.SubfieldBase

    def __init__(self, *args, **kwargs):
        bf_size = int(kwargs.pop('bitfield_size', 0))

        assert bf_size > 0, '%ss must have a positive bitfield_size' % self.__class__.__name__
        self._bf_size = bf_size

        super(BitField, self).__init__(*args, **kwargs)


    def db_type(self):
        return 'BIT(%d)' % self._bf_size


    def to_python(self, value):
        print('to_python starts')

        if isinstance(value, bitstring.BitArray):
            return value    

        value = str(value)

        regex = re.compile('^[01]{%d}$' % self._bf_size)

        assert regex.search(value) == True, 'The value must be a bit string.'

        print('to_python ends')

        return bitstring.BitArray(bin=value)


    def get_db_prep_value(self, value):
        return value.bin

models.py的内容:

from django.db import models
import extfields

class MessageManager(models.Manager):
    """
    This manager is solely for the Message model. We need to alter the default
    QuerySet so that we'll get the correct values for the attributes bit field   
    """
    def get_query_set(self):
        return super(MessageManager, self).get_query_set().defer(
            'attributes'
        ).extra(
            select={'attributes': 'BIN(attributes)'}
        )


class Message(models.Model):
    attributes = extfields.BitField(bitfield_size=15)

    objects = MessageManager()

当我使用python命令行(通过python manage.py shell)时,得到的结果是:

>>> from main import models
>>> m = models.Message.objects.get(pk=1)
>>> m
<Message_Deferred_attributes: Message_Deferred_attributes object>
>>> m.attributes
u'1110001110'
>>> type(m.attributes)
<type 'unicode'>
>>> m.attributes = '1312312'
>>> m.attributes
'1312312'

如你所见,m.attributes是一个普通字符串,而不是bitstring.BitArray的实例。

有人能告诉我我哪里出错了吗?

我在Ubuntu 10.04上使用的是Python 2.6.5。我导入的bitstring模块是这个:http://code.google.com/p/python-bitstring/。Python-django包的版本是1.1.1-2ubuntu1.3。

编辑(回应emulbreh的评论):

现在我的to_python()定义是这样的:

def to_python(self, value):
        print 'to_python'

        if isinstance(value, bitstring.BitArray):
            return value    

        print type(value)    
        value = str(value)
        print'\n'
        print value
        print '\n'
        print type(value)        

        regex = re.compile('^[01]{%d}$' % self._bf_size)

        assert regex.search(value) == True, 'The value must be a bit string.'
        value = bitstring.BitArray(bin=value)
        print '\n'
        print type(value)
        print 'End of to_python'

        return value

控制台输出是:

之后,抛出了一个AssertionError错误。

1 个回答

1

你在使用QuerySet的时候,不需要做什么特别的事情来支持自定义字段。现在你是通过defer()来延迟加载你的字段,然后再添加一个原始的额外属性(select=),这个属性的名字恰好和你的字段一样。如果你只是去掉自定义管理器(或者去掉.defer().extra()的调用),那就没问题了。

撰写回答