Opened 16 years ago
Closed 12 years ago
#9245 closed Bug (fixed)
Using choices in a mode field forces use of TypedChoiceField in a form field
Reported by: | Joey Wilhelm | Owned by: | Badri |
---|---|---|---|
Component: | Forms | Version: | 1.0 |
Severity: | Normal | Keywords: | |
Cc: | mmitar@…, s.kuzmenko@…, lukasz@…, David Gouldin | Triage Stage: | Accepted |
Has patch: | yes | Needs documentation: | yes |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description
For most field types, I can override the class used to display the field with:
def formfield(self, **kwargs): defaults = {'form_class': CustomFormField} defaults.update(kwargs) return super(MyFieldClass, self).formfield(**defaults)
However, the Field class specifically overrides this when the field has 'choices'.
Lines 311-326 of django/db/models/fields/init.py contain the offending code. The specific override is on line 318:
form_class = forms.TypedChoiceField
In my case, I have a subclass of TypedChoiceField which I would like to use to display the field. Instead of the simple bit of code shown above, however, I had to duplicate the entire formfield function from the base Field class, and change that one offending line.
Attachments (5)
Change History (21)
comment:1 by , 16 years ago
Component: | Database layer (models, ORM) → Forms |
---|---|
Triage Stage: | Unreviewed → Accepted |
comment:2 by , 16 years ago
Owner: | changed from | to
---|---|
Status: | new → assigned |
comment:3 by , 16 years ago
Summary: | django.db.models.fields.Field forces use of TypedChoiceField → Using choices in a mode field forces use of TypedChoiceField in a form field |
---|
follow-up: 5 comment:4 by , 16 years ago
Has patch: | set |
---|---|
Resolution: | → fixed |
Status: | assigned → closed |
by , 16 years ago
by , 16 years ago
Attachment: | 9245.2.diff added |
---|
comment:5 by , 16 years ago
Replying to badri:
I have uploaded the patch twice. please delete the first one.
comment:6 by , 16 years ago
Resolution: | fixed |
---|---|
Status: | closed → reopened |
Seems to have been accidentally marked as "fixed". Reopening.
comment:7 by , 16 years ago
Just a quick note: I've hit this issue for a different reason.
In my circumstance I'm passing in "choices" to a ManyToManyField declaration (as I don't want to use a query to declare the choices) which is leading to the model form setting the corresponding field up as a TypedChoiceField. This, of course, has a single selection widget as its default widget rather than multiple select.
comment:8 by , 14 years ago
Patch needs improvement: | set |
---|---|
Severity: | → Normal |
Type: | → Bug |
The tests would need to be rewritten using unittests since this is now Django's preferred way.
comment:9 by , 14 years ago
Cc: | added |
---|---|
Easy pickings: | unset |
comment:10 by , 14 years ago
Cc: | added |
---|
comment:12 by , 13 years ago
I ended up creating a new patch since the old one used doctests and the solution was based on a hardcoded list of form fields that should be overriden which basically reintroduced the same problem in a different form.
by , 13 years ago
Attachment: | issue9245-alt.diff added |
---|
An alternative patch which puts the responsibility of handling overrides on model fields
comment:13 by , 13 years ago
Cc: | added |
---|
by , 13 years ago
Attachment: | 9245.more-flexible-formfield.diff added |
---|
comment:14 by , 13 years ago
Needs documentation: | set |
---|---|
Patch needs improvement: | unset |
Thank you for your work and sorry for the late review.
In the revised patch attached, I've moved the logic to a new, separate method called choices_formfield()
. This complicates the API slightly, but at least it removes the necessity for all subclasses of Field
to modify their logic based on self.choices
.
I'll seek some feedback. If this approach is approved then some documentation will be needed.
comment:15 by , 12 years ago
Cc: | added |
---|
Julien, this patch will not work as-is. Your new Field.choices_formfield function is called from Field.formfield using **defaults, but defaults will never include form_class. This means that Field.choices_formfield will always be called with its default value for form_class (None unless overridden by a custom Field subclass) even when the user's intent was to provide a custom form_class for that model field. So for this model:
class Foo(models.Model): bar = models.IntegerField(choices=bar_choices, form_class=MyCustomFormClass)
MyCustomFormClass would never make it to bar's Field.choices_formfield, and so an instance of it would not be returned by Field.formfield.
However, it also not work to simply call self.choices_formfield(form_class=form_class, **defaults) from Field.formfield. Subclasses of Field (such as IntegerField) frequently override formfield() and call super's formfield() with a form_class kwarg. This means that calling Field.choices_formfield with Field.formfield's form_class would break models like this:
class Foo(models.Model): bar = models.IntegerField(choices=bar_choices)
In this case, IntegerField.formfield would call Field.formfield with form_class=forms.IntegerField, which would then be passed through to Field.choices_formfield, causing forms.TypedChoiceField not to be used as was intended.
As the implementation sits currently, Field has no way to determine whether the intent of the user was to specify their own form_class or whether the Field subclass injected its own form_class before calling Field.formfield. This is broken, plain and simple. In order for Field.formfield to make a good decision about which class to use when instantiating a form field, it needs both pieces of information: the model field's default form_class, and the user's override of that default (if provided). Here's the solution I propose:
class Field(object): default_form_class = forms.CharField # ... def formfield(self, form_class=None, **kwargs): if self.choices: form_class = form_class or forms.TypedChoiceField # ... else: form_class = form_class or self.default_form_class class IntegerField(Field): default_form_class = forms.IntegerField
This allows Field.formfield to have both pieces of information, and it alleviates the need for IntegerField to override formfield. It is not backwards compatible, as any model field subclasses which use this formfield override approach would need to be changed to fit the new default_form_class property approach.
comment:16 by , 12 years ago
Resolution: | → fixed |
---|---|
Status: | reopened → closed |
Seems this issue has been fixed (#18162, [b6f4a92ff45d98a63dc29402d8ad86b88e6a6697]).
Changed description to match the problem.