10

为什么在记录信息(django)时get_FOO_display()返回整数值?

我有一个模型字段,它使用一个选项来限制它的值。这工作正常,我让它在应用程序中的任何地方都可以工作,除了记录信息时,get_FOO_display() 方法返回底层整数值而不是人类可读的版本。

这是模型定义(删节):

THING_ROLE_MONSTER = 0
THING_ROLE_MUMMY = 1

ROLE_CHOICES = (
    (THING_ROLE_MONSTER, u'Monster'),
    (THING_ROLE_MUMMY, u'Mummy'),
)

# definition of property within model
class Thing(models.Model):
    ...
    role = models.IntegerField(
        'Role',
        default=0,
        choices=ROLE_CHOICES
    )

如果我在 (django) 交互式 shell 中运行它,它的行为完全符合您的预期:

>>> from frankenstein.core.models import Thing
>>> thing = Thing()
>>> thing.role = 0
>>> thing.get_role_display()
u'Monster'

但是,当我在字符串格式化/日志记录方案中使用完全相同的构造时,我遇到了问题:

logger.info('New thing: <b>%s</b>', thing.get_role_display())

返回:

New thing: <b>0</b> 

帮助!

[更新 1]

当我在交互式 shell 中运行日志记录时,我得到了正确的输出:

>>> from frankenstein.core.models import Thing
>>> import logging
>>> thing = Thing()
>>> thing.role = 0
>>> logging.info('hello %s', b.get_role_display())
INFO hello Monster

[更新 2] Django 内部结构

跟进下面@joao-oliveira 的回答,我深入研究了内部结构并发现了以下内容。

底层的 _get_FIELD_display 方法django.db.models如下所示:

def _get_FIELD_display(self, field):
    value = getattr(self, field.attname)
    return force_unicode(dict(field.flatchoices).get(value, value), strings_only=True)

如果我在代码中放置一个断点,然后运行 ​​ipdb 我可以看到我遇到了问题:

ipdb> thing.get_role_display()
u'1'
ipdb> thing._get_FIELD_display(thing._meta.get_field('role'))
u'1'

所以,修复并没有改变任何东西。如果我然后尝试手动运行_get_FIELD_display方法代码,我会得到:

ipdb> fld = thing._meta.get_field('role')
ipdb> fld.flatchoices
[(0, 'Monster'), (1, 'Mummy')]
ipdb> getattr(thing, fld.attname)
u'1'
ipdb> value = getattr(thing, fld.attname)
ipdb> dict(fld.flatchoices).get(value, value)
u'1'

这相当于说:

ipdb> {0: 'Monster', 1: 'Mummy'}.get(u'1', u'1')
u'1'

所以。我们遇到的问题是该方法使用字符串值u'1'在选择字典中查找相应的描述,但字典键是整数,而不是字符串。因此,我们永远不会得到匹配,而是默认值,它设置为现有值(字符串)。

如果我手动强制强制转换为 int,则代码按预期工作:

ipdb> dict(fld.flatchoices).get(int(value), value)
'Mummy'
ipdb> print 'w00t'

这一切都很好,但没有回答我最初的问题,即为什么 get_foo_display 方法在大多数情况下返回正确的值。在某些时候,必须将字符串 (u'1') 转换为正确的数据类型 (1)。

[更新 3] 答案

虽然荣誉奖必须归功于 Joao 的洞察力,但赏金要归功于 Josh,因为他指出了一个直截了当的事实,即我一开始就传递了错误的值。我把这归结为来自“强类型世界”的移民,这些事情不可能发生!

我在这里没有包含的代码是该对象是从 django 表单中初始化的,使用cleaned_data来自ChoiceField。问题在于 ChoiceField 的输出是字符串,而不是整数。我错过的一点是,在松散类型的语言中,可以使用字符串设置整数属性,并且不会发生任何坏事。

现在研究了这一点,我发现我应该使用TypedChoiceField,以确保输出cleaned_data始终是整数。

谢谢你们。

4

3 回答 3

20

如果这听起来屈尊俯就,我真的很抱歉,但是您是否 100% 确定您将值设置为整数 1 而不是字符串“1”?

我已经深入研究了内部结构并运行了一些测试,并且您遇到的问题有意义的唯一方法是将值设置为字符串。在这里查看我的简单测试:

>>> from flogger.models import TestUser
>>> t = TestUser()
>>> t.status = 1
>>> t.get_status_display()
u'Admin'
>>> t.status = '1'
>>> t.get_status_display()
u'1'

检查您的视图代码,或任何实际设置值的代码,并直接检查该字段的输出。

正如您从内部模型代码中粘贴的那样:

def _get_FIELD_display(self, field):
    value = getattr(self, field.attname)
    return force_unicode(dict(field.flatchoices).get(value, value), strings_only=True)

它只是获取字段的当前值,并索引到字典中,如果未找到查找,则返回属性的值。

我猜以前没有错误,因为该值在插入数据库之前被强制转换为整数。

编辑:

关于您提到python类型系统的更新。首先,您应该使用TypedChoiceField来确保表单验证您期望的类型。其次,python一种强类型语言,但是在准备数据库时IntegerField它会自己进行强制转换。int()

变量没有类型,但其中的值是类型。实际上,我很惊讶 theIntegerField也将字符串强制为 int 。在这里学习很好 - 首先检查基础知识!

于 2012-12-13T12:00:36.023 回答
2

没有尝试过您的代码,@like-it 也没有回答抱歉,但是models.Model中的_get_FIELD_display在字段中设置了get_Field_display函数,所以这可能就是您获得该输出的原因

尝试调用 _get_FIELD_display:

logging.info('hello %s', b._get_FIELD_display(b._meta.get('role')))
于 2012-12-12T15:49:18.203 回答
0

尝试这个:

class Thing(models.Model):

    THING_ROLE_MONSTER = 0
    THING_ROLE_MUMMY = 1

    ROLE_CHOICES = (
        (THING_ROLE_MONSTER, u'Monster'),
        (THING_ROLE_MUMMY, u'Mummy'),
    )

    role = models.IntegerField('Role', default=0,choices=ROLE_CHOICES)
于 2012-12-12T12:42:47.097 回答