Jan 01

Django ModelForm and newforms

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.


Comments

1 Michael Newman says...

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.

Posted at 4:30 p.m. on February 13, 2008

2 Dan Fairs says...

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.

Posted at 4:43 p.m. on February 13, 2008

3 James Gardner says...

Hey,

This was posted a while ago (like most decent django posts). I'll have to read around the rest of your blog once I'm done here.

One question, what (if anything) do you base your project structure on? Any kind of standard?

Great stuff, thanks!

James

Posted at 12:16 p.m. on January 25, 2010

I've disabled comments for now due to spam problems - I'll turn them back on when I've fixed it!

This won't be published anywhere, it's just in case I need to contact you.

You can use Markdown in your comments. Be sensible!

Sorry about this, but I don't want spam comments.