Django newforms documentation updated

The Django newforms documentation has been updated some days ago. It includes an explanation on how to use form_for_models and form_for_instance and provides some examples.

More interesting, however, is when not to use them: “If you want to create a form whose fields map to more than one model, or a form that contains fields that aren’t on a model, you shouldn’t use these shortcuts.”


Leave a Comment

Using dynamic choices with Django newforms and custom widgets

Most of the examples I have found on the web about replacing the default Django newforms widgets use hard coded values for the list of choices the widget displays. But those hard coded examples fall short when the widgets are bounded to many-to-many fields or to foreign keys in the model class. That is, when the list of choices is generated dynamically at run time, read from a database table.

The solution is obvious simple: fetch the dictionary of choices from the original widget, and use it as a parameter for your new custom widget.

It sounds simple, and it is. But it took me some time to figure out how to do it. The Django newforms lilbrary documentation is still work in progress, at least at the time of this writing. I could not found where the original widgets were stored in the Form instance. Thankfully, Django is an open source project, and Python has an interactive shell mode. Inspection of the source code and regression tests, and some playing with the Python shell is really an enlightening process. Anyway, I am posting this on the blog for further reference. I also include a working example of a sample Django blogging application I wrote for testing: pretty basic, but it and ilustrates, among other things, the use of custom widgets with dynamic choices.

Maybe the explanaition is “over-verbose”… suggestions accepted.

Don’t hit the database twice

Consider the following model class:

</p>

<h1>models.py Models for django blog application</h1>

<p>from django.db import models</p>

<p>class Tag(models.Model):
    tag = models.CharField(maxlength=50)</p>

<p>class Author(models.Model):
    name = models.CharField(maxlength=100)
    email = models.EmailField()</p>

<p>class Article(models.Model):
        (...)
    author = models.ForeignKey(Author)
    tags = models.ManyToManyField(Tag)

The standard method for displaying a form for the model class in a webpage is to subclass forms.Form, create an instance of the subclass and return the rendered the template. Examples of this can be found on the Django website and on the web.

Django newforms provides two methods that simplify the subclassing of forms.Form: form_for_instance and form_for_model. Both methods return a new class: a subclass of Form, more precisely, tuned to your model class or model instance. If you have a model class or model instance with a many-to-many relationship, and you don’t mind displaying the relationship as a listbox on the webpage, then these two methods will take care of querying the database, building the list of options and feeding it to SelectField or a SelectMultipleField widget.

          #  ArticleForm is a subclass of form.Form
          ArticleForm = forms.models.form_for_model(Article)
The next step is to create an instance of ArticleForm, and return the rendered tempalte:

        form = ArticleForm()
        return render_to_response('post.html', { 'form': form })

I don’t like lists for multiple option selection, at least not in webpages. I prefer checkboxes. It takes some CSS tweaking to render them correctly and evenly spaced on the webpage, but it greatly enhances the user experience.

The class generated by form_for_model (and by form_for_instance) stores the widgets in a variable named base_fields (a dictionary). This makes it simple to replace any of the default widgets with any other widget, provided it makes sense to do so. For example, the following code replaces the author and tags widgets with a RadioSelect widget and a CheckboxSelectMultiple widget:

        ArticleForm.base_fields['tags'].widget = CheckboxSelectMultiple(choices= ... )
        ArticleForm.base_fields['author'].widget = RadioSelect(choices= ... )

The only thing left is to provide the list of choices that both CheckboxSelectMultiple and RadioSelect constructors expect as one of their parameters. One way to build such lists is to query the database and fetch the entries for the Tag and Author classes. Something like tagChoices = Tag.objects.all() and authorChoices = Author.objects.all(). Convert the resulting queryset to a dictionary and use it as a parameter to the RadioSelect widget constructor.

But this is querying the database twice and for the same data each time the form is displayed on the webpage: once for the original SelectMultiple widget, and then again for the custom widget. And it would be a shame to have your name on such code… Obviously, this can be avoided by retrieving the list of choices from the original SelectMultiple widget and using it as the list of choices for our custom widget:

        ArticleForm = forms.models.form_for_model(Article)</p>

<pre><code>    ArticleForm.base_fields['tags'].widget = CheckboxSelectMultiple(
            choices=ArticleForm.base_fields['tags'].choices) 

    ArticleForm.base_fields['author'].widget = RadioSelect(
            choices=ArticleForm.base_fields['author'].choices)

    form = ArticleForm()
    return render_to_response('post.html', { 'form': form })
</code></pre>

<p>

That’s it. The Author SelectField gets replaced by radio buttons, and the Tag MultipleSelectField by some nice checkboxes. The database gets hit only once. Nothing to be ashamed of.

A complete example

Updated 2007-5-5: added editpost.html, which was not included by mistake.

The following is a test blog application I wrote. It uses form_for_instance and form_for_model to define the proper subclass of form, then replaces the standard widgets with custom ones. The widgets load the Author and Tags choices dynamically. In addition, creation and edition of the blog posts are handled by the same method. A regular expression in urls.py converts web page names to slugs as registered in the database, so permalinks are effectively implemented.

This blogging app is really basic: the Django admin interface is needed to add new tags and authors; no authentication; post slugs have to be hand created; no plugins; etc. But deactivate the edit line in urls.py, use css to pump up the desigh, edit your posts using the admin interface, and you have a Django-powered miniblog you can start customizing. Maybe pass the form with the article content to another method, before rendering the template, and write some plugins to pre-process the content prior to show it on the webpage.

Some screenshots of the Django mini-blog

This is pretty spartan. Throw in some CSS if you like.

Django Blog, main page

Django blog, edit post

[Read the rest of this entry...]


Comments (11)

Iterating over items of selection fields in django templates using newforms

A year ago I wrote a custom CheckboxSelectMultiple control for django. My application needed to display a series of checkboxes on the webpage, but the default django control did not allow iteration over each checkbox when the control was rendered in the template (as it was possible with the RadioSelect control). This finer control was necessary because I needed to insert extra HTML between each checkbox.

As of version 0.95, django has been under heavy changes, and my custom control no longer works. In particular, the old forms module is being discarded in favor of the newforms module that will become the default forms module sometime in the future. A good explanation can be found in the on-site django documentation, under newforms-migration plan.

The good news is that newforms allows access to individual items of the form fields, multiple-select fields included. The newforms documentation is still work in progress, so it took me a while to figure out how to do it… by inspecting the source code and regression tests. It seems pretty obvious now, should have asked in the django-users list.

The example code has been tested with django svn release 4812 (2007-3-23).

2007-03-27: By mistake I published an incorrect version of views.py. Code has been corrected so that now add_post saves the tag field as expected. (Post has a many-to-many relationship with Tag, so form.save() is not enough to save the form data.)

2007-04-12: The code for views.py has been corrected again. The code posted 2007-03-27 works, but as I discovered later, there is no need to create another object (p = Post(**cleandata)) to handle the many-to-many field data. form.save() takes care of everything, as expected.

2007-04-24: You may be interested in this post.

template

</p>

<ul>
{% for choice in form.base_fields.tag.choices %}
<li> ({{ choice.0 }}, {{ choice.1 }}) </li>
{% endfor %}
</ul>

<p>

models.py

</p>

<h1>-<em>- coding: utf-8 -</em>-</h1>

<h1>models.py</h1>

<p>from django.db import models</p>

<p>class Tag(models.Model):
    tag = models.CharField(maxlength=20)</p>

<pre><code>def __str__(self):
    return self.tag
</code></pre>

<p>class Post(models.Model):
    # other fields here:
    # text = models.CharField(maxlength=255)
    # title = models.CharField(maxlength=50)
    # etc.
    tag = models.ManyToManyField(Tag)</p>

<p>

views.py

</p>

<h1>-<em>- coding: utf-8 -</em>-</h1>

<h1>views.py</h1>

<p>from django.template import Context, loader
from django.http import HttpResponse, HttpResponseRedirect</p>

<p>from django import newforms as forms
from django.newforms.widgets import *</p>

<p>from project.models import *</p>

<p>def add_post(request):
    postForm = forms.models.form_for_model(Post)
    if request.method == 'POST':
       form = postForm(request.POST)
        if form.is_valid():
            form.save()
            return HttpResponseRedirect("/")
    else:
         form = postForm()
         t = loader.get_template('add_post.html')
         c = Context({
               'form': form,
               })</p>

<pre><code>     return HttpResponse(t.render(c))
</code></pre>

<p>

views.py

This is an old version of views.py. The code works, but as I discovered later, there is no need to create another object (p = Post(**cleandata)) to handle the many-to-many field data. form.save() takes care of everything, as expected.

</p>

<h1>-<em>- coding: utf-8 -</em>-</h1>

<h1>views.py</h1>

<p>from django.template import Context, loader
from django.http import HttpResponse, HttpResponseRedirect</p>

<p>from django import newforms as forms
from django.newforms.widgets import *</p>

<p>from project.models import *</p>

<p>def add_post(request):
    postForm = forms.models.form_for_model(Post)
    if request.method == 'POST':
        form = postForm(request.POST)
        if form.is_valid():
           cleandata = form.clean_data
           # use the form tag ids to select the Tag instances
           # related to this Post entry
           tag = Tag.objects.in_bulk(cleandata['tag'])
           # need to delete the tag ids from clean data,
           # otherwise p = Post(** cleandata) will complain that
           # tag is not a parameter of Post( )
           del cleandata['tag']</p>

<pre><code>       # create an instance of Post from the form data
       p = Post(**cleandata)
       p.save()   # need to save so p gets an id.
       p.tag = tag
       p.save()

       return HttpResponseRedirect(&amp;amp;quot;/&amp;amp;quot;)
   else:
   form = postForm()

   t = loader.get_template('add_post.html')
   c = Context({
          'form': form,
    })

    return HttpResponse(t.render(c))
</code></pre>

<p>


Leave a Comment