Monday, March 3, 2008

Django ModelForm - replacement for form_for_model & form_for_instance

Each commit, Django gets more amazing.

I wrote about newforms library before. The library was a big step in Django form displaying and validating.

The first approach was to use forms.form_for_model() and forms.form_for_instance() respectively.

As Django programmer I found this a little confusing. Both were similar. The only difference was that form_for_instance() took an object instance for saving instead
of creating a new one. Recently django developers came up with a great idea - forms.ModelForm which combines both into one. It's great!

Just take a look at the example below.
Assume you want to create a new view function for creating & editing an object.
You only want to edit 3 fields (name, last_name, status). Also, you've created a
customized field type for "status", and want to use that.

Scenario #1: (forms.form_for_model && forms.form_for_instance)


def f_callback(field, **kwargs):
if field.name == "status":
return MyStatusField(**kwargs)
else:
return field.formfield(**kwargs)

def create_edit_customer(request, customer_id=None):

# check if object_id is None and if object exists
if object_id is None:
CustomerForm = forms.form_for_model(Customer,
formfield_callback=f_callback,
fields=('name','last_name','status')
else:
customer = get_object_or_404(Customer, id=customer_id)
CustomerForm = forms.form_for_instance(customer,
formfield_callback=f_callback,
fields=('name','last_name','status')

if request.method == "POST":
form = CustomerForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect('customer/added/')
else:
form = CustomerForm()

return render_to_response("customers/customer_form.html", {'form' : form })




You could create your own form_for_model & form_for_instance subclass (using forms.BaseForm for example), but it would be too complicated. Hope you don't use this form often :-)

Scenario #2 (ModelForm)

1. Longer version.


class CustomerModelForm(forms.ModelForm):

class Meta:
model = Customer
fields = ('name','last_name','status')

status = MyCustomerField()

def create_edit_customer(request, customer_id=None):

if customer_id is not None:
customer = get_object_or_404(Customer, id=customer_id)
else:
customer = None


if request.method == "POST":
form = CustomerModelForm(data=request.POST, instance=customer)
if form.is_valid():
form.save()
return HttpResponseRedirect("/customer/added/")
else:
form = CustomerModelForm(instance=customer)

return render_to_response("customers/customer_form.html", {'form' : form })



2. Shorter version.


class CustomerModelForm(forms.ModelForm):

class Meta:
model = Customer
fields = ('name','last_name','status')

status = MyCustomerField()

def create_edit_customer(request, customer_id=None):

if customer_id is not None:
customer = get_object_or_404(Customer, id=customer_id)
else:
customer = None

form = CustomerModelForm(data=request.POST or None, instance=customer)

if form.is_valid():
form.save()
return HttpResponseRedirect("/customer/added/")

return render_to_response("customers/customer_form.html", {'form' : form }



We are using one form class for editing & creating objects.
We can also subclass CustomerModelForm and edit/change the way the form looks
and behave (by adding field clean() method.

Django forms library looks better each day :-)

No comments: