How to test form values in a template? (simplified)

24 views
Skip to first unread message

Joshua Russo

unread,
Aug 7, 2011, 12:11:36 PM8/7/11
to django...@googlegroups.com
I realize that I went from too little information, to too much information in the previous post, so this is an attempt to find a middle ground.

What I'm building is the ability to have a list of checkable options, and depending on the setup for a give option it may have a text field to enter additional information. This text field can be either displayed as a single line text input or a multi-line textarea. 

My problem is that I want to have these different setup values available to control the validation and display of the form, but there doesn't seem a consistent way to test the values. When the form is POSTed back and isn't valid, the value out of the BoundField's value() metod for BooleanFields is a string, where it's a actual boolean on the initial creation of the page. I understand why this is, it's pulling from the POST data instead of the initial data. 

The way I over came this was to add a to_python() method to the BoundField class in django/forms/forms.py:

    def to_python(self):
        return self.field.to_python(self.value())

Below is what I'm doing. I'm using the new to_python() method are in the __init__ of the form, the save logic of the view, and the template at the end. I left out the CreateItemsSection() function that builds of the listItems dict because its convoluted. The listItems structure is in a form that's easily looped through in the template and the function creates (on the initial load) or inserts (on POST back) the forms of the formset. If it would be helpful I can post it but I think there should be enough information here.

Is there a better way to accomplish what I'm doing with the to_python() method? 

# Form class 

# I use the self["multiLineInfo"] pattern instead of self.fields["multiLineInfo"] 
# because the former gives a BoundField which merges the Field and the 
# post/initial data
class OrganizationItemForm(ModelForm):
    selected      = forms.BooleanField(required=False)  
    extraRequired = forms.BooleanField(required=False, 
        widget=forms.HiddenInput)
    multiLineInfo = forms.BooleanField(required=False, 
        widget=forms.HiddenInput)
    
    def __init__(self, *args, **kwargs):
        super(AuditModelForm, self).__init__(*args, **kwargs)
        if self["multiLineInfo"].to_python():
            self.fields["descr"].widget = forms.Textarea(attrs={"class":"descriptionEdit"})
        else:
            self.fields["descr"].widget = forms.TextInput(attrs={"class":"itemDescrEdit"})     
        self.fields["descr"].required = self["extraRequired"].to_python() and self["selected"].to_python()
        
    class Meta:
        model = OrganizationItem

# Model classes

class OrganizationItem(models.Model):
    organization = models.ForeignKey(Organization)
    item       = models.ForeignKey(ListItem)
    descr      = models.TextField("Description", blank=True)

# View 

def organizationAdd(request):

    OrganizationItemFormSet = formset_factory(OrganizationItemForm)

    if request.method == 'POST':
        form = OrganizationForm(request.POST)
        orgItemFormSet = OrganizationItemFormSet(request.POST)    
        if form.is_valid():
            form.save() 
            for itm in orgItemFormSet:
                if itm["selected"].to_python():
                    itm.instance.organization = form.instance
            if orgItemFormSet.is_valid():
                SaveItems(form.instance, orgItemFormSet)
                redirect("orgEdit", form.instance.pk)
            else:
                form.instance.delete()
        listItems = CreateItemsSection(orgItemFormSet, category__organization=True)        
    else:
        form = OrganizationForm()        
        orgItemFormSet = OrganizationItemFormSet() 
        listItems = CreateItemsSection(orgItemFormSet, "organization", category__organization=True)

    context = {
        'title': 'New organization', 
        'form': form, 
        'listItems': listItems, 
        'listItemsManagementForm': orgItemFormSet.management_form, 
        'isNew': True}
    
    return render_to_response('organizationEdit.html', context,
            context_instance=RequestContext(request))

# Template

{{ listItemsManagementForm }}
        <ul>
        {% for item in listItems %}            
            <li{% if item.form.descr.errors %} class="errors" {% endif %}>
            {{ item.form.descr.errors }}{% if item.form.descr.errors %}<br>{% endif %}
            <label>{{ item.form.selected }} {{ item.label }}</label>
            {% if item.form.extraRequired.to_python or item.descrLabel %}
                {% if item.form.multiLineInfo.to_python %}
                    <br>
                {% else %}
                    &ndash;
                {% endif %}
                <span class="listItemLabel{% if item.form.extraRequired.to_python %} required{% endif %}">{{ item.descrLabel }}</span>
                {% if item.form.multiLineInfo.to_python %}
                    <br>
                {% else %}
                    &nbsp;
                {% endif %}
                {{ item.form.descr }}
            {% else %}
                {{ item.form.descr.as_hidden }}
            {% endif %}
            {{ item.form.extraRequired }}
            {{ item.form.multiLineInfo }}
            </li>
        {% endfor %}
        </ul>

Shawn Milochik

unread,
Aug 7, 2011, 12:25:59 PM8/7/11
to django...@googlegroups.com
Are you saying that you want to show some form inputs conditionally
based upon configuration, for example for each user?

If that's your goal then it's very easy to do by adding the logic in the
form's __init__. Add/remove fields there and (possibly) override save()
if you have to take any additional action.

There's no need to do things like start modifying how the guts of
forms.Form works for something like this.

Shawn


Joshua Russo

unread,
Aug 7, 2011, 12:47:23 PM8/7/11
to django...@googlegroups.com
It's more that I want to have different ways of displaying the same form field. I want the text field to be on the same line as the checkbox when it's an input field and below it when it's displayed as a textarea. There's also the point of changing the required setting of the same field based on if the checkbox is selected or not. 

I guess what I'm trying to get at is, how should I access these values to determine the display and validation. If I don't use my method I will have to test for both the boolean value and the string value 'True' instead. That's what I'm trying to avoid.

Shawn Milochik

unread,
Aug 7, 2011, 1:01:07 PM8/7/11
to django...@googlegroups.com
The validation is easy. Override the form's clean() method to do any
validation which needs to check the value of more than one field. For
example, if you want a text box to be required sometimes, define it as
not required in the form, then check the boolean in clean() and raise a
forms.ValidationError if appropriate.

If you want to change which widget is being used and where it's
displayed based on the checkbox then you'd have to use AJAX to make that
work "live" anyway. Or maybe have two form fields, one of each type, and
dynamically hide one and show the other when the checkbox is changed.
You could also use your form's clean() override to assign the correct
value to your form field.

Example:
Say you have a field named named 'reason,' and you want to make it
a select box with hard-coded choices if a boolean for is True, but a
free-form text field if it's False.

If you have fields named reason_select and reason_text, you could
use JavaScript to select the appropriate one to show based on the checkbox.

Then, in form.clean(), you use the value of the checkbox to
determine whether to fill self.cleaned_data['reason'] with the value
from self.cleaned_data['reason_select'] or self.cleaned_data['reason_text'].

I hope this helps. I think we're zeroing in on your solution.

Reply all
Reply to author
Forward
0 new messages