formsweb2pymultiple-forms

web2py: multiple forms on one page


I am trying to make a form which shows all products from a group in a list. They can be given a quantity and added to a quote. Which is then stored in the database.

None of the automagical form options are working for me. So I've made each row showing information for a given product with the Quantity box and an add item button it's own form. But the loop which makes each form is doing something strange.

Controller:

products = db(db.product.group_id == productgroupnumber).select()
forms=[]
for product in products:
    form = FORM(TABLE(TR(TD(product.productname),
                         TD((product.purchasecost or 0)),
                         TD((product.monthlycost or 0)),
                         TD(INPUT(_type='number', _name='quantity')),
                         TD(INPUT(_type='submit', _value=T('Add to Offer')))
                         )
                      )
                )
    forms.append(form)

session.quotedproducts = []
if form.accepts(request, session, keepvalues = True):
    product = db(db.product.id == product_id).select().first()
    offeritem = [product_id, request.vars.quantity, product.purchasecost, product.monthlycost]
    session.quotedproducts.append(offeritem)
    response.flash = T("Item added to offer")`

For 2 rows. The View has the below 2 forms, with only one hidden div with the formkey and formname. So I can't name the forms in order to process them properly:

<form action="#" enctype="multipart/form-data" method="post">
    <table>
        <tr>
           <td>Block of 10 Phone Numbers</td>
           <td>19.0</td>
           <td>0</td>
           <td><input name="quantity" type="number" /></td>
           <td><input type="submit" value="Add to Offer" /></td>
       </tr>
    </table>
</form>     

<form action="#" enctype="multipart/form-data" method="post">
    <table>
        <tr>
            <td>100 Block of Phone Numbers</td>
            <td>149.0</td>
            <td>0</td>
            <td><input name="quantity" type="number" /></td>
            <td><input type="submit" value="Add to Offer" /></td>
        </tr>
    </table>

<!--Why is there only one of these??--> 
    <div style="display:none;">
        <input name="_formkey" type="hidden" value="b99bea37-f107-47f0-9b1b-9033c15e1193" />
        <input name="_formname" type="hidden" value="default" />
    </div>
</form>

How do I give the forms individual names (preferably product.id)? I tried adding the formname argument:

form.accepts(request, session, formname=product.id)

But this only names one form and the other is still named 'Default'.


Solution

  • In your code, you create multiple forms in the for loop, but after exiting the loop, you call form.accepts(). At that point, the value of form is the last form created in the loop, so only that form is processed.

    Note, when a form is initially created, the form.accepts (or the preferred form.process) method adds the _formname and _formkey hidden fields to the form (these are used for CSRF protection). When that same method is called after form submission, it additionally handles form validation. So, given your workflow, you must process all the forms both at creation and submission. Maybe something like this:

    products = db(db.product.group_id == productgroupnumber).select()
    forms = []
    for product in products:
        quantity_name = 'quantity_%s' % product.id
        form = FORM(TABLE(TR(TD(product.productname),
                             TD((product.purchasecost or 0)),
                             TD((product.monthlycost or 0)),
                             TD(INPUT(_type='number', _name=quantity_name)),
                             TD(INPUT(_type='submit', _value=T('Add to Offer')))
                             )
                          )
                    )
        if form.process(formname=product.id, keepvalues=True).accepted:
            offeritem = [product.id, form.vars[quantity_name],
                         product.purchasecost, product.monthlycost]
            session.quotedproducts.append(offeritem)
            response.flash = T("Item added to offer")
        forms.append(form)