为什么get_FOO_display()在记录信息(django)时会返回整数值?
我有一个模型字段,它使用一个选项来限制其值。这很好用 我在应用程序的任何地方都可以使用它,除非记录信息, 当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的回答,我挖了内部并发现了以下内容。
django.db.models
中的基础_get_FIELD_display方法如下所示:
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指出一个直言不讳的事实,即我正在以错误的价值开始。我把它归结为“强烈打造世界”的移民,这些事情不可能发生!
我在此处未包含的代码是使用ChoiceField中的cleaned_data
从django表单初始化对象。这个问题是ChoiceField的输出是一个字符串,而不是一个整数。我错过的一点是,在松散类型的语言中,可以使用字符串设置整数属性,并且不会发生任何不好的事情。
现在看一下,我发现我应该使用TypedChoiceField,以确保cleaned_data
的输出始终是整数。
谢谢大家。
答案 0 :(得分:11)
如果这听起来居高临下我真的很抱歉,但你是否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()
。
变量不是键入的,但是它们中的值是。我真的很惊讶IntegerField
也将字符串强制转换为int。在这里学习很好 - 首先检查基础知识!
答案 1 :(得分:2)
没有尝试过您的代码,@ like-it也没有回答抱歉,但来自models.Model的_get_FIELD_display在curried字段中设置了get_Field_display功能,所以那就是可能为什么你得到那个输出
尝试调用_get_FIELD_display:
logging.info('hello %s', b._get_FIELD_display(b._meta.get('role')))
答案 2 :(得分: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)