1. Skip to navigation
  2. Skip to content

Entries in the Category “Python”

Live Blogging the Django Sprint - Sat 10PM

written by Michael Trier, on Dec 1, 2007 10:23:00 PM.

12/1 – 10:30PM

Well I’m about done for the day. This afternoon was less productive for me, but I sure did learn a lot. Digging into issues and trying to verify that the problem exists, or that the attached patch actually solves the problem can be a lot of work, but very rewarding in helping to understand what is going on under the hood of the Django Framework.

Outside of take a nice break to spend some time with my family, This evening consisted of mostly checking code and verifying patches.

At this point there are about 30 or so tickets that are on the completed list for the sprint. This doesn’t mean they are all ready for check-in but a good majority of them are. I don’t have any accurate statistics since our no-bot (I later found out that the stats were being provided by an actual person—go figure) stopped providing them near the end of the work day.

I imagine that there will be a nice handful of folks that will continue on tomorrow to try and take advantage of the momentum that has been achieved. As for myself, I’ll be more hit and miss due to prior obligations, but I will be online and trying to lend a hand where I can be effective.

Live Blogging the Django Sprint - Sat 6PM

written by Michael Trier, on Dec 1, 2007 6:05:00 PM.

12/1 – 6PM

I spent the last few hours digging into various tickets and trying to find something that I feel like I can get my head around. I’m trying my best to stay away from newforms-admin stuff because there’s so much activity there, and I have not checked out and implemented that branch as of yet. It looks like I’ll try to find something that needs tests, docs, or confirmation. My brain is a bit fried at this point and I don’t want to try to tackle anything too large.

It looks like I’ll be working with a couple other developers to test patches and review them. In addition I think I will get into some more Core Framework or Database related stuff.

Time to eat and decorate the tree. I’m going to try to get on later this evening and see if I can help out with a few more items.

Live Blogging the Django Sprint - Sat 3PM

written by Michael Trier, on Dec 1, 2007 3:19:00 PM.

12/1 – 3PM

Well I got the tree and I’m back at it. I decided to dig back into ticket #3264 and write some tests for it. It’s been a while since I’ve run the test framework against the Django source so it took some configuring to get all the right things in the right places.

Once I was sure that I could run the test suite and have everything pass successfully, I began to write my tests. Everything went fine, but all of a sudden I realized my test passed fine without the patch. And this is why we write tests. :)

Digging into it further I discovered that if the form class is a subclass of BaseForm, not Form then the initial values work fine on their own. It’s only when the form class subclasses Form that there is a problem. So for the moment I’ll finish up my test against the Form class and leave it at that.

I can’t go back to my original ticket because I’m still waiting for a design decision. I think I’ll start poking around for the next thing.

Activity on the IRC has been a flurry lately. Everything seems to be gelling now. The IRC bots stats at the moment are:

timeline stats at 20:00 UTC: attachment=61, changeset=22, closedticket=60, editedticket=217, newticket=21, wiki=26 total=407 trac changes

That’s some impressive progress so far.

By the way, speaking of bots, there are two really cool bots that were implemented. The one you see above gives periodic updates of the trac status changes. It’s great to see all the progress. A second bot automatically does a lookup to the trac page everytime someone enters something like #3262 into IRC. This means that as someone references a ticket, the bot responds immediately with something like:

#5701 (Fix naive introspection when using Django decorators) [ http://code.djangoproject.com/ticket/5701 ]

This makes it great for the developers to click on the link and see immediately what the person is referring to.

I’m not sure who put these bots together, but if it was you, please take credit. This is awesome stuff.

Live Blogging the Django Sprint - Sat 11AM

written by Michael Trier, on Dec 1, 2007 10:27:00 AM.

12/1 – 11AM

Well theju and I just finished up testing the patch for ticket #3632. We’ve decided to let the triagers decide if we need to add tests for it. The console testing we did proved the patch worked fine. It’s such a small change. I think if I have some time later today I’ll go ahead and add tests to it.

Things are really getting into full swing at this point since the Lawrence folks are online. It’s great to see everyone working together on all of this. A lot of open source projects could really benefit from this type of approach. Not only does it get a lot of things accomplished, but it also allows a bunch of people to get involved in the process and realize that contributing is not as scary as it might sometimes seem. This whole experience is a great community builder.

Time to go get a Christmas tree…

Live Blogging the Django Sprint - Sat 9AM

written by Michael Trier, on Dec 1, 2007 9:51:00 AM.

12/1 – 9AM

This morning I started to look around for a new ticket to investigate since I didn’t want to do too much effort on #2493 until a decision is made about it. There are a lot of related problems associated with using introspection. For instance the db_table attribute can be set which will cause the table name to be inconsistent with the standard format so just using the appname as a prefix will not work. If the model name is changed a reset is impossible currently. Introspection could solve that but only in the case where there is no db_table specified.

After bopping around a bit theju on the IRC channel asked me if I would test ticket #3632. This ticket is one that’s important to me because it solves the issue of not being able to get bound values when you use the form option with the form_for_instance convenience method. This would be a nice timesaver and the functionality is already there.

I checked out the patch and tried to apply it to trunk. It no longer applied cleanly. The original patch was made in March.

theju and I decided to work on the fix together, he would write tests and I would update the patch. We had one snag right away. The original patch was using SortedDictFromList which was removed recently. So we updated it to use SortedDict and continued testing.

Elsewhere it looks like a lot of work is coming together. Right now there are nine separate items that have been patched and are awaiting review, confirmation, or triaging. If I get a moment later I’ll dig into some of the fixes.

Live Blogging the Django Sprint - Fri 9PM

written by Michael Trier, on Dec 1, 2007 9:27:00 AM.

Today (depending on which timezone you’re in) the Django Project is doing a worldwide sprint. It’s a time where developers all of the world come together in person and through IRC to work on squashing bugs and making enhancements. You can find more information about the sprint at the Django Project Wiki. I’m participating in the sprint and plan to do some live blogging on it as we go.

11/30 – 9PM

The sprint actually started at about 9PM on Friday night for me. The folks in Australia and elsewhere were going at it. There wasn’t a lot of IRC activity until about midnight or so when everyone was up in arms over the fact that MediaTemple, the host of the Django Project website decided to take the servers down for 5 hours! Unbelievable. Thank goodness for the doc files in the trunk and for Google cache.

For my part last night I wanted to get warmed up and decided to grab a ticket that needed some confirmation. I claimed ticket #2493 which deals with an issue where foreign constraints are unable to be dropped by the reset management command. After confirming the problem I began to look into the cause. It turns out that in the process I actually uncovered a larger issue that the drop statements in sql_delete really need to be using introspection across the board, provided that’s the design goal of the command.

Since the sever was down and I was getting tired I went to bed.

PyCon 2007 Talks Available

written by Michael Trier, on Nov 26, 2007 7:26:00 PM.

Looks like some of the PyCon 2007 talks are available for download. I really looking forward to these since I was not able to attend this year.

Simpler NewForms Save

written by Michael Trier, on Nov 25, 2007 11:53:00 PM.

In my last post on Customizing NewForms Nick pointed out that I had forgot my save() method in the form class definition. The problem was a simple copy and paste error but as I began to copy in the save() method it occurred to me that there might be a simpler way to handle saving in NewForms.

The Normal Save

Normally your form class save() method will look something like this:


def save(self, id=None, commit=True):
    if id is None:
        instance = Link()
    else:
        instance = Link.objects.get(pk=id)

    instance.title = self.cleaned_data['title']
    instance.url   = self.cleaned_data['url']
    instance.notes = self.cleaned_data['notes']

    if commit:
        instance.save()

    return instance

In the above situation I’m using a combined add/edit save method. If the id is passed in then I’m using it to look up the object and update the attributes accordingly. If there is no id then I’m just creating a new instance and saving it to the database, provided the commit option is set.

You may prefer to do the lookup in the view and pass in the instance, but either way you’re going to end up with something that resembles the above code in some form or fashion.

At this point in the process the validation has already occurred. We could call the is_valid() method prior to doing the save if we wanted to, or we can rely on the view to handle that for us.

The important thing to notice about the above code is that it is very generic in nature. If we were working with a Foo model then the code would look like the following:


def save(self, id=None, commit=True):
    if id is None:
        instance = Foo()
    else:
        instance = Foo.objects.get(pk=id)

    instance.attribute = self.cleaned_data['attribute']
    ...

    if commit:
        instance.save()

    return instance

Simplifying the Save

It occured to me that I might be able to simplify this process a bit. It then occured to me that the NewForms source code must be doing this already in order to set up the save() method code for the form_for_model and form_for_instance convienence methods.

After digging in a bit I figured out that there is a save_instance method that essentially uses some meta programming to simplify the above code. save_instance is part of the django.newforms.models module. By importing the method and passing in the appropriate values we get a lot of functionality for free. Now my save() method looks like the following:


def save(self, id=None, commit=True):
    if id is None:
        instance = Link()
    else:
        instance = Link.objects.get(pk=id)
    return forms.models.save_instance(self, instance, commit=commit)

This same structure will work for any form we would be saving.

Notice that we’re just passing in an instance of our model and whatever we have specified for the commit option. the save_instance method handles the rest for us. The save_instance method actually accepts a couple other arguments, so if you’re inclined you can accept and pass those along as well. Here’s a list of the arguments:

  • form – an instance of the form that contains the cleaned_data values.
  • instance – The instance model that will be updated with the new values. Note in the case of a new object, we’re just instantiating the model and passing it in.
  • fields (None) – A restricted list of fields that should be updated. This would be in the case where the fields we want to update are not all the fields in the form instance.
  • fail_message (‘saved’) – If there are errors on the form instance a ValueError exception is raised with the following formatted message:

"The %s could not be %s because the data didn't validate." % (opts.object_name, fail_message)) 

The fail_message is used to format the message. (In case you’re curious opts.object_name is the models meta object_name, which is generally just the model class name.) Usually the default will suffice.

  • commit (True) – If this is true then the changes to the model instance will be saved to the database, otherwise the instance model is updated but the changes are not saved.

Note, the save_instance method always returns back the model instance with the updated values.

Possible Further Enhancements

The above is nice, but it would be great to take it one step further. I’d like to see NewForms automatically add a default save() method to the Form class. Although truthfully it is not something likely to happen since the process of loading the instance could change widely. The above solution is a good compromise in keeping our code DRY, yet still providing us with the flexibility we need.

Update – Some of the folks in the Django IRC Channel pointed me to a discussion on on the discussion group. This looks like it has the potential for addressing this issue by holding an instance of the model in the Form, along with doing away with form_for_model and form_for_instance methods. We’ll have to watch this one closely.

Other Approaches

Another approach to solving this problem, by some, has been to coerce form_for_instance, and using it’s save() method. If you’ve looked into the options for form_for_instance you will have noticed that it accepts a form option. Using the form option you can call form_for_instance and pass in your custom form class. For instance:


form = forms.form_for_instance(Entry, form=EntryForm)

The problem with this approach is that the form_for_instance method will not load up the custom form class with the instance initial data. (See Changeset 3632 for more information on this). So the solution is to do something sort of funky, such as:


instance = Entry.objects.get(pk=id)
Form = forms.form_for_instance(instance, form=EntryForm)

form_instance = Form(request.POST) 
if form_instance.is_valid():
  form_instance.save()

Warning I didn’t test the above but pulled it out of some code I’ve seen flying around. It’s just too ugly for my tastes, and so the solution I’ve proposed above will suffice for now.

I’ve only been playing around with this approach for a couple of hours so I haven’t had a chance to really bang on it. If I notice some problems I’ll post more about it here. Until then, enjoy.

Customizing NewForms

written by Michael Trier, on Nov 23, 2007 4:48:00 PM.

In my last post I covered a very basic introduction to getting NewForms working in your application. The purpose of that post was just to make sure we were all on the same page. For a much more detailed explanation, I highly recommend James Bennett’s post on NewForms, part 1.

If you’ve followed along then at this point you should have a functioning add/edit form that posts back your information and saves it in the database. This works great as long as you want every field in your model to appear on the form. Generally that’s not the case. A lot of times there are fields, like user, that you want to set in the view before saving the form data. You don’t want the user to be able to specify the user, you want to get this information from the currently logged in user information.

To customize your NewForms output there are several different approaches. I’m going to touch on a couple of them, but keep in mind there are many ways to achieve the same results. We’re going to look at the following five ways to modify the fields that appear in the NewForms output:

  • Make the field not editable
  • Modify it Inline
  • Using the Fields Parameter
  • Modifying with a Callback
  • Using a NewForm Class

Make the Field not Editable

The quickest and easiest solution to remove a field from your NewForms output is to just set the field option editable=False in your model class. This will cause the form field to not be displayed in the output. For instance:


class Entry(models.Model):
    pub_date = models.DateTimeField()
    title = models.CharField(maxlength=100)
    description = models.TextField()
    user = models.ForeignKey(User)
    last_updated = models.DateTimeField(editable=False)

In the Entry model above we have set the last_updated field to be not editable, by setting the editable field option to False. Although this will cause the NewForm output to not contain the last_updated field, usually this is not an acceptable solution because it has the added side effect of also removing the field from the Administration pages.

A case where this might make sense is one similar to the above where you have a field like last_updated, which holds the date/time the record was last updated. In that case, you just need to override the model’s save() method and update the field accordingly.

Modifying it Inline

Back to our original hypothetical. Let us assume that we want to store the logged in user information when the user signs our guessbook, but obviously we don’t want to allow the user to type in their User ID. So instead, let’s modify the form so that it doesn’t display the user field, but instead have it get auto-populated in the view.

There are three changes we need to make to accomplish this:

1. Modify the base_fields dict to hide the user field

2. If required, modify the user field to not be required

3. Modify the save routine to set the user information before saving the record.

The following modified view accomplishes all of the above:


from django.shortcuts import render_to_response, get_object_or_404
from django.http import HttpResponseRedirect
from django import newforms as forms
from django.newforms import widgets
from guestbook.models import Entry

def add_edit_entry(request, id=None):
    if id is None:
        EntryForm = forms.form_for_model(Entry)
    else:
        entry = get_object_or_404(Entry, pk=id)
        EntryForm = forms.form_for_instance(entry)

    EntryForm.base_fields['user'].widget = widgets.HiddenInput()
    EntryForm.base_fields['user'].required = False

    if request.method == 'POST':
        form = EntryForm(request.POST)
        if form.is_valid():
            entry = form.save(commit=False)
            entry.user = request.user
            entry.save()

            return HttpResponseRedirect("/guestbook")
    else:
        form = EntryForm()

    return render_to_response('guestbook/add_edit_entry.html', {'form': form})

Let’s decipher what’s going on in the above code. We start off by modifying the base_fields dict to hide the user field. To accomplish this we need to set the form field’s widget to the HiddenInput widget. This causes the form to be rendered in the NewForm output but it’s type is hidden, so it’s invisible to the end user. We’ve also modified its required attribute to False. Note that we need to make the field not required otherwise the form.is_valid() method will return False, not allowing us to set the user information ourself.

The next change we needed to make is to modify the form.save() method by setting the parameter commit to False. This causes the form.save() method to not save the form data directly to the database, but instead return a new model instance with the form data. By doing this we now have the opportunity to modify the model before saving it.

At this point we just need to set the entry.user to the current request user information and then finally call the entry.save() method which will save our form information to the database.

This method of modifying the form information is one approach to the problem of customizing your form. It can be used to further modify other fields, their properties, or set customized widgets, etc…

The problem with this approach is three-fold. First it quickly becomes unwieldy. Secondly it’s not very reusable. We would need to do the same thing in another view if we wanted similar behavior. Finally and most importantly you make some compromises in security by doing so.

For instance, note that we have set the user widget to a HiddenInput() widget. This still renders the user field to the web page, but renders it as a hidden input field. Any fields that appear on a form are available to be compromised by the client. You should never trust information that comes from a user form, because it can easily be manipulated. If the user field is not something we want on the form, then the best approach is to not have it appear at all, hidden or not. The next approach will do that for us.

Using the Fields Parameter

Both the form_for_model and form_for_instance methods accept a fields parameter, which takes a list of field names to display on the form. By specifying just the fields we want in that list the form will be rendered accordingly. This has the added benefit of not displaying the fields we don’t want as hidden field elements, leading to better security. For instance:


from django.shortcuts import render_to_response, get_object_or_404
from django.http import HttpResponseRedirect
from django import newforms as forms
from guestbook.models import Entry

def add_edit_entry(request, id=None):
    fields = ('pub_date', 'title', 'description')

    if id is None:
        EntryForm = forms.form_for_model(Entry, fields=fields)
    else:
        entry = get_object_or_404(Entry, pk=id)
        EntryForm = forms.form_for_instance(entry, fields=fields)

    if request.method == 'POST':
        form = EntryForm(request.POST)
        if form.is_valid():
            entry = form.save(commit=False)
            entry.user = request.user
            entry.save()

            return HttpResponseRedirect("/guestbook")
    else:
        form = EntryForm()

    return render_to_response('guestbook/add_edit_entry.html', {'form': form})

Notice that all we added was a fields list containing the fields we want, and passed that into the form_for_model and form_for_instance methods. The result is much cleaner now and the excluded fields will not appear in the NewForm output.

There’s been talk about adding an exclude parameter (or something similar), since the usual case is that you want most of the form but just want to exclude one or two fields. It would be a nice addition, but at the same time it’s important that form_for_model and form_for_instance don’t get unwieldy, since they are just “quick and easy” shortcuts and not meant to be the be all end all for NewForms work.

Another approach to this is to use the formfield callback.

Modifying with a Callback

A formfield callback is a function that we define that gets called each time a new form field is being created. The formfield callback can also be used to do other things to the form field at the time of creation, such as overriding the default form field type.


from django.shortcuts import render_to_response, get_object_or_404
from django.http import HttpResponseRedirect
from django import newforms as forms
from django.newforms import widgets
from guestbook.models import Entry

def my_callback(field, **kwargs):
    if field.name == 'user':
        return None
    else:
        return field.formfield(**kwargs)

def add_edit_entry(request, id=None):
    if id is None:
        EntryForm = forms.form_for_model(Entry, formfield_callback=my_callback)
    else:
        entry = get_object_or_404(Entry, pk=id)
        EntryForm = forms.form_for_instance(entry, formfield_callback=my_callback)

    if request.method == 'POST':
        form = EntryForm(request.POST)
        if form.is_valid():
            entry = form.save(commit=False)
            entry.user = request.user
            entry.save()

            return HttpResponseRedirect("/guestbook")
    else:
        form = EntryForm()

    return render_to_response('guestbook/add_edit_entry.html', {'form': form})

Note that we simple created a formfield callback function and assigned it to the formfield_callback parameter of the form_for_model and form_for_instance methods.

To exclude the field from the form definition all we have to do is return None for the specified field. This will cause that field to not be part of the form’s field definition. Notice that we need to handle the case where the field is not the field(s) we’re trying to exclude. This is because all fields get passed into the formfield callback. You can read more about this in the Django Documentation.

All of the above might seem like a lot of work and leaves you with an implementation that is not very reusable or DRY. Well you’re right, and the quicker you learn that the better. That’s why there are form classes.

Using a NewForm Class

Customizing your form by creating a form class is the “endorsed” way to customize the look and feel of your forms—for good reason. It gives you the most flexibility and in the long run it is the cleanest way to approach the task. That said, there seems to be incredible resistance by some in creating a form class. Creating the form_for_model and form_for_instance convenience methods make using NewForms immediately accessible but on the other hand it has also put blinders on some programmers causing them to be resistant to more beneficial approaches.

To create a custom form class we need to simply inherit from the forms class and define the form in a similar manner as you define your models. I’m not going to go into the detail of creating a form class because it’s covered well in the offical documentation on newforms. Again, I also highly recommend James Bennett’s post on NewForms, part 1.

So where does this information go? Your custom form class can go wherever you want since it’s just python code. Some people prefer to put it in their view.py file directly, others prefer to put it in its own forms.py file. I prefer the latter, but it really doesn’t matter where you put it. (Believe it or not this is an often asked question.) For example here is one of my applications’ directory structure:

Here’s a simple form class for our Guestbook add / edit entry form:


from django import newforms as forms
from models import Entry
from django.newforms.extras import widgets

class EntryForm(forms.Form):
    pub_date = forms.DateField(label=u'Publish date', widget=widgets.SelectDateWidget())
    title = forms.CharField(max_length=100)
    description = forms.CharField()

    def save(self, id=None, commit=True):
        if id is None:
            instance = Entry()
        else:
            instance = Entry.objects.get(pk=id)
        return forms.models.save_instance(self, instance, commit=commit)

To exclude the fields we don’t want we just need to exclude the field from the NewForms class definition. Once we have our NewForms class defined we can use it in our view as follows:


from django.shortcuts import render_to_response, get_object_or_404
from django.http import HttpResponseRedirect
from django import newforms as forms
from guestbook.models import Entry
from guestbook.forms import EntryForm

def add_edit_entry(request, id=None):
    if request.method == 'POST':
        form = EntryForm(request.POST)
        if form.is_valid():
            entry = form.save(id=id, commit=False)
            entry.user = request.user
            entry.save()

            return HttpResponseRedirect("/guestbook")    
    else:
        if id is None:
            form = EntryForm()
        else:
            entry = get_object_or_404(Entry, pk=id)
            form = EntryForm(entry.__dict__)

    return render_to_response('guestbook/add_edit_entry.html', {'form': form})

As I stated above, using a form class gives you the most flexibility and provide the best option for reuse throughout your code.

We covered a lot of different approaches and I’m sure there many more creative solutions that people have come up with. Hopefully I’ve given you a couple more options for handling the customization of your NewForm output. The important thing is to be aware of the benefits and trade offs of each approach, and choose wisely.

Getting Started with NewForms

written by Michael Trier, on Nov 23, 2007 5:23:00 AM.

NewForms are a wonderful thing, and once you “get it” there is an “a ha” moment and you begin to appreciate the architecture behind the new forms implementation. Until then it is easy to find yourself struggling with getting basic functionality working. I do not want to duplicate the excellent documentation already provided, so instead I’ll assume you have familiarized yourself with what is there. In addition to the standard documentation, I highly recommend that you familiarize yourself with the Big Nerd Ranch’s post on Using the Django newforms Library.

This post will be a simple walkthrough on creating newforms. I will build on it in a followup post that will address customizing newforms.

Mapping Urls

When I get started on something I always like to start with defining my urls information. A lot of times this helps me think through my requirements for what I’ll be doing and how it will fall into the constructs of the rest of my application. In this case we are just going to create a simple add / edit form for a Guest Book page. Guest Book will consist of a standard entry form for people to “sign our guest book”. Along with a the standard generic list and object detail view urls we’re going to add in our add / edit view url. When we get done it should look something like this:


from guestbook.models import Entry

info_dict = {
    'queryset': Entry.objects.all(),
}

urlpatterns = patterns('django.views.generic.list_detail',
   (r'^(?P[\w-]+)/$', 'object_detail', dict(info_dict, slug_field='slug')),
   (r'^/?$', 'object_list', info_dict),
)

urlpatterns += patterns('guestbook.views',
    (r'^add/$', 'add_edit_entry'),
    (r'^edit/(?P\d+)/$', 'add_edit_entry'),
)

Notice that we’re just mapping /add/ and /edit/#/ to a view method named add_edit_entry. This method will combin both our add and edit functionality for the newform.

That’s all we need to do for the urls. You can choose to make them appear however fits your application the best.

Next we need to lay out our view code. This is where the meat of newforms gets implemented.

Create the View

Most people get their first experience with newforms through the use of the form_for_model and form_for_instance helper methods on the forms class.

These methods make it really easy to get started. The form_for_model and form_for_instance methods are used to create unbound and bound forms respectively. Unbound forms are what you will generally use when you are creating an “add” type of form. The form itself is not bound to any existing data. The bound form is bound to existing data and is used when creating an “edit” type of form. Consider the following views:


from django.shortcuts import render_to_response, get_object_or_404
from django.http import HttpResponseRedirect
from django import newforms as forms
from models import Entry

def add_edit_entry(request, id=None):
    if id is None:
        EntryForm = forms.form_for_model(Entry)
    else:
        entry = get_object_or_404(Entry, pk=id)
        EntryForm = forms.form_for_instance(entry)

    if request.method == 'POST':
        form = EntryForm(request.POST)
        if form.is_valid():
            form.save()            
            return HttpResponseRedirect("/guestbook")
    else:
        form = EntryForm()

    return render_to_response('guestbook/add_edit_question.html', {'form': form})

This is a pretty straightforward implementation of an add / edit form. You’ll see this type of code structured many different ways, but I’ve found that this format works well for me. Notice how we’re using form_for_model when an id is not specified, whereas we are using form_for_instance when we have an object instance.

On thing that sometimes trips people up is the fact that form_for_model and form_for_instance return a Form class, not a Form instance. Its the reason we’re instantiating the EntryForm class in the else clause.

form.is_valid() is used to validate the contents of the form. If everything checks out the form is saved and the user is redirected back to our Guestbook listing page. If the form doesn’t validate then the user is returned back to the add/edit form.

When the form is returned this time the form object will be bound and have error information pertaining to the invalid information. The HTML output will include the validation errors near the field that has the problem. You can control how this information gets output by taking a more granular approach to form rendering.

And Now for the Template

A very basic template implementation typically will look like this:


{% extends "base.html" %}

{% block content %}
{{ form }}
{% endblock %}

This will display our form using the as_table specification, meaning that the form will be enclosed in an HTML table. The as_table rendering does not include the containing table tags, so be sure to include them in your template as I’ve shown above. Or better yet investigate the as_ul or as_p implementations.

That’s all it takes to get started with newforms. It’s pretty simple and we’ve only touched the surface of what’s possible. The real value in newforms is the built-in validation and clean data functionality. You can read more about each of these in the newforms documentation.

I’ve got an Introduction to NewForms screencast in the works that will give me the opportunity to dive into some of these things much deeper.

Update

James Bennett just posted an excellent post on NewForms. It’s definitely a required read.