Opened 8 years ago

Closed 7 years ago

#18064 closed New feature (duplicate)

Allow changing form field properties after form creation

Reported by: Chris Wilson Owned by: nobody
Component: Forms Version: 1.4
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


Currently, if I want to tweak the properties of some fields in a ModelForm, I have to replace them in the subclass like this:

class DocumentForm(ModelForm):
    title = models.Document._meta.get_field('title').formfield(required=False)
    programs = models.Document._meta.get_field('programs').formfield(required=False)
    authors = models.Document._meta.get_field('authors').formfield(required=False)
    confidential = models.Document._meta.get_field('confidential').formfield(widget=AdminYesNoWidget)
    uploader = models.Document._meta.get_field('uploader').formfield(required=False)
    file = models.Document._meta.get_field('file').formfield(widget=AdminFileWidgetWithSize)

This is very bulky to just change some properties. The problem is that the field copies some of its properties into weird places like the widget during initialisation:

class Field(object):
    def __init__(...)
        if help_text is None:
            self.help_text = u''
            self.help_text = smart_unicode(help_text)
        widget = widget or self.widget
        if isinstance(widget, type):
            widget = widget()

        # Trigger the localization machinery if needed.
        self.localize = localize
        if self.localize:
            widget.is_localized = True

        # Let the widget know whether it should display as required.
        widget.is_required = self.required

        # Hook into self.widget_attrs() for any Field-specific HTML attributes.
        extra_attrs = self.widget_attrs(widget)
        if extra_attrs:

        self.widget = widget

If we refactored this so that all the property initialisation was done in setters, then we could just write:

class DocumentForm(ModelForm):
    def __init__(...)
        self['title'].required = False
        self['programs'].help_text = 'Hold down Ctrl to select multiple options'
        self['authors'].required = False
        self['confidential'].widget = AdminYesNoWidget
        self['uploader'].required = False
        self['file'].widget = AdminFileWidgetWithSize

Which is more concise, much clearer, and does not override things unnecessarily.

I can write the code, but first I want to:

  • see if any core developers object, to avoid wasting effort
  • know how to run the Django tests and which suite(s) I should be running
  • know where to put the tests.

Change History (4)

comment:1 Changed 8 years ago by Keryn Knight <django@…>

Type: UncategorizedNew feature

For future reference/discussion, #17924 looks to cover a similar sort of need.

comment:2 Changed 8 years ago by Chris Wilson

On Thu, 5 Apr 2012, Beres Botond wrote:

Isn't it this what you are trying to do?

class DocumentForm(ModelForm):
       def __init__(self, *args, **kwargs):
               super(MyForm, self).__init__(*args, **kwargs)
               self.fields['title'].required = False

Which works perfectly fine. You can tweak pretty much any property at
runtime like that, without replacing the field entirely.

Unfortunately not. As you can see from the code above, the Field constructor copies the required parameter to the widget, but only during __init__:

        # Let the widget know whether it should display as required.
        widget.is_required = self.required

So changing the property afterwards doesn't affect the widget as it should. Currently only the FileInput is affected by this, but nothing says that other controls won't take advantage of knowing this in future.

I agree with Keryn that #17924 covers a similar need, except for configuring the controls at runtime, e.g. depending on settings or database contents.

comment:3 Changed 8 years ago by Jannis Leidel

Closing as a duplicate of #17924.

comment:4 Changed 7 years ago by Florian Apolloner

Resolution: duplicate
Status: newclosed

Actually closing it…

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