Better Use of Newforms

The newforms library is a huge time-saver, but when I first started using it, I still found myself writing tedious repetitve code to get it to function how I wanted. While I could get away with it on smaller sites, I recently built a site with some big forms on it and decided to improve my process.

HTML Rendering

First off, {{ form }} or {{ form.as_p }}, rarely cut it in real world apps. We need to be able to customize our forms to improve the layout or add extra information. I started using inclusion tags to render the form fields and labels. Here is my trivial inclusion tag:

@register.inclusion_tag('_display_field.html')
def display_field(field, alt_label=''):
    """
    Print HTML for a newform field. 
    Optionally, a label can be supplied that overrides the default label generated for the form.
    
    Example:
    {% display_field form.my_field "My New Label" %}
    """

    if alt_label:
        field.label = alt_label
    return { 'field': field }

And the accompanying HTML:

{% if field.errors %}
<p class="error">{{ field.errors|join:"<br />" }}</p>
{% endif %}
<label for="{{ field.auto_id }}">
    {{ field.label }}
    {% if field.field.required %}<br />
<span class="required">required</span>
    {% endif %}
</label> 
{{ field }} <br />

This should all be pretty straight forward. The only bit of trickery here is where newforms hides the required value for a field, field.field.required.

This little bit of code changed some unmanagebly large forms into an HTML template that looks like this:

{% display_field form.first_name %}
{% display_field form.last_name %}
{% display_field form.email %}

Now that’s a form you can live with!

update 3/25: There has been some chat on the developer mailing list about fixing some of this inside Django itself.

Form Handling

This is a simple little trick, but it will go a long way towards keeping your code clean and DRY. If you often (or always) perform the same task when processing a form, get that code out of your views. Instead, create a method to handle it inside the form itself. This keeps your views easy to read and makes it easy to edit if you ever make changes to the form itself.

Here’s a quick example of creating a new user:

class UserForm(forms.Form):
    first_name = forms.CharField()
    last_name = forms.CharField()
    email = forms.EmailField()
    username = forms.CharField()
    password = forms.CharField(widget=forms.PasswordInput(render_value=False))
        
    def save(self):
        new_user = User.objects.create_user(username=self.cleaned_data['username'],
                                            email=self.cleaned_data['email'],
                                            password=self.cleaned_data['password'])
        new_user.first_name = self.cleaned_data['first_name']
        new_user.last_name = self.cleaned_data['last_name']
        new_user.is_active = False
        new_user.save()
        return new_user

NB: For the sake of brevity, this leaves out some important error checking. Don’t use this code in production.

Now your view code is dead simple:

form = UserForm(request.POST)
if form.is_valid():
    user = form.save()

Hope you found this useful. More newforms shortcuts in the coming weeks.

Reader Comments

  • March 25, 2008 at 2:46 p.m. #
    Jökull chimed in with:

    I used to put all my .save() in my forms. However over time I found myself going back and forth between view.py and forms.py because so much business logic ends up in the .save(). Just a heads up. Something to think about if you like to keep your business logic in one place.

    One tool I’ve used a lot is an update_object helper function. If my cleaned_dict matches the object properties I can do this: obj = update_object(Object, **cleaned_dict) and then obj.save().

    def update_object(o, **attrs):
        for k, v in attrs.iteritems():
            setattr(o, k, v)
        return o
    
  • March 25, 2008 at 4:28 p.m. #
    pytechd mentioned:

    I have a quick little code library that lets me do similar, but is more controlled in Python since I rarely adjust the actual form output methods. I don’t like the overhead of WTForm (and it wasn’t around when I started my project). I can e-mail it and should have it on djangosnippets in a few days.

    {% form %}
    {% field form.first_name span=6 %}
    {% field form.last_name span=6 %}
    {% endform %}

    It’s table based. CSS forms are nice, but are havoc when your application is based on a LOT of complicated forms. The little template tag {% form %} sets up a table with appropriate columns (12—lets you do 2, 3, and 4 columns easily).

    Very similar ideas. :)

  • April 3, 2008 at 4:06 a.m. #
    emes responded:

    There’s already a tool for that in Django, called newforms.ModelForm. Does the same in save() automatically. The only part left for you is data validation:

    http://www.djangoproject.com/documentation/modelforms/

  • April 3, 2008 at 8:23 a.m. #
    Peter Baumgartner responded:

    @emes, Yes, ModelForm works great when your form translates very closely to a model, but when building real world apps, that isn’t always going to be the case. Take a look at my FormMail clone for an example of this technique that uses a different approach.

Got something to say?





This was written on March, 13 2008 and is filed in code, django.

Thanks for checking in on our blog today. Want to learn more about Lincoln Loop? Visit our home page.

Categories

Archives

Elsewhere

What I’ve been up to online

  • $ cheat git
    git cheat sheet from err the blog
    08.05.09, 14:38 #
  • signing off twitter for a while...
    08.05.05, 9:06 #
  • @jburslem - trying to contact you via http://snurl.com/26uw, but getting an error. Could you drop me a line? pete@lincolnloop.com
    08.05.02, 15:24 #
  • Learning Python Design Patterns Through Video Lectures - good coders code, great reuse
    Google Techtalks about Python Design Patterns
    08.04.23, 22:37 #
  • SEOmoz | The Beginner's Checklist for Small Business SEO
    Excellent tips as usual from SEOmoz
    08.04.23, 15:58 #
  • appengine-monkey - Google Code
    Monkeypatches for the App Engine environment to Make It Normal
    08.04.18, 8:34 #
  • Monkey patches for httplib, urllib and urllib2 for App Engine: http://tinyurl.com/6nlrh2
    08.04.18, 8:21 #
  • Docstoc.com - Find and store professional documents
    boilerplate contracts
    08.04.17, 10:50 #
  • Giving mod_wsgi another spin, so far so good
    08.04.17, 0:46 #
  • CodeClimber: "All I need is a Programmer"
    Why experienced programmers rarely give you a "bro deal"
    08.04.17, 0:44 #
  • Fenton Township residents creates CashOffersFast.com to help struggling home sales
    Some local media coverage for the owner of a web application we developed.
    08.04.16, 20:12 #
  • google-app-engine-django - Google Code
    Helpers to make Django development easier on Google App Engine
    08.04.15, 10:11 #
  • Git Magic - Preface

    08.04.13, 20:05 #
  • The Thing About Git
    Some nice git tips
    08.04.08, 21:07 #
  • Your own appengine | One More Blog
    Build your own App Engine. While I wouldn't touch this with a 10' pole, he brings up a good point of data portability. I hope we'll soon be able to use Django with non-traditional databases like CouchDB or SimpleDB soon.
    08.04.08, 20:57 #

Interested in working with us?
Fill out the form below or contact us at:

PO Box 774441
Steamboat Springs, CO
80477

970.879.8810
info@lincolnloop.com