Django’s ModelForm

In our previous article, How to Use Python Django Forms, we learned how to create Django form objects and use them inside templates for common tasks such as data validation and HTML generation. In this article, we are going to learn how to use Django’s ModelForm to create forms directly from models. Compared to a normal Django form object, a ModelForm object binds itself to a Django Model, thus relieving you of the responsibility to create a form object by hand.

Create a ModelForm for Model Post

In this next code snippet, we define, create and print a PostForm object.

>>> from myblog import models as m
>>> from django.forms import ModelForm
>>> class PostForm(ModelForm):
...     class Meta:
...         model = m.Post
...
>>> post = m.Post.objects.all()[0]
>>> post
<Post: Post object>
>>> form = PostForm(instance=post)
>>> form.as_p()
u'<p><label for="id_content">Content:</label> <input id="id_content" maxlength="256" name="content" type="text" value="New Post" /></p>\n<p><label for="id_created_at">Datetime created:</label> <input id="id_created_at" name="created_at" type="text" value="2013-08-14 21:12:30" /></p>'

As you can see, the ModelForm object’s as_p method returns exactly the same output as our previous form object’s as_p method. Therefore, we can simply replace the form object in our post upload view with a ModelForm object.

# myblog/forms.py
...
from myblog import models as m
 
...
 
class PostModelForm(forms.ModelForm):
    class Meta:
        model = m.Post
 
# myblog/views.py
from myblog.forms import PostForm, PostModelForm
 
 
from myblog.forms import PostForm, PostModelForm
 
 
def post_form_upload(request):
    if request.method == 'GET':
        form = PostModelForm()
    else:
        # A POST request: Handle Form Upload
        # Bind data from request.POST into a PostForm
        form = PostModelForm(request.POST)
        # If data is valid, proceeds to create a new post and redirect the user
        if form.is_valid():
            content = form.cleaned_data['content']
            created_at = form.cleaned_data['created_at']
            post = m.Post.objects.create(content=content, created_at=created_at)
            return HttpResponseRedirect(reverse('post_detail', kwargs={'post_id': post.id}))
 
    return render(request, 'post/post_form_upload.html', {
        'form': form,
    })

Now you can refresh the page http://127.0.0.1:8000/post/form_upload.html to see that the new ModelForm object is rendered identically like the previous form object.

Create a ModelForm for Model Post

Customize ModelForm objects

Instead of exposing all the fields of a model to the user, we can customize the list of fields to exclude certain database fields that we’d like to hide from the user. For example, Comment.created_at is probably a field whose value should default to django.utils.timezone.now() instead of being supplied by the user.

# myblog/forms.py
class CommentModelForm(forms.ModelForm):
    class Meta:
        model = m.Comment
        exclude = ('created_at',)
 
# Python shell
>>> from myblog.forms import CommentModelForm
>>> form = CommentModelForm()
>>> form.as_p()
u'<p><label for="id_post">Post:</label> <select id="id_post" name="post">\n<option value="" selected="selected">---------</option>\n<option value="1">Post object</option>\n<option value="2">Post object</option>\n<option value="3">Post object</option>\n</select></p>\n<p><label for="id_message">Message:</label> <textarea cols="40" id="id_message" name="message" rows="10">\r\n</textarea></p>'

Notice that Comment.created_at is not listed in the output from form.as_p.

Another type of customization we can perform is changing the default field types or widgets associated with certain model fields. For example, PostModelForm.content is rendered using a HTML input element, which is the default Django HTML widget for any CharField. We can change it to use a textarea element instead.

# myblog/forms.py
class PostModelForm(forms.ModelForm):
    class Meta:
        model = m.Post
        widgets = {
            'content': forms.Textarea(attrs={'cols': 80, 'rows': 20})
        }

Now you can refresh the page http://127.0.0.1:8000/post/form_upload.html and notice that the form field Content is rendered as a textarea element instead of a input element now.

Customize ModelForm objects

Summary and Tips

In this article, we learned how to use and customize Django’s ModelForm objects to expose a limited interface of a Django Model such as Post or Comment. Using ModelForm is more favorable than using Form because the former integrates Model objects tightly into its core functionality and relieves the programmer of some of the responsibility when creating a form by hand.

Leave a Reply

Your email address will not be published. Required fields are marked *