The Five Steps of API-First Design
Back-End Digital Product Development Front-EndAnd why API-First Design should be a part of your next web service project Some of the greatest inventions have occurred by accident, but...
9 min read
Feb 19, 2007
The original Django tools for creating HTML forms and validating user supplied data (forms, manipulators, and validators) are currently being replaced by the newforms library, which is expected to be completed for version 1.0. The newforms library will be a nice change to Django, as it is much more elegant and easier to use than the oldforms library. Unfortunately, the inclusion of the newforms library will be backwards incompatible, so the development team is going to include both libraries in Django 1.0 to ease the transition, and then completely drop oldforms from the framework in later versions.
Thus, current Django developers are encouraged to embrace the newforms library as soon as possible, and new developers are discouraged from spending time learning the oldforms API altogether. This all sounds great, except that the newforms documentation is far from complete at this time. This article’s goal is to give you enough information so that you can get started using the library now.
If you want to learn all about Django, I’ll be teaching the Django Bootcamp at Big Nerd Ranch, April 2 – 6.
from django import forms
If you will be using the newforms library, you are encouraged to import it in the following way:
from django import newforms as forms
so that when the newforms library is renamed to “forms” in the future, you will not have to change your code.
For the examples we will be discussing, we will use the following model class:
from django.db import models
class Item(models.Model):
STATUS_CHOICES = (
('stk', 'In stock'),
('bac', 'Back ordered'),
('dis', 'Discontinued'),
('nav', 'Not available'),
)
serial_number = models.CharField(maxlength=15)
name = models.CharField(maxlength=100)
description = models.TextField(blank=True)
date_added = models.DateField(auto_now_add=True)
date_removed = models.DateField(blank =True, null=True)
date_backordered = models.DateField(blank=True, null=True)
comments = models.TextField(blank=True)
status = models.CharField(maxlength=3, choices=STATUS_CHOICES, default='stk')
The date_added field will automatically set the date when an Item is created, and we have listed some choices for the status field.
One of the neat things about newforms is that you can create them from specific model classes or their instances:
from django import newforms as forms
from yourproject.yourapplication.models import Item
ItemFormClass = forms.models.form_for_model(Item) # This creates a form *class* for Item
form = ItemFormClass() # Then you instantiate the form class
Note how the form_for_model() method created a class for us, and we have to make an instance to use the form. You can look at the HTML generated if you simply print the form in a shell session:
>>> print form
<tr><th><label for="id_serial_number">Serial number:</label></th><td>
<input id="id_serial_number" type="text" name="serial_number" maxlength="15" /></td></tr>
<tr><th><label for="id_name">Name:</label></th><td>
<input id="id_name" type="text" name="name" maxlength="100" /></td></tr>
<tr><th><label for="id_description">Description:</label></th><td>
<textarea name="description" id="id_description"></textarea></td></tr>
<tr><th><label for="id_date_added">Date added:</label></th><td>
<input type="text" name="date_added" id="id_date_added" /></td></tr>
<tr><th><label for="id_date_removed">Date removed:</label></th><td>
<input type="text" name="date_removed" id="id_date_removed" /></td></tr>
<tr><th><label for="id_date_backordered">Date backordered:</label></th><td>
<input type="text" name="date_backordered" id="id_date_backordered" /></td></tr>
<tr><th><label for="id_comments">Comments:</label></th><td>
<textarea name="comments" id="id_comments"></textarea></td></tr>
<tr><th><label for="id_status">Status:</label></th><td>
<input id="id_status" type="text" name="status" maxlength="3" /></td></tr>
By default, the form is laid-out as a table, and labels and id’s are created for each field in the form. This behavior can be easily changed to other lay-out conventions and tagging schemes. The current Django documentation explains all of these features quite nicely, so I will skip further details here. Also note that the form HTML is not embedded within <table></table> and <form></form> tags, and the <input type=”submit”> tag is missing. You have to supply those in your own form skeleton. As an example, see the listing of Add_Item.html template below:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>Add Item</title>
</head>
<body>
<form action="." method="post">
<table>
</table>
<input type="submit" value="Add Item" />
</form>
</body>
</html>
Now we are in a position to create a new Item entry with the following view:
from django.shortcuts import render_to_response
from django.http import HttpResponse, HttpResponseRedirect, Http404
from yourproject.yourapplication.models import Item
from django import newforms as forms
def add_item(request):
AddItemFormClass = forms.form_for_model(Item) # Create the form class
if request.POST:
form = AddItemFormClass(request.POST) # Instantiate and load POST data
if form.is_valid(): # Validate data
form.save() # Add the item
return HttpResponseRedirect('/index')
else:
form = AddItemFormClass() # Instantiate empty form
return render_to_response('Add_Item.html', {'form': form})
Note how we can initialize an AddItemFormClass instance with POST data or empty. Also, if there are validation errors, the form will automatically be redisplayed with errors listed above the appropriate fields. Finally, saving the form automatically creates a new instance of Item and saves it to the database. Cool!
However, not all is well. The Status field in our form is a text input and we must manually type a choice, such as ‘bac’. We need to change this to a popup menu to select a valid choice with a meaningful tag. Thankfully, we can easily correct this using a widget. Widgets are ways of displaying fields in HTML, and we can change the default widget for any field of our AddItemFormClass:
from django.shortcuts import render_to_response
from django.http import HttpResponse, HttpResponseRedirect, Http404
from NewForms.warehouse.models import Item
from django import newforms as forms
from django.newforms import widgets
def add_item(request):
AddItemFormClass = forms.form_for_model(Item) # Create the form class
AddItemFormClass.base_fields['status'].widget = widgets.Select(choices=Item.STATUS_CHOICES)
if request.POST:
form = AddItemFormClass(request.POST) # Instantiate and load POST data
if form.is_valid(): # Validate data
form.save() # Add the item
return HttpResponseRedirect('/index')
else:
form = AddItemFormClass() # Instantiate empty
return render_to_response('Add_Item.html', {'form': form})
You can look at django/newforms/widgets.py to find other available widgets.
For updates of Item entries, we want to pre-populate the form with data. How do we do that? The answer is to create the form class from an instance, not a model class.
def update_item(request, item_id):
current_item = Item.objects.get(id=item_id) # Get the Item instance
AddItemFormClass = forms.form_for_instance(current_item) # Create the form class
AddItemFormClass.base_fields['status'].widget = widgets.Select(choices=Item.STATUS_CHOICES)
if request.POST:
form = AddItemFormClass(request.POST) # Instantiate and load POST data
if form.is_valid(): # Validate data
form.save() # Save the item
return HttpResponseRedirect('/index')
else:
form = AddItemFormClass() # Instantiate empty
return render_to_response('Add_Item.html', {'form': form})
The two view functions add_item() and update_item() are quite similar and you probably would prefer to combine them into a single view. For the sake of simplicity and brevity, that task will be left as an exercise.
So far, we have easily written two views to add new Item entries to our database and to modify current ones. But this has been an all or nothing proposition up to this point: the form displays all the fields in the model. Frequently you will want to modify only some of the fields and leave others hidden. For example, our date_added field automatically sets the date, and we may not want the user to fiddle with that.
def update_description(request, item_id):
current_item = Item.objects.get(id=item_id) # Get the Item instance
AddItemFormClass = forms.form_for_instance(current_item) # Create the form class
AddItemFormClass.base_fields['serial_number'].widget = widgets.HiddenInput()
AddItemFormClass.base_fields['name'].widget = widgets.HiddenInput()
AddItemFormClass.base_fields['date_added'].widget = widgets.HiddenInput()
AddItemFormClass.base_fields['date_removed'].widget = widgets.HiddenInput()
AddItemFormClass.base_fields['date_backordered'].widget = widgets.HiddenInput()
AddItemFormClass.base_fields['comments'].widget = widgets.HiddenInput()
AddItemFormClass.base_fields['status'].widget = widgets.HiddenInput()
if request.POST:
form = AddItemFormClass(request.POST) # Instantiate and load POST data
if form.is_valid(): # Validate data
form.save() # Save the item
return HttpResponseRedirect('/index')
else:
form = AddItemFormClass() # Instantiate empty
return render_to_response('Add_Item.html', {'form': form})
By using the widget HiddenInput, we have left all fields out of the form except for the description, which can be edited.
You can even trick the form into not asking for a required field, and then add the data yourself, programmatically:
def add_item_without_name(request):
AddItemFormClass = forms.form_for_model(Item) # Create the form class
AddItemFormClass.base_fields['name'].widget = widgets.HiddenInput() # Hide name field
AddItemFormClass.base_fields['name'].required = False # Make name field not required
AddItemFormClass.base_fields['status'].widget = widgets.Select(choices=Item.STATUS_CHOICES)
if request.POST:
form = AddItemFormClass(request.POST) # Instantiate and load POST data
if form.is_valid(): # Validate data
newItem = form.save(commit=False) # Create the item
newItem.name = 'To be determined' # Add the data required by the model
newItem.save()
return HttpResponseRedirect('/index')
else:
form = AddItemFormClass() # Instantiate empty
return render_to_response('Add_Item.html', {'form': form})
Even though the name field is required by the model class, we tricked the form into not validating it by setting that field to not required. But if we were to save the new Item at this point, we would have a database error, so we create an Item instance with newItem = form.save(commit=False), where commit=False prevents writing to the DB. After newItem is created, we set a name for it and save the valid Item to the database.
Finally, what if you want to have a completely custom form, not attached to any specific database model? In that case you manually define a form class with any field specifications required:
class CustomForm(forms.Form):
serial_number = forms.CharField(max_length=15)
status = forms.CharField(max_length=3, widget=widgets.Select(choices=Item.STATUS_CHOICES))
This custom form will allow us to add an Item to the database with minimal information:
def add_minimal(request):
if request.POST:
form = CustomForm(request.POST) # Instantiate and load POST data
if form.is_valid(): # Validate data
newItem = Item(serial_number=form.clean_data['serial_number'],
name='To be determined',
description='No description',
comments='No comments',
status=form.clean_data['status'])
newItem.save()
return HttpResponseRedirect('/NewForms/index')
else:
form = CustomForm() # Instantiate empty
return render_to_response('Add_Item.html', {'form': form})
In this case, we just use the custom form to validate the user data, and just get that valid data to create a new Item instance and save it in the old fashioned way. If you want to show some data on the form, you could use the “initial” field argument when defining the CustomForm class or later using the base_fields dictionary.
class CustomForm(forms.Form):
serial_number = forms.CharField(max_length=15, initial='Acme_wazmo_123')
status = forms.CharField(max_length=3,
widget=widgets.Select(choices=Item.STATUS_CHOICES),
initial='stk')
I hope this article will provide enough information to get you going with newforms in a useful way. Happy coding.
And why API-First Design should be a part of your next web service project Some of the greatest inventions have occurred by accident, but...
WWDC 2018 began this week in San José, CA, with the usual excitement of thousands of developers from all over the world that could...
One of the greatest features of Alexa is that it functions as a personal assistant you can interact with without having to physically touch...