tags:

views:

1244

answers:

3

I have a web report that uses a Django form (new forms) for fields that control the query used to generate the report (start date, end date, ...). The issue I'm having is that the page should work using the form's initial values (unbound), but I can't access the cleaned_data field unless I call is_valid(). But is_valid() always fails on unbound forms.

It seems like Django's forms were designed with the use case of editing data such that an unbound form isn't really useful for anything other than displaying HTML.

For example, if I have:

if request.method == 'GET':
    form = MyForm()
else:
    form = MyForm(request.method.POST)

if form.is_valid():
    do_query(form.cleaned_data['start_date'], form.cleaned_data['end_date'])

is_valid() will fail if this is a GET (since it's unbound), and if I do:

if request.method == 'GET':
    form = MyForm()
    do_query(form.cleaned_data['start_date'], form.cleaned_data['end_date'])
else:
    form = MyForm(request.method.POST)
    if form.is_valid():
       do_query(form.cleaned_data['start_date'], form.cleaned_data['end_date'])

the first call to do_query triggers exceptions on form.cleaned_data, which is not a valid field because is_valid() has not been called. It seems like I have to do something like:

if request.method == 'GET':
    form = MyForm()
    do_query(form['start_date'].field.initial, form['end_date'].field.initial)
else:
    form = MyForm(request.method.POST)
    if form.is_valid():
       do_query(form.cleaned_data['start_date'], form.cleaned_data['end_date'])

that is, there isn't a common interface for retrieving the form's values between a bound form and an unbound one.

Does anyone see a cleaner way to do this?

A: 

You can pass a dictionary of initial values to your form:

if request.method == "GET":
    # calculate my_start_date and my_end_date here...
    form = MyForm( { 'start_date': my_start_date, 'end_date': my_end_date} )
...

See the official forms API documentation, where they demonstrate this.

edit: Based on answers from other users, maybe this is the cleanest solution:

if request.method == "GET":
    form = MyForm()
    form['start_date'] = form['start_date'].field.initial
    form['end_date'] = form['end_date'].field.initial
else:
    form = MyForm(request.method.POST)
if form.is_valid():
    do_query(form.cleaned_data['start_date'], form.cleaned_data['end_date'])

I haven't tried this though; can someone confirm that this works? I think this is better than creating a new method, because this approach doesn't require other code (possibly not written by you) to know about your new 'magic' accessor.

Justin Voss
Yes, but then the default values aren't defined with the form (like with initial=value in the field declarations). If you use the form in multiple places then you'll have to repeat your default values.
davidavr
+2  A: 

If you add this method to your form class:

def get_cleaned_or_initial(self, fieldname):
        if hasattr(self, 'cleaned_data'):
            return self.cleaned_data.get(fieldname)
        else:
            return self[fieldname].field.initial

you could then re-write your code as:

if request.method == 'GET':
    form = MyForm()
else:
    form = MyForm(request.method.POST)
    form.is_valid()

do_query(form.get_cleaned_or_initial('start_date'), form.get_cleaned_or_initial('end_date'))
Matthew Christensen
+1  A: 

Unbound means there is no data associated with form (either initial or provided later), so the validation may fail. As mentioned in other answers (and in your own conclusion), you have to provide initial values and check for both bound data and initial values.

The use case for forms is form processing and validation, so you must have some data to validate before you accessing cleaned_data.

zgoda