#34711 closed New feature (wontfix)

Make ChoiceField auto-detect and coerce values.

Reported by: GHPS Owned by: nobody
Component: Forms Version: 4.2
Severity: Normal Keywords:
Cc: Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description (last modified by GHPS)

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:

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.

dynamic integer model choice field always fails form.has_changed()

Change History (9)

comment:1 by GHPS, 17 months ago

Description: modified (diff)

comment:2 by Natalia Bidart, 17 months ago

Resolution: needsinfo
Status: newclosed

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!

comment:3 by GHPS, 17 months ago

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

comment:4 by GHPS, 17 months ago

Resolution: needsinfo
Status: closednew

comment:5 by David Sanders, 17 months ago

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 TypedChoiceField A model form will use these automatically for an IntegerField by default.

Regards,
David

comment:6 by David Sanders, 17 months ago

Resolution: invalid
Status: newclosed

in reply to:  6 comment:7 by GHPS, 17 months ago

Resolution: invalid
Status: closednew

Sorry for reopening this issue again...

Replying to 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.

comment:8 by GHPS, 17 months ago

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...

comment:9 by Mariusz Felisiak, 17 months ago

Resolution: wontfix
Status: newclosed
Summary: form.has_changed() is always True for IntegerChoices Enum Model FieldMake ChoiceField auto-detect and coerce values.
Type: BugNew feature

I appreciate you'd like to reopen the ticket, but we will not change this long-standing behavior and incorporate TypedChoiceField logic in ChoiceField. If you don't agree, please follow the triaging guidelines with regards to wontfix tickets and take this to DevelopersMailingList.

Note: See TracTickets for help on using tickets.
Back to Top