[Django] #34711: form.has_changed() is always True for IntegerChoices Enum Model Field

43 views
Skip to first unread message

Django

unread,
Jul 14, 2023, 2:32:40 PM7/14/23
to django-...@googlegroups.com
#34711: form.has_changed() is always True for IntegerChoices Enum Model Field
--------------------------------------+------------------------
Reporter: GHPS | Owner: nobody
Type: Bug | Status: new
Component: Forms | Version: 4.2
Severity: Normal | Keywords:
Triage Stage: Unreviewed | Has patch: 0
Needs documentation: 0 | Needs tests: 0
Patch needs improvement: 0 | Easy pickings: 0
UI/UX: 0 |
--------------------------------------+------------------------
Summary:

While trying to find out why the forms in two different applications are
marked for saving even if no field has been changed, I could pin the
problem down
to the integer enum fields used in both models and form.ChoiceField
involved.
In short form.has_changed() is triggered because of a double data
conversion
during the round-trip.

The value of the integer enum field is converted to a string and rendered.
The POST request comes back with this string which gets compared to the
original
enum integer which shows that they are different. During the - unnecessary
- saving
the string becomes an int again - which is why the original problem is
hidden
from the perspective of a user.

Background:

In Django 4.2 on Linux (Kubuntu 22.04) take the following enum and
create a model field in model.py:

{{{
class eAssets(models.IntegerChoices):
dvd =1, _('DVD')
bluray =3, _('Blu-ray')
none =0, _('-')

eAsset=models.PositiveSmallIntegerField(choices=eAssets.choices,
default=eAssets.none)
}}}


Create a choice form field in form.py:

{{{
eAsset=forms.ChoiceField(label=mdMovie._meta.get_field('eAsset').verbose_name,
choices=[(dcEntry.value, dcEntry.label) for dcEntry in mdMovie.eAssets],
initial=mdMovie.eAssets.none)

After processing the POST request, form.has_changed() is always true in
view.py:

>fmForm=fmMovie(vRequest.POST, vRequest.FILES or None,
instance=rcCurrentMovie)
>fmForm.has_changed()
True
>fmForm.changed_data
['eAsset']
}}}

Links:
- I've found an old ticket #22097 and it looks to me like this nine year
old bug has shown its ugly face again.
- On Stackoverflow someone reported the same problem.
[https://stackoverflow.com/questions/68763230/django-forms-a-dynamic-
integer-model-choice-field-always-fails-form-has-changed|A dynamic integer
model choice field always fails form.has_changed()]

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

Django

unread,
Jul 14, 2023, 2:52:32 PM7/14/23
to django-...@googlegroups.com
#34711: form.has_changed() is always True for IntegerChoices Enum Model Field
------------------------+--------------------------------------

Reporter: GHPS | Owner: nobody
Type: Bug | Status: new
Component: Forms | Version: 4.2
Severity: Normal | Resolution:

Keywords: | Triage Stage: Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
------------------------+--------------------------------------
Description changed by GHPS:

Old description:

New description:

Summary:

While trying to find out why the forms in two different applications are
marked for saving even if no field has been changed, I could pin the
problem down
to the integer enum fields used in both models and form.ChoiceField
involved.

In short, form.has_changed() is triggered because of a double data
conversion
during the round-trip.

The value of the integer enum field is converted to a string and rendered.
The POST request comes back with this string which gets compared to the
original
enum integer which shows that they are different. During the - unnecessary
- saving
the string becomes an int again - which is why the original problem is
hidden
from the perspective of a user.

This bug seem to be a regression from bug #22097.

Background:

eAsset=models.PositiveSmallIntegerField(choices=eAssets.choices,
default=eAssets.none)
}}}

--

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

Django

unread,
Jul 14, 2023, 5:04:18 PM7/14/23
to django-...@googlegroups.com
#34711: form.has_changed() is always True for IntegerChoices Enum Model Field
------------------------+--------------------------------------
Reporter: GHPS | Owner: nobody
Type: Bug | Status: closed
Component: Forms | Version: 4.2
Severity: Normal | Resolution: needsinfo

Keywords: | Triage Stage: Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
------------------------+--------------------------------------
Changes (by Natalia Bidart):

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


Comment:

Hello,

I have tried to reproduce your issue, and since the provided code is
incomplete, I built my own models and tests. But, in my testing, if
request.POST has the correct and complete information for the model
instance, `has_changed` correctly reports False.

So, I'll be closing this report as `needsinfo` since we need more
information to properly triage this ticket. Specifically, we'd need a
django test project with the necessary models and forms, plus instructions
in how to trigger the issue.

Could you please provide that? Thanks!

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

Django

unread,
Jul 15, 2023, 10:56:13 AM7/15/23
to django-...@googlegroups.com
#34711: form.has_changed() is always True for IntegerChoices Enum Model Field
------------------------+--------------------------------------
Reporter: GHPS | Owner: nobody
Type: Bug | Status: closed
Component: Forms | Version: 4.2
Severity: Normal | Resolution: needsinfo
Keywords: | Triage Stage: Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
------------------------+--------------------------------------

Comment (by GHPS):

As suggested I completely started from scratch[1].
The project is as barebone as it can be.

But the problem still remains - form.has_changed() is alway True for
IntegerChoice enums. Even for different browsers (Firefox/Chrome) or
platforms (Linux/Android)

The sample code is ready to run: The database has three entries which
can be viewed under local URL /test/[1,2,3]/. Pressing the save button
will always trigger the message that the eAsset field has been changed.

1: https://github.com/GHPS/Django-Enum-Bug

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

Django

unread,
Jul 16, 2023, 7:01:16 AM7/16/23
to django-...@googlegroups.com
#34711: form.has_changed() is always True for IntegerChoices Enum Model Field
------------------------+--------------------------------------
Reporter: GHPS | Owner: nobody
Type: Bug | Status: new

Component: Forms | Version: 4.2
Severity: Normal | Resolution:
Keywords: | Triage Stage: Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
------------------------+--------------------------------------
Changes (by GHPS):

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


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

Django

unread,
Jul 16, 2023, 4:09:10 PM7/16/23
to django-...@googlegroups.com
#34711: form.has_changed() is always True for IntegerChoices Enum Model Field
------------------------+--------------------------------------
Reporter: GHPS | Owner: nobody
Type: Bug | Status: new
Component: Forms | Version: 4.2
Severity: Normal | Resolution:
Keywords: | Triage Stage: Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
------------------------+--------------------------------------

Comment (by David Sanders):

Hi GHPS,

Thanks for taking the time to setup a small test project, though
unfortunately the issue isn't with Django in this case; it's with the
sample project's code.

If you want to manually override model form fields you must be sure to use
the correct form field type. In the case of an `IntegerField` with
`choices` you must use a
[https://docs.djangoproject.com/en/4.2/ref/forms/fields/#typedchoicefield
TypedChoiceField] A model form will use these automatically for an
`IntegerField` by default.

Regards,
David

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

Django

unread,
Jul 16, 2023, 4:09:25 PM7/16/23
to django-...@googlegroups.com
#34711: form.has_changed() is always True for IntegerChoices Enum Model Field
------------------------+--------------------------------------
Reporter: GHPS | Owner: nobody
Type: Bug | Status: closed
Component: Forms | Version: 4.2
Severity: Normal | Resolution: invalid

Keywords: | Triage Stage: Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
------------------------+--------------------------------------
Changes (by David Sanders):

* status: new => closed

* resolution: => invalid


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

Django

unread,
Jul 17, 2023, 7:15:16 AM7/17/23
to django-...@googlegroups.com
#34711: form.has_changed() is always True for IntegerChoices Enum Model Field
------------------------+--------------------------------------
Reporter: GHPS | Owner: nobody
Type: Bug | Status: new

Component: Forms | Version: 4.2
Severity: Normal | Resolution:
Keywords: | Triage Stage: Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
------------------------+--------------------------------------
Changes (by GHPS):

* status: closed => new

* resolution: invalid =>


Comment:

Sorry for reopening this issue again...

Replying to [comment:6 David Sanders]:


>> If you want to manually override model form fields you must be sure to
use the correct form field type. In the case of an IntegerField with

choices you must use a ​TypedChoiceField A model form will use these


automatically for an IntegerField by default.

Thanks for the suggestion which I checked on the small test project
mentioned above.

So I've changed the form field type from ChoiceField to TypedCoiceField.
{{{
eAsset=forms.TypedChoiceField(choices=sampleModel.eAssets.choices)
}}}

This change, however, did nothing to the original problem: The
form.has_changed() flag is alway True
regardless of changing or not changing any field in the form.

In the next step I completely removed said line and therefore relied on
the
form field automatically generated - voila, no problem.

Seemingly the ChoiceField/TypedChoiceField methods convert the outgoing
and incoming choice values which confuses the form.has_changed()
detection.

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

Django

unread,
Jul 17, 2023, 7:35:55 AM7/17/23
to django-...@googlegroups.com
#34711: form.has_changed() is always True for IntegerChoices Enum Model Field
------------------------+--------------------------------------
Reporter: GHPS | Owner: nobody
Type: Bug | Status: new
Component: Forms | Version: 4.2
Severity: Normal | Resolution:
Keywords: | Triage Stage: Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
------------------------+--------------------------------------

Comment (by GHPS):

Update:

Setting **coerce=int** in TypeChoiceField works as well but seems to me a
rather crude way to treat
the original problem.

With this approach the developer is forced to remember the right field
type (TypeChoiceField) and
involved values (ints) to clamp a conversion mechanism hidden behind the
scene down to ground.

In both, the enum (IntegerChoice) and the model
(PositiveSmallIntegerField) the information is available
that we are dealing with integers. Perhaps this information can be
utilized to let ChoiceField do
the right thing without intervention...

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

Reply all
Reply to author
Forward
0 new messages