Go ahead take a bit!
Header image

Django 1.3 Form API ModelForm Example

17 Comments

I’m in the midst of trying to learn Django. The documentation is usually very good but it fell flat when putting together a working example that used the Form API especially with ModelForm. Searching “Django Form API example” was far too broad and the tutorials I could find all seemed out of date or at least not in line with the documentation. After stumbling upon Advanced Django Forms Usage and a forum answer to my question I was able to piece together a working example. This is an extreme over simplification and is meant only to demonstrate the Form API using ModelForm. Note that there is an extra model in my example as I had originally hoped to include formsets. I’ve since decided to keep this example simpler. I may revisit this example to include them at a later date.

WARNING! I am not a Django developer. I don’t know what I’m doing and probably got somethings wrong. This is the best way I could see to do it based on my reading. That said this is fully working code which is more than can be said about the code snippets I could find.

examplesite.zip should contain all the code.

forms.py

1
2
3
4
5
6
7
8
9
10
from django.forms import ModelForm
from phonebook.models import Contact, PhoneNo

class ContactForm(ModelForm):
    class Meta:
        model = Contact

class PhoneNoForm(ModelForm):
    class Meta:
        model = PhoneNo

models.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from django.db import models

class Contact(models.Model):
    name = models.CharField(max_length=80)

    def __unicode__(self):
        return self.name

class PhoneNo(models.Model):
    contact = models.ForeignKey(Contact)
    phone_no = models.CharField(max_length=20)
    phone_type = models.CharField(max_length=10)

    def __unicode__(self):
        return self.phone_no

urls.py

1
2
3
4
5
6
7
8
9
10
from django.conf.urls.defaults import patterns, include, url

urlpatterns = patterns('',
    url(r'^contacts/$', 'phonebook.views.contacts'),  
    url(r'^contact_add/$', 'phonebook.views.contact_add'),
    url(r'^contact_edit/(?P<contact_id>\d+)/$',
    'phonebook.views.contact_edit'),
    url(r'^contact_delete/(?P<contact_id>\d+)/$',
    'phonebook.views.contact_delete'),
)

views.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
from django.shortcuts import render_to_response, redirect, get_object_or_404
from django.template import RequestContext

from phonebook.models import Contact, PhoneNo
from phonebook.forms import ContactForm, PhoneNoForm

def contacts(request):
    latest_contact_list = Contact.objects.all().order_by('name')
   
    return render_to_response('phonebook/contacts.html',
    {'latest_contact_list': latest_contact_list,})

def contact_add(request):
    # sticks in a POST or renders empty form
    form = ContactForm(request.POST or None)
    if form.is_valid():
        cmodel = form.save()
        #This is where you might chooose to do stuff.
        #cmodel.name = 'test1'
        cmodel.save()
        return redirect(contacts)

    return render_to_response('phonebook/contact_add.html',
                              {'contact_form': form},
                              context_instance=RequestContext(request))
                             
def contact_edit(request, contact_id):
    contact = get_object_or_404(Contact, pk=contact_id)
    form = ContactForm(request.POST or None, instance=contact)
    if form.is_valid():
        contact = form.save()
        #this is where you might choose to do stuff.
        #contact.name = 'test'
        contact.save()
        return redirect(contacts)

    return render_to_response('phonebook/contact_edit.html',
                              {'contact_form': form,
                               'contact_id': contact_id},
                              context_instance=RequestContext(request))
                             
def contact_delete(request, contact_id):
    c = Contact.objects.get(pk=contact_id).delete()

    return redirect(contacts)

contact_add.html

1
2
3
4
5
6
7
8
9
10
11
</div>
<h1>Add Contact</h1>
{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}

<form action="/phonebook/contact_add/" method="post">
{% csrf_token %}
<table>
{{ contact_form.as_table }}
</table>
<input type="submit" value="Save" />
</form>

contact_edit.html

1
2
3
4
5
6
7
8
9
10
11
</div>
<h1>Edit Contact</h1>
{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}

<form action="/phonebook/contact_edit/{{ contact_id }}/" method="post">
{% csrf_token %}
<table>
{{ contact_form.as_table }}
</table>
<input type="submit" value="Save" />
</form>

contacts.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
</div>
<h1>Contacts</h1>

<a href="/phonebook/contact_add/">+ Add Contact</a>
{% if latest_contact_list %}
    <table border=1>
        <tr>
          <th>Name</th>
          <th>&nbsp;</th>    
        </tr>
        {% for contact in latest_contact_list %}
        <tr>
          <td>
            <a href="/phonebook/contact_edit/{{ contact.id }}/">
      {{ contact.name }}
      </a>
          </td>
          <td>
            <a href="/phonebook/contact_edit/{{ contact.id }}/">edit</a>
      <a href="/phonebook/contact_delete/{{ contact.id }}/">delete</a>
          </td>      
        </tr>
        {% endfor %}    
    </table>

{% else %}
    <p>No contacts created.</p>
{% endif %}

You can follow any responses to this entry through the RSS 2.0 You can leave a response, or trackback.

17 Responses

  • koenb says:

    A few remarks:

    - you should add “commit=False” into the form saves at line 17 and 31.

    - you should not hardcode the urls into your html: use the url tag in the hrefs and leave action blank (since you are committing to the same view).

    - I would recommend showing a confirmation form before deleting (no actions on GET)

    • Aaron Short says:

      Your right. I just learned about “commit=False” just a few days ago. I still have to pickup on url tags. I knew better on a confirmation for deleting and no action on GET. Laziness won out at that part of the example and delete was just tacked on for completion. I may expand on this example eventually and will correct those issues when I do.

  • Pingback: Django Tutorials index « Roshan Book

  • rossdavidh says:

    Thanks, this was way more useful than the documentation! I generally like django’s documentation better than most, but an example just slightly more involved than “hello, world” is a much better way to explain something, I think. Thanks for posting!

  • Josh says:

    I’m not entirely sure where cmodel comes from and what it is?

  • Josh says:

    Just so you know, this has been the best piece of Django help I have come across..
    Seriously, thank you.

  • Joel Goldstick says:

    I like your article. I downloaded your code and ran the site. It only lets you put in phone numbers in the admin app. Is that what you wanted? or is there a missing view?

  • sahat says:

    Very helpful tutorial! Thanks.

  • susan says:

    THANK YOU! I agree with Ross – this example helped me get my site up and going (or the basics of it anyway)

  • Colin says:

    Finally a simple example that works, i’ve trying to post data in for ages now. You are a few steps ahead of me, i’ve come from PHP and that logic doesn’t apply, but warming to Django.

    Thanks for the exmaple.

    Col.

  • Pingback: Django calendar « djangogal

  • Hi Aaron,
    This is a brilliant example and I thank you for it. I built your example and this works fine. Thank you for your time and effort.

    When I try to do the same with my own code the def add_somename() works fine. The def edit_somename() is where I am having trouble. I cannot see where I have gone wrong.

    What is happening is the _id is being dropped. For instance the following is what Django is reporting back…
    [03/Oct/2012 22:17:41] “GET /depot/ HTTP/1.1″ 200 5831
    [03/Oct/2012 22:17:48] “GET /depot_edit/2/ HTTP/1.1″ 200 779
    [03/Oct/2012 22:17:54] “POST /depot_edit// HTTP/1.1″ 404 3903

    In the final line /2/ is replaced by // or so I believe to be the case. I believe the problem is in views.edit_somename. This does not happen in your example.

    I am only learning Django, learning the hard way most of the time ;-)

    If you need my code I will gladly forward it to you, I prefer to ask. People can be very busy, I know.

    Thanks again for the effort and time that you have put in,
    Tommy.

  • Jonathan Rickard says:

    This was very helpful. Thank you.

  • Ricardo says:

    I am tremendously thankfull to you Aaron. This is hands down the best example of how to use ModelForm that i have seen. You saved my day! Thank you!

  • Dylan says:

    You are rad. There is nowhere else that this is covered so explicitly. It was sending me mad. Thank you.

  • Jamie says:

    Thanks for the example. The official documentation for modelforms is still sorely lacking. I got further in 15 minutes after finding your example then I did in 2 evenings before.



Leave a Reply

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

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>