Declarative syntax for widgets in ModelForm

81 views
Skip to first unread message

Ivan Sagalaev

unread,
Jul 6, 2008, 7:27:59 AM7/6/08
to django-d...@googlegroups.com
Hi, everyone!

I was recently refactoring some old code to use a ModelForm and was
stuck at an issue that we don't have a simple way to say "this form has
these fields from a model but with other widgets".

For example I have an Article model that I want to edit in a form:

class ArticleForm(ModelForm):
class Meta:
model = Article
fields = ['author', 'text']

This is nice! But now I want to use a bit larger textarea for 'text' and
want specify it with attrs={'cols': 80, 'rows': 20}. Currently I have
two options:

1. Redeclare entire field:

class ArticleForm(ModelForm):
text = CharField(
u'Text',
is_required=True,
widget=Textarea(...))

class Meta:
model = Article
fields = ['author']

The problem is that along with a widget I have to repeat other
attributes of a field: type, label, required-ness, help_text etc...
Which is bad because it's, ahem, "non DRY" :-). It's also looks a bit
strange because now fields are declared in two different ways.

2. Fix widget in __init__:

class ArticleForm(ModelForm):
class Meta:
model = Article
fields = ['author', 'text']

def __init__(self, *args, **kwargs):
super(ArticleForm, self).__init__(*args, **kwargs)
self.fields['text'].widget = Textarea(...)

Here the problem is that it has enough boilerplate code to be, shall we
say, not beautiful. And also it defeats nice declarative nature of a
ModelForm.

In other words the problem of ModelForm is that we have very usefule
separation between fields and widgets but we cannot declare them separately.


## Proposal

To fix this I was thinking along the lines of:

class ArticleForm(ModelForm):
class Meta:
model = Article
fields = ['author', 'text']
widgets = {
'text': Textarea(...),
}

I know it can be done now with formfieldcallback but it doesn't look
nearly as readable because you have to write an imperative logic
comparing field names.

Sound sane?

Steve Holden

unread,
Jul 6, 2008, 7:57:24 AM7/6/08
to django-d...@googlegroups.com
What's CSS for?

regards
Steve
--
Steve Holden +1 571 484 6266 +1 800 494 3119
Holden Web LLC http://www.holdenweb.com/

Ivan Sagalaev

unread,
Jul 6, 2008, 8:43:01 AM7/6/08
to django-d...@googlegroups.com
Steve Holden wrote:
> What's CSS for?

For styling. Choosing widgets has nothing to do with styling. My cols
and rows for textarea is just an example that, incidently, can be
emulated with CSS. But often you want to use a different widget
altogether, say, a set of radio buttons instead of default select multiple.

P.S. And if you allow me to be pedantic, cols and rows attributes are
not presentational. They even required by HTML 4.01. So CSS here is not
an adequate replacement.

Julien Phalip

unread,
Jul 6, 2008, 9:13:27 AM7/6/08
to Django developers
> ## Proposal
>
> To fix this I was thinking along the lines of:
>
>      class ArticleForm(ModelForm):
>          class Meta:
>              model = Article
>              fields = ['author', 'text']
>              widgets = {
>                  'text': Textarea(...),
>              }
>
> Sound sane?

The proposal sounds reasonable to me. I too find the current ways for
customizing ModelForm defeating the whole purpose of it (ie.
simplicity/cleanliness)...

Yuri Baburov

unread,
Jul 6, 2008, 9:15:10 AM7/6/08
to django-d...@googlegroups.com
Hi Ivan,

Yes, together with dynamic choices will perform great (on init,
developers might assign some options to widgets...).
And before it's landed into the trunk (i hope it will ;) ) you can
make your own subclass of ModelForm which does exactly what you
proposed, and then subclass it everywhere ;)

--
Best regards, Yuri V. Baburov, ICQ# 99934676, Skype: yuri.baburov,
MSN: bu...@live.com

Ivan Sagalaev

unread,
Jul 6, 2008, 9:20:32 AM7/6/08
to django-d...@googlegroups.com
Yuri Baburov wrote:
> And before it's landed into the trunk (i hope it will ;) ) you can
> make your own subclass of ModelForm which does exactly what you
> proposed, and then subclass it everywhere ;)

Yuri, it's a bit redundant note :-). Sure everyone can subclass anything
or patch Django installation. I'm merely asking *if* it's sane enough to
be in Django itself. If yes I'm happy to make a patch.

Yuri Baburov

unread,
Jul 6, 2008, 9:35:37 AM7/6/08
to django-d...@googlegroups.com
Hi Ivan,

1) You missed making a subclass as third option in your initial post
(and preferred one), that's why i suggested it.
2) "Sure everyone can subclass anything or patch Django installation"
is not much realistic statement: for some things, it's much easier
than for other ;)
3) Why I think your idea is good, and why I vote +1 for it:
Adding more and more declarative options to ModelForm and for NFA's
ModelAdmin is good, cause it's making simple forms easier to write.
90% of application forms are typical, let's make them declarative if
it's possible.
100% of developers want to make their simple forms even more simple
(some forms will always be custom).

Julien Phalip

unread,
Jul 6, 2008, 9:44:14 AM7/6/08
to Django developers
To add to the case, redefining fields in ModelForm can introduce
serious problems and do more than just compromise DRY.
E.g. if you have a model field that's optional (blank=True), you need
not to forget to add 'required=False' to your form field re-
declaration...

As for dynamic choices, I think that still belongs to the init method,
not the Meta class.

Yuri Baburov

unread,
Jul 6, 2008, 9:54:14 AM7/6/08
to django-d...@googlegroups.com
On Sun, Jul 6, 2008 at 8:44 PM, Julien Phalip <jph...@gmail.com> wrote:
> As for dynamic choices, I think that still belongs to the init method,
> not the Meta class.

No, I mean kinda dynamic_FIELD_choices methods, as it was suggested
for ModelAdmin.

> On Jul 6, 11:20 pm, Ivan Sagalaev <man...@softwaremaniacs.org> wrote:
>> Yuri Baburov wrote:
>> > And before it's landed into the trunk (i hope it will ;) ) you can
>> > make your own subclass of ModelForm which does exactly what you
>> > proposed, and then subclass it everywhere ;)
>>
>> Yuri, it's a bit redundant note :-). Sure everyone can subclass anything
>> or patch Django installation. I'm merely asking *if* it's sane enough to
>> be in Django itself. If yes I'm happy to make a patch.
> >
>

--

Michael Elsdörfer

unread,
Jul 6, 2008, 10:54:26 AM7/6/08
to Django developers
>      class ArticleForm(ModelForm):
>          class Meta:
>              model = Article
>              fields = ['author', 'text']
>              widgets = {
>                  'text': Textarea(...),
>              }

Why stop at widgets? Let's say I want to change just the label of a
field, or the the choices option of a ModelChoiceField - I also have
to redefine the whole field. Why not do something like:

class ArticleForm(ModelForm):
text = {'widget': 'Text'}
author = {'label': Textarea(...)}
class Meta:
model = Article
fields = ['author', 'text']

Or, more explicit:

class ArticleForm(ModelForm):
text = Attrs(widget='Text')

By the way, IIRC the following should already somewhat do what you
want in most cases, although it's obviously not very nice:

class ArticleForm(ModelForm):
author =
Article._meta.get_field('author').formfield(**custom_options)
class Meta:
model = Article

Michael

Joseph Kocherhans

unread,
Jul 6, 2008, 11:12:04 AM7/6/08
to django-d...@googlegroups.com
On Sun, Jul 6, 2008 at 6:27 AM, Ivan Sagalaev
<man...@softwaremaniacs.org> wrote:
>
> ## Proposal
>
> To fix this I was thinking along the lines of:
>
> class ArticleForm(ModelForm):
> class Meta:
> model = Article
> fields = ['author', 'text']
> widgets = {
> 'text': Textarea(...),
> }
>
> I know it can be done now with formfieldcallback but it doesn't look
> nearly as readable because you have to write an imperative logic
> comparing field names.

+1. I think this would wrap up one of the last reasons to use
formfield_callback, but I think it should be on the post-1.0 list.

Joseph

Ivan Sagalaev

unread,
Jul 6, 2008, 12:06:22 PM7/6/08
to django-d...@googlegroups.com
Michael Elsdörfer wrote:
> Why stop at widgets?

Basically, because I was struggling with widgets many times but didn't
have any practical needs for changing a label, for example. I don't say
it's useless or something but I just can't defend it from my experience.

> By the way, IIRC the following should already somewhat do what you
> want in most cases, although it's obviously not very nice:
>
> class ArticleForm(ModelForm):
> author =
> Article._meta.get_field('author').formfield(**custom_options)
> class Meta:
> model = Article

I do something like this right now. I even have a shortcut function
"model_field" for this which makes it shorter. However, see:

class ArticleForm(ModelForm):
author = model_field(Article, 'author', attrs)

class Meta:
model = Article

every such field definition forces me to repeat field's name twice and
also repeat a model that is already set in Meta.

Ivan Sagalaev

unread,
Sep 27, 2008, 8:13:26 AM9/27/08
to django-d...@googlegroups.com

Hello everyone!

I've just got to implementing this:
http://code.djangoproject.com/ticket/9223

The patch is rather simple, backwards-compatible, contains tests and
docs. I suppose docs require some proof-reading, as usual.

SmileyChris

unread,
Sep 28, 2008, 11:19:22 PM9/28/08
to Django developers
I've always just done this by doing:

MyForm(ModelForm)
model = MyModel
def __init__(self, *args, **kwargs):
self.fields['name'].widget = Textarea() # or whatever

Do we really need another way of doing this? Or am I overlooking
something that this new method introduces?

Something else to consider is how this effects subclasses (I haven't
thought to much about it, just raising a potential gotcha)

Ivan Sagalaev

unread,
Sep 29, 2008, 2:56:38 AM9/29/08
to django-d...@googlegroups.com
SmileyChris wrote:
> I've always just done this by doing:
>
> MyForm(ModelForm)
> model = MyModel
> def __init__(self, *args, **kwargs):
> self.fields['name'].widget = Textarea() # or whatever

You've forgot to call `super` :-). I know that it's only an example but
it adds another line and also shows that such things can easily be
forgotten in real code.

> Do we really need another way of doing this? Or am I overlooking
> something that this new method introduces?

I've addressed this exact thing my first email on subject, so quoting
myself:

> Here the problem is that it has enough boilerplate code to be, shall we
> say, not beautiful. And also it defeats nice declarative nature of a
> ModelForm.

So, yes, it's not the end of the world but it's the same convenience as
`fields` or `exclude` that all could be simulated in __init__.

SmileyChris

unread,
Sep 29, 2008, 5:07:36 AM9/29/08
to Django developers
Bah, it was just prototype code but point taken ;)

I do feel like it leads to slippery slope though. LikeMichael said,
"why stop at widgets?" I often need to change labels and help text
too.

zvoase

unread,
Sep 29, 2008, 8:46:26 AM9/29/08
to Django developers
I understand the motivation for this, and support the idea, but I
think the implementation is a little repetitive:

class MyForm(forms.ModelForm):
class Meta:
fields = # LIST OF FIELDS
widgets = # DICT OF FIELD NAMES : WIDGETS

Why not do something like this:

class MyForm(forms.ModelForm):
class Meta:
fields = {
'text': forms.CharField(widget=forms.Textarea(), ...),
'other_field': None,
}

Where the 'None' indicates "do the default". This way you could
completely redefine the behaviour of the model form. Putting in a list
or tuple would just do what it's always done.

Regards,
Zack

Julien Phalip

unread,
Sep 29, 2008, 9:40:34 AM9/29/08
to Django developers
On Sep 29, 10:46 pm, zvoase <crack...@gmail.com> wrote:
> Why not do something like this:
>
> class MyForm(forms.ModelForm):
> class Meta:
> fields = {
> 'text': forms.CharField(widget=forms.Textarea(), ...),
> 'other_field': None,
> }

This syntax would not be completely DRY. If, for example, the model's
'text' field was optional (blank=True), you would need to explicitly
specify the form field as optional (required=False) as well:

...
'text': forms.CharField(widget=forms.Textarea(),
required=False...),
...

By having a separate 'widgets' option you could modify the widgets
independently from anything else. Not only is that DRY but also that
would prevent some potential mistakes.

Julien Phalip

unread,
Sep 29, 2008, 9:46:27 AM9/29/08
to Django developers
On Sep 29, 11:40 pm, Julien Phalip <jpha...@gmail.com> wrote:
> On Sep 29, 10:46 pm, zvoase <crack...@gmail.com> wrote:
>
> > Why not do something like this:
>
> > class MyForm(forms.ModelForm):
> > class Meta:
> > fields = {
> > 'text': forms.CharField(widget=forms.Textarea(), ...),
> > 'other_field': None,
> > }
>
> This syntax would not be completely DRY. If, for example, the model's
> 'text' field was optional (blank=True), you would need to explicitly
> specify the form field as optional (required=False) as well:
>
> ...
> 'text': forms.CharField(widget=forms.Textarea(),
> required=False...),
> ...
>
> By having a separate 'widgets' option you could modify the widgets
> independently from anything else. Not only is that DRY but also that
> would prevent some potential mistakes.

Also, you can already completely override a field like follows:

class MyForm(forms.ModelForm):
title = forms.CharField(widget=forms.Textarea(), ...)

Ivan Sagalaev

unread,
Sep 29, 2008, 10:37:06 AM9/29/08
to django-d...@googlegroups.com
SmileyChris wrote:
> I do feel like it leads to slippery slope though. LikeMichael said,
> "why stop at widgets?" I often need to change labels and help text
> too.

Full customization already can be done by specifying fields directly in
a form class. In my experience widgets are just very common case.

Brian Beck

unread,
Sep 29, 2008, 1:22:52 PM9/29/08
to Django developers
On Sep 29, 10:37 am, Ivan Sagalaev <man...@softwaremaniacs.org> wrote:
> Full customization already can be done by specifying fields directly in
> a form class. In my experience widgets are just very common case.

I think your model_field helper being built-in (short for
x._meta.get_field(y).formfield(**params), which is not very pretty)
would be the best solution here. In my experience, customizing just
the widget for a model field is just as common as changing just the
label, just the help text, or just the 'required' status. Using your
helper one could override any number of these, the rest being taken
from the defaults.

Ivan Sagalaev

unread,
Sep 29, 2008, 2:23:08 PM9/29/08
to django-d...@googlegroups.com
Brian Beck wrote:
> I think your model_field helper being built-in (short for
> x._meta.get_field(y).formfield(**params), which is not very pretty)
> would be the best solution here. In my experience, customizing just
> the widget for a model field is just as common as changing just the
> label, just the help text, or just the 'required' status. Using your
> helper one could override any number of these, the rest being taken
> from the defaults.

May be... I just don't like how it looks. Apart from repeating model
name for each field and field's own name twice it is not as readable as
a couple of params in Meta that clearly show in what the form is
different from default state.

Reply all
Reply to author
Forward
0 new messages