Best practice when testing form values in a template?

31 views
Skip to first unread message

Joshua Russo

unread,
Aug 2, 2011, 10:14:55 PM8/2/11
to django...@googlegroups.com
Ok, so I've created a fairly simple form:


class OrganizationItemForm(AuditModelForm):
    selected      = forms.BooleanField()  
    extraRequired = forms.BooleanField(widget=forms.HiddenInput)
    multiLineInfo = forms.BooleanField(widget=forms.HiddenInput)
    
    def __init__(self, *args, **kwargs):
        super(AuditModelForm, self).__init__(*args, **kwargs)
        if self["multiLineInfo"].value():
            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"].value()
        
    class Meta:
        model = OrganizationItem

I use this as a single checkbox entry that could optionally have either a single line edit, multi-line edit, or no text box at all

The applicable section of the template looks like this:


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

Keep in mind, this is a work in progress. I've not integrated the error output yet. Right now my problem is that if I simply click submit to test all of the error conditions, the value of the hidden fields is a string. This seems to be because to_python is only called for the cleaned value. The cleaned data is only available after the form passes validation.

What is the best practice for carrying data like this in a form and using it in the templates to govern conditionals?

Joshua Russo

unread,
Aug 3, 2011, 4:36:36 PM8/3/11
to django...@googlegroups.com
Or am I not using these concepts as intended?

Joshua Russo

unread,
Aug 3, 2011, 10:46:22 PM8/3/11
to django...@googlegroups.com
Really? Nothing? Do you need more information? From the lack of response I feel like I'm completely off the mark and nobody wants to tell me.

Shawn Milochik

unread,
Aug 3, 2011, 11:55:05 PM8/3/11
to django...@googlegroups.com
On 08/03/2011 10:46 PM, Joshua Russo wrote:
> Really? Nothing? Do you need more information? From the lack of
> response I feel like I'm completely off the mark and nobody wants to
> tell me. --

1: https://code.djangoproject.com/wiki/UsingTheMailingList

If you're not getting help then it's almost certainly because you
haven't made it easy for the all-volunteer army to help you.

You posted code that shows a subclass of an unknown form, you have
invalid code (it looks like self["multiLineInfo"] should be
self.fields["multiLineInfo"]), and a template but no view to explain the
context the template is processing.

What you should have done instead of pasting incomplete code is make the
simplest working (or non-working, as the case may be) example that
demonstrates your issue.

On top of that, replying with "Really? Nothing?" is a great way to get
ignored. Nobody wants to help someone with that attitude.

So, to recap:
Read this, then re-post:
https://code.djangoproject.com/wiki/UsingTheMailingList

Shawn


Joshua Russo

unread,
Aug 5, 2011, 9:40:18 PM8/5/11
to django...@googlegroups.com
Ok, I apologize. Here is a fuller representation of what I'm doing. I had a hard time figuring out how much was enough versus too much. This is simplified, but I think it represents what I'm trying to do. For instance, don't pay too much attention to the save logic in the view, I haven't actually really worked on it much yet.

To restate my scenario, I want to carry information in hidden fields of a form that determine how the form is displayed and functions. I realize I do have access to the self.Initial values in the form itself but I don't think that there's easy access to those values in the template. The value that I want seems to be the value() method of the BoundField, but run through the to_python() method of the field. I actually solved this by adding a to_python() method to the BoundField that does just this, but I would like to solve this without modifying the Django code. The samples below do not reflect my added 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"].value():
            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"].value() and self["selected"].value()
        
    class Meta:
        model = OrganizationItem

# Model classes

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

class ListItem(models.Model):
    category = models.ForeignKey(ListCategory)
    name     = models.CharField("Name", max_length=200)
    order    = models.IntegerField("Order")
    extraInfo     = models.BooleanField("Requires extra information")
    multiLineInfo = models.BooleanField("The display should use a multi-line edit box instead of a single line edit")
    descrLabel    = models.CharField("Extra information label", max_length=50,
        blank=True)

class ListCategory(modelUtils.MyModelAudit):
    catId = models.CharField("Category identifier", max_length=15,
        primary_key=True)
    name  = models.CharField("Name", max_length=100)
    order = models.IntegerField("Order")
    group = models.ForeignKey(ListGroup)
    organization = models.BooleanField("Should this be associated with an organization?")
    twoColumn    = models.BooleanField("Should the current list span 2 columns?")

class ListGroup(modelUtils.MyModelAudit):
    name = models.CharField("Name", max_length=15)
    descr = models.CharField("Description", max_length=100, blank=True)
    order = models.IntegerField("Order")

# View example
def organizationAdd(request):

    OrganizationItemFormSet = formset_factory(OrganizationItemForm)
    
    if request.method == 'POST':
        orgItemFormSet = OrganizationItemFormSet(request.POST)    
        orgItemFormSetIsValid = orgItemFormSet.is_valid()
        if orgItemFormSetIsValid:
            orgItemFormSet.save(commit=False)
    else:
        orgItemFormSet = OrganizationItemFormSet() 
        listItems = CreateItemsSection(orgItemFormSet, "organization", category__organization=True)

    context = {
        'listItems': listItems, 
        'listItemsManagementForm': orgItemFormSet.management_form}
    
    return render_to_response('organizationEdit.html', context,
            context_instance=RequestContext(request))


def CreateItemsSection(formset, idField="", objId=None, relation=None, **kwargs):
    """Construct the list of selectable items"""

    qs = ListItem.objects.filter(**kwargs)
    
    # Get the appliable list of groups
    groups = qs.values(
        "category__group", "category__group__descr"
        ).order_by(
        "category__group__order"
        ).distinct()
    
    # Convert the items relation into a dictionary of ListItem PK and XItems.descr
    selectedData = {}
    if relation is not None:
        selectedData = dict([(item.item__id, item.descr) for item in relation])
    
    retVal = []
    
    # Track the current form to display when the formset is populated from a postback
    formIndex = 0;
    postedData = len(formset) > 0
    
    # Assemble the information for each category group
    for grp in groups:
        # The current group's description
        curGrpDescr = grp["category__group__descr"]
        # Initialize the current group entry with a name and a list for the
        # category entries
        curGrp = {"group": curGrpDescr, "categories": []}
        # Get the applicable list of catagories
        categories = qs.filter(category__group=grp["category__group"]).values(
            "category", "category__name", "category__twoColumn").order_by("category__order").distinct()
        
        # These are used to determine when to start and end the table rows
        startRow = True
        endRow = False 
        
        # Assemble the category information
        for cat in categories:
            # The current category description (name field)
            curCatDescr = cat["category__name"] 
            # Get the current category's items
            listItems = qs.filter(category=cat["category"]).order_by("order")
            # Should the category display span 2 columns
            twoColumn = cat["category__twoColumn"]
            
            # Build the checkboxes, each contains a formset form
            items = []
            for li in listItems:
                    
                if postedData:
                    form = formset.forms[formIndex]                
                    formIndex += 1
                else:   
                    # Check if the item is selected items
                    isSelected = selectedData.has_key(li.pk)
                    data = ""
                    if isSelected:
                        data = selectedData[li.pk]
                    initialData = {
                        'selected': isSelected,
                        idField: objId,
                        'item': li.pk
                        'descr': data,
                        'extraRequired': li.extraInfo,
                        'multiLineInfo': li.multiLineInfo}
                    form = formset.add_form(initial=initialData)
    
                items.append({
                    'label': li.name
                    'descrLabel': li.descrLabel,
                    'category': cat["category"],
                    'order': li.order,
                    'form': form})
            
            # If the category should span 2 columns, ensure the last category
            # ends it's row and the current category both starts and ends this row
            if twoColumn:
                lastCatIndex = len(curGrp["categories"]) - 1
                if lastCatIndex > -1:
                    curGrp["categories"][lastCatIndex]["endRow"] = True
                
                startRow = True
                endRow = True
            
            # Append to the current group's list of categories
            curCat = {
                "category": curCatDescr, 
                "items": items,
                "startRow": startRow,
                "endRow": endRow,
                "twoColumn": twoColumn}
            curGrp["categories"].append(curCat)
            
            # Set the default row managment for the next category
            if not twoColumn:
                startRow = not startRow
                endRow = not endRow
            else:
                startRow = True
                endRow = False
            
        # Append the group to the return list
        retVal.append(curGrp)

    return retVal

# My custom formset for adding arbitrary forms to the set (I included this 
# because I didn't think that the CreateItemsSection function the save method 
# in the view would make sense without it.)
class XManagementForm(forms.formsets.ManagementForm):
    """
    ``ManagementForm`` is used to keep track of how many form instances
    are displayed on the page. If adding new forms via javascript, you should
    increment the count field of this form as well.
    """
    def __init__(self, data=None, extra_fields=None, *args, **kwargs):
        if extra_fields:
            for f, tv in extra_fields.items():
                self.base_fields[f] = tv[0](widget=forms.HiddenInput)
                if kwargs.has_key('initial'):
                    kwargs['initial'][f] = tv[1]
        super(XManagementForm, self).__init__(data, *args, **kwargs)


class BaseXFormSet(forms.formsets.BaseFormSet):

    def __init__(self, *args, **kwargs):
        self.added_forms = 0
        super(BaseXFormSet, self).__init__(*args, **kwargs)

    def _management_form(self):
        """Returns the ManagementForm instance for this FormSet."""
        if self.data or self.files:
            form = XManagementForm(self.data, auto_id=self.auto_id, prefix=self.prefix,
                extra_fields=self.extra_fields)
            #import pdb; pdb.set_trace()
            if not form.is_valid():
                raise forms.ValidationError('ManagementForm data is missing or has been tampered with')
        else:
            form = XManagementForm(auto_id=self.auto_id, prefix=self.prefix, extra_fields=self.extra_fields,
                initial={
                    forms.formsets.TOTAL_FORM_COUNT: self.total_form_count(),
                    forms.formsets.INITIAL_FORM_COUNT: self.initial_form_count()
            })
        return form
    management_form = property(_management_form)

    def initial_form_count(self):
        """Returns the number of forms that are required/pre-populated in this FormSet."""
        if not (self.data or self.files):
            initial_forms = self.initial and len(self.initial) or 0
            initial_forms += self.added_forms
            if initial_forms > self.max_num > 0:
                initial_forms = self.max_num
            return initial_forms
        return super(BaseXFormSet, self).initial_form_count()

    def add_form(self, existing_form=None, **kwargs):
        self.added_forms = self.added_forms + 1
        l_curIdx = len(self.forms)
        if existing_form:
            l_new_form = existing_form
            l_new_form.prefix = self.add_prefix(l_curIdx)
        else:
            l_new_form = self._construct_form(l_curIdx, **kwargs)
        l_new_form.form_index = l_curIdx
        self.forms.append(l_new_form)
        return l_new_form
        
    def save(self, commit=True):
        if hasattr(self.form, "save") and callable(self.form.save):
            for frm in self.forms:
                frm.save(commit=commit)
        else:
            raise NotImplementedError('The form %s does not implement the save() method' % self.form.__class__.__name__)
            

def formset_factory(form, formset=BaseXFormSet, extra=0, can_order=False,
                    can_delete=False, max_num=0, extra_fields=None):
    """Return a FormSet for the given form class."""
    attrs = {'form': form, 'extra': extra,
             'can_order': can_order, 'can_delete': can_delete,
             'max_num': max_num, 'extra_fields': extra_fields}
    return type(form.__name__ + 'FormSet', (formset,), attrs)


# Template

{{ listItemsManagementForm }}
{% for group in listItems %}
    {% for cat in group.categories %}
        {% if cat.startRow %}<tr>{% endif %}
        <td{% if cat.twoColumn %} colspan="2" {% endif %}>
        {{ cat.category }}<br>
        <ul>
        {% for item in cat.items %}            
            <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.value or item.descrLabel %}
                {% if item.form.multiLineInfo.value %}
                    <br>
                {% else %}
                    &ndash;
                {% endif %}
                <span class="listItemLabel{% if item.form.extraRequired.value %} required{% endif %}">{{ item.descrLabel }}</span>
                {% if item.form.multiLineInfo.value %}
                    <br>
                {% else %}
                    &nbsp;
                {% endif %}
                {{ item.form.descr }}
            {% else %}
                {{ item.form.descr.as_hidden }}
            {% endif %}
            {{ item.form.extraRequired }}
            {{ item.form.multiLineInfo }}
            </li>
        {% endfor %}
        </ul>
        </td>
        {% if cat.endRow %}</tr>{% endif %}
    {% endfor %}
{% endfor %}

Reply all
Reply to author
Forward
0 new messages