Personal tools
Document Actions

Django ModelForm and newforms

by Dan Fairs posted on 2008-01-01 20:41 last modified 2008-01-01 20:43 —
Browsing the Django code after a recent svn up shows that newforms.form_for_instance and friends are deprecated and that you should use a ModelForm instead. This post gives a brief example of how to do this.

Happy new year, everyone! I spent it huddled up with a cold and lots of water and hot lemon. I hope you had a better time than I did.

To business. I noticed after a recent svn up that newforms.form_for_instance and friends are deprectated. The comment instructs you to use ModelClass instead. Unfortunately, at the time of writing, there aren't any documents on how to do this. I therefore thought it would be useful to share a brief example with you.

This isn't meant to be comprehensive; it should, however, serve as an example to get you going. I'll also post it over on djangosnippets.org which has a bit more exposure than this blog.

The Problem

What form_for_instance and form_for_model did for you was to generate a form based on a Django instance or model. This saves a lot of messing around coding your own forms and keeping them in sync with your models. Doing all that manually clearly violates Django's DRY principle. For complex forms, you're still going to need to do this yourself; but if you're just creating add/edit forms for model instances then form_for_* are useful.

These helper functions have now been replaced with a unified approach using ModelClass. ModelClass lives in django.newforms.model.

ModelClass: The Theory

To use ModelClass, you basically do the following:

  • Create a model as you normally would
  • Create a subclass of Django's ModelClass with Meta inner class indicating the underlying model
  • Instantiate the form in view code

It's all pretty straightforward - once the form has been created, data validation and cleaning work in exactly the same way as regular newforms.

An Example

Let's go through an example. First off, we start with a model. This would probably live in your models.py for your application, as normal.

class Project(models.Model):
    title = models.CharField(max_length=50)
    created_on = models.DateTimeField(auto_now_add=True)
    description = models.TextField(max_length=5000)

    def __unicode__(self):
        return self.title

    class Admin:
        pass

Hopefully nothing should be too scary there.

I then created a views/forms.py module, and place the following code in there:

from pm.models import Project
from django import newforms as forms

class ProjectForm(forms.ModelForm):
       
    class Meta:
        model=Project

Once again, very simple. The only thing to note is the specification of the model to use as the basis of the form in the Meta class. This follows the pattern used in regular model classes pretty closely.

That's all the code there is to it on the forms side - on to the view. That is, once again, very simple:

from django.contrib.auth.decorators import permission_required
from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect
from django.shortcuts import render_to_response
from pm.forms import ProjectForm

@permission_required('pm.add_project')
def project_add(request):
    project = Project()
    if request.POST:
        form = ProjectForm(data=request.POST, instance=project)
        if form.is_valid():
            form.save()
            return HttpResponseRedirect(reverse(project_detail, args=(project.id,)))
        else:
            request.user.message_set.create(message='Please check your data.')
    else:
        form = ProjectForm(instance=project)

    context = section(request, 'projects')
    context['form'] = form
    return render_to_response('templates/pm/project_add.html', RequestContext(request, context))

If you've done any newforms programming, the above should look very familiar to you. In fact, there's nothing different here than what you're used to. The only real difference is the use of the instance keyword argument to pass in a pre-created instance. It's not absolutely necessary to pass request.POST as a keyword argument, it'll work as the first positional parameter.

The template simply uses {{ form.as_p }} to render the finished form.

Note I've also used the permission_required decorator. Always check security on your views - don't ever rely on someone just 'not knowing' your URLs.

Note also that this code refers to something called project_detail - this is just another view. You can read about the fantastic reverse() function on the B-List.

There's more that you can do with this - I recommend that you check out the newforms documentation and the Django sources themselves for more information. However, I hope that the above is enough to get you started with simple ModelForm forms.


Great article!

Posted by Michael Newman at 2008-02-13 17:30
Thanks for sharing your knowledge! New forms is still really new and us bleeding edge code junkies need as much information as we can get. Could you explain the section function you use in the context? I appreciate it. Thanks to Simon Wilson for the link to this post and blog too.

That section() function

Posted by Dan Fairs at 2008-02-13 17:43
Thanks for the feedback.

The section() function that I use there actually just returns some standard stuff that I want in every context for the view. It looks like this (hm, this may get mangled by the comments engine!:

def section(request, section, context=None, **kwargs):
if not context:
context = {}
context['request'] = request
context['section_id'] = section
context.update(kwargs)
return context

So there's no magic there. It could have almost been done with a context processor, but I wanted more call-specific information (in this case, the section ID) in the dict.
 

Powered by Plone CMS, the Open Source Content Management System

This site conforms to the following standards: