Django ModelForm and newforms
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.
That section() function
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.
Great article!