[Django] #30902: The value of a TextChoices/IntegerChoices field has a differing type

34 views
Skip to first unread message

Django

unread,
Oct 23, 2019, 6:31:43 AM10/23/19
to django-...@googlegroups.com
#30902: The value of a TextChoices/IntegerChoices field has a differing type
-------------------------------------+-------------------------------------
Reporter: | Owner: nobody
NyanKiyoshi |
Type: | Status: new
Uncategorized |
Component: Database | Version: 3.0
layer (models, ORM) |
Severity: Normal | Keywords: enum
Triage Stage: | Has patch: 0
Unreviewed |
Needs documentation: 0 | Needs tests: 0
Patch needs improvement: 0 | Easy pickings: 0
UI/UX: 0 |
-------------------------------------+-------------------------------------
If we create an instance of a model having a `CharField` or `IntegerField`
with the keyword `choices` pointing to `IntegerChoices` or `TextChoices`,
the value returned by the getter of the field will be of the same type as
the one created by `enum.Enum` (enum value).

For example, this model:

{{{#!python

from django.db import models
from django.utils.translation import gettext_lazy as _


class MyChoice(models.TextChoices):
FIRST_CHOICE = "first", _("The first choice, it is")
SECOND_CHOICE = "second", _("The second choice, it is")


class MyObject(models.Model):
my_str_value = models.CharField(max_length=10,
choices=MyChoice.choices)
}}}


Then this test:
{{{#!python

from django.test import TestCase
from testing.pkg.models import MyObject, MyChoice


class EnumTest(TestCase):
def setUp(self) -> None:
self.my_object =
MyObject.objects.create(my_str_value=MyChoice.FIRST_CHOICE)

def test_created_object_is_str(self):
my_object = self.my_object
self.assertIsInstance(my_object.my_str_value, str)
self.assertEqual(str(my_object.my_str_value), "first")

def test_retrieved_object_is_str(self):
my_object = MyObject.objects.last()
self.assertIsInstance(my_object.my_str_value, str)
self.assertEqual(str(my_object.my_str_value), "first")
}}}

And then the results:
{{{

(django30-venv) ➜ django30 ./manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
F.
======================================================================
FAIL: test_created_object_is_str (testing.tests.EnumTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/mikailkocak/Development/django30/testing/tests.py", line
14, in test_created_object_is_str
self.assertEqual(str(my_object.my_str_value), "first")
AssertionError: 'MyChoice.FIRST_CHOICE' != 'first'
- MyChoice.FIRST_CHOICE
+ first


----------------------------------------------------------------------
Ran 2 tests in 0.002s

FAILED (failures=1)
}}}


We notice when invoking `__str__(...)` we don't actually get the `value`
property of the enum value which can lead to some unexpected issues,
especially when communicating to an external API with a freshly created
instance that will send `MyEnum.MyValue`, and the one that was retrieved
would send `my_value`.

--
Ticket URL: <https://code.djangoproject.com/ticket/30902>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

Django

unread,
Oct 23, 2019, 9:26:30 AM10/23/19
to django-...@googlegroups.com
#30902: The value of a TextChoices/IntegerChoices field has a differing type
-------------------------------------+-------------------------------------
Reporter: NyanKiyoshi | Owner: nobody
Type: Uncategorized | Status: new
Component: Database layer | Version: 3.0
(models, ORM) |
Severity: Release blocker | Resolution:
Keywords: enum | Triage Stage: Accepted
Has patch: 0 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Carlton Gibson):

* cc: Nick Pope (added)
* severity: Normal => Release blocker
* stage: Unreviewed => Accepted


Comment:

Hi NyanKiyoshi, what a lovely report. Thank you.

**Clearly** :) the expected behaviour is that `test_created_object_is_str`
should pass.

It's interesting that the underlying `__dict__` values differ, which
explains all I guess:

Created: `{'_state': <django.db.models.base.ModelState object at
0x10730efd0>, 'id': 1, 'my_str_value': <MyChoice.FIRST_CHOICE: 'first'>}`
Retrieved: `{'_state': <django.db.models.base.ModelState object at
0x1072b5eb8>, 'id': 1, 'my_str_value': 'first'}`

Good catch. Thanks again.

--
Ticket URL: <https://code.djangoproject.com/ticket/30902#comment:1>

Django

unread,
Oct 23, 2019, 9:28:57 AM10/23/19
to django-...@googlegroups.com
#30902: The value of a TextChoices/IntegerChoices field has a differing type
-------------------------------------+-------------------------------------
Reporter: NyanKiyoshi | Owner: nobody
Type: Bug | Status: new

Component: Database layer | Version: 3.0
(models, ORM) |
Severity: Release blocker | Resolution:
Keywords: enum | Triage Stage: Accepted
Has patch: 0 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Carlton Gibson):

* type: Uncategorized => Bug


--
Ticket URL: <https://code.djangoproject.com/ticket/30902#comment:2>

Django

unread,
Oct 23, 2019, 9:32:33 AM10/23/19
to django-...@googlegroups.com
#30902: The value of a TextChoices/IntegerChoices field has a differing type
-------------------------------------+-------------------------------------
Reporter: NyanKiyoshi | Owner: nobody
Type: Bug | Status: new
Component: Database layer | Version: 3.0
(models, ORM) |
Severity: Release blocker | Resolution:
Keywords: enum | Triage Stage: Accepted
Has patch: 0 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Carlton Gibson):

* Attachment "ticket_30902.zip" added.

Sample project with provided models. Run ./manage.py test

Django

unread,
Oct 23, 2019, 10:27:29 AM10/23/19
to django-...@googlegroups.com
#30902: The value of a TextChoices/IntegerChoices field has a differing type
-------------------------------------+-------------------------------------
Reporter: NyanKiyoshi | Owner: nobody
Type: Bug | Status: new
Component: Database layer | Version: 3.0
(models, ORM) |
Severity: Release blocker | Resolution:
Keywords: enum | Triage Stage: Accepted
Has patch: 1 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Carlton Gibson):

* has_patch: 0 => 1


Comment:

Possible [https://github.com/django/django/pull/11964 PR].

--
Ticket URL: <https://code.djangoproject.com/ticket/30902#comment:3>

Django

unread,
Oct 23, 2019, 7:49:17 PM10/23/19
to django-...@googlegroups.com
#30902: The value of a TextChoices/IntegerChoices field has a differing type
-------------------------------------+-------------------------------------
Reporter: NyanKiyoshi | Owner: nobody
Type: Bug | Status: new
Component: Database layer | Version: 3.0
(models, ORM) |
Severity: Release blocker | Resolution:
Keywords: enum | Triage Stage: Accepted
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 1

Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Shai Berger):

* cc: Shai Berger (added)
* needs_better_patch: 0 => 1


Comment:

This issue only arises if one explicitly calls `str()` on the enum value.
Otherwise, as the supplied test shows, the value is an instance of `str`.
As such, it serializes e.g. to JSON correctly with `json.dumps()`, so I
don't see an issue with external APIs either.

There's deeper discussion in the PR.

--
Ticket URL: <https://code.djangoproject.com/ticket/30902#comment:4>

Django

unread,
Oct 24, 2019, 4:25:40 AM10/24/19
to django-...@googlegroups.com
#30902: The value of a TextChoices/IntegerChoices field has a differing type
-------------------------------------+-------------------------------------
Reporter: NyanKiyoshi | Owner: nobody
Type: Bug | Status: new
Component: Database layer | Version: 3.0
(models, ORM) |
Severity: Release blocker | Resolution:
Keywords: enum | Triage Stage: Accepted
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 1

Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------

Comment (by Carlton Gibson):

So discussion is on the PR, but, summary here, we need this to work:

{{{
>>> t = Template("Will '{{ object.my_str_value }}' render how users expect
it to?")
>>> c = Context({"object": my_object })
>>> t.render(c)
"Will 'MyChoice.FIRST_CHOICE' render how users expect it to?"
}}}

Where the currently it doesn't.

--
Ticket URL: <https://code.djangoproject.com/ticket/30902#comment:5>

Django

unread,
Oct 25, 2019, 3:39:20 AM10/25/19
to django-...@googlegroups.com
#30902: The value of a TextChoices/IntegerChoices field has a differing type
-------------------------------------+-------------------------------------
Reporter: NyanKiyoshi | Owner: nobody
Type: Bug | Status: closed

Component: Database layer | Version: 3.0
(models, ORM) |
Severity: Release blocker | Resolution: fixed

Keywords: enum | Triage Stage: Accepted
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 1

Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Mariusz Felisiak <felisiak.mariusz@…>):

* status: new => closed
* resolution: => fixed


Comment:

In [changeset:"dbcd7b064e7278614f29fc45468d461e263d4da7" dbcd7b06]:
{{{
#!CommitTicketReference repository=""
revision="dbcd7b064e7278614f29fc45468d461e263d4da7"
Fixed #30902 -- Added __str__() for model choice enums.

Allows expected behavior when cast to str, also matching behaviour of
created instances with those fetched from the DB.

Thanks to Simon Charette, Nick Pope, and Shai Berger for reviews.
}}}

--
Ticket URL: <https://code.djangoproject.com/ticket/30902#comment:6>

Django

unread,
Oct 25, 2019, 3:39:59 AM10/25/19
to django-...@googlegroups.com
#30902: The value of a TextChoices/IntegerChoices field has a differing type
-------------------------------------+-------------------------------------
Reporter: NyanKiyoshi | Owner: nobody
Type: Bug | Status: closed
Component: Database layer | Version: 3.0
(models, ORM) |
Severity: Release blocker | Resolution: fixed
Keywords: enum | Triage Stage: Accepted
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 1

Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------

Comment (by Mariusz Felisiak <felisiak.mariusz@…>):

In [changeset:"8740ff334ae3e001514c42ac1fb63a5e4cfa5cd1" 8740ff33]:
{{{
#!CommitTicketReference repository=""
revision="8740ff334ae3e001514c42ac1fb63a5e4cfa5cd1"
[3.0.x] Fixed #30902 -- Added __str__() for model choice enums.

Allows expected behavior when cast to str, also matching behaviour of
created instances with those fetched from the DB.

Thanks to Simon Charette, Nick Pope, and Shai Berger for reviews.

Backport of dbcd7b064e7278614f29fc45468d461e263d4da7 from master
}}}

--
Ticket URL: <https://code.djangoproject.com/ticket/30902#comment:7>

Django

unread,
Oct 25, 2019, 3:40:56 AM10/25/19
to django-...@googlegroups.com
#30902: The value of a TextChoices/IntegerChoices field has a differing type
-------------------------------------+-------------------------------------
Reporter: NyanKiyoshi | Owner: nobody
Type: Bug | Status: closed
Component: Database layer | Version: 3.0
(models, ORM) |
Severity: Release blocker | Resolution: fixed
Keywords: enum | Triage Stage: Accepted
Has patch: 1 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by felixxm):

* needs_better_patch: 1 => 0


--
Ticket URL: <https://code.djangoproject.com/ticket/30902#comment:8>

Reply all
Reply to author
Forward
0 new messages