Why does get\_FOO\_display() return integer value when logging info (django)?

I have a model field that is using a choice to restrict its value. This works fine
and I have it working everywhere within the app, except when logging information,
when the get\_FOO\_display() method returns the underlying integer value instead
of the human-readable version.

This is the model definition (abridged):

```
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
    )

```

If I run this within the (django) interactive shell it behaves exactly as you would expect:

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

```

**However**, when I use exactly the same construct within a string formatting / logging
scenario I get the problem:

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

```

returns:

```
New thing: <b>0</b> 

```

Help! 

[UPDATE 1]

When I run the logging within the interactive shell I get the correct output:

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

```

[UPDATE 2] Django internals

Following up on the answer from @joao-oliveira below, I have dug into the internals and uncovered the following.

The underlying \_get\_FIELD\_display method in `django.db.models` looks like this:

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

```

If I put a breakpoint into the code, and then run ipdb I can see that I have the issue:

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

```

So, the fix hasn't changed anything. If I then try running through the `_get_FIELD_display` method code by hand, I get this:

```
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'

```

Which is equivalent to saying:

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

```

So. The problem we have is that the method is using the string value `u'1'` to look up the corresponding description in the choices dictionary, but the dictionary keys are integers, and not strings. Hence we never get a match, but instead the default value, which is set to the existing value (the string). 

If I manually force the cast to int, the code works as expected:

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

```

This is all great, but doesn't answer my original question as to why the get\_foo\_display method **does** return the right value most of the time. At some point the string (u'1') must be cast to the correct data type (1).

**[UPDATE 3] The answer**

Whilst an honourable mention must go to Joao for his insight, the bounty is going to Josh for pointing out the blunt fact that I am passing in the wrong value to begin with. I put this down to being an emigre from 'strongly-typed-world', where these things can't happen!

The code that I didn't include here is that the object is initialised from a django form, using the `cleaned_data` from a [ChoiceField](https://docs.djangoproject.com/en/dev/ref/forms/fields/#choicefield). The problem with this is that the output from a ChoiceField is a string, not an integer. The bit I missed is that in a loosely-typed language it is possible to set an integer property with a string, and for nothing bad to happen.

Having now looked into this, I see that I should have used the [TypedChoiceField](https://docs.djangoproject.com/en/dev/ref/forms/fields/#typedchoicefield), to ensure that the output from `cleaned_data` is always an integer.

Thank you all.