| | 2171 | Formsets |
|---|
| | 2172 | ======== |
|---|
| | 2173 | |
|---|
| | 2174 | A formset is a layer of abstraction to working with multiple forms on the same |
|---|
| | 2175 | page. It can be best compared to a data grid. Let's say you have the following |
|---|
| | 2176 | form:: |
|---|
| | 2177 | |
|---|
| | 2178 | >>> from django import newforms as forms |
|---|
| | 2179 | >>> class ArticleForm(forms.Form): |
|---|
| | 2180 | ... title = forms.CharField() |
|---|
| | 2181 | ... pub_date = forms.DateField() |
|---|
| | 2182 | |
|---|
| | 2183 | You might want to allow the user to create several articles at once. To create |
|---|
| | 2184 | a formset of ``ArticleForm``s you would do:: |
|---|
| | 2185 | |
|---|
| | 2186 | >>> from django.newforms.formsets import formset_factory |
|---|
| | 2187 | >>> ArticleFormSet = formset_factory(ArticleForm) |
|---|
| | 2188 | |
|---|
| | 2189 | You now have created a formset named ``ArticleFormSet``. The formset gives you |
|---|
| | 2190 | the ability to iterate over the forms in the formset and display them as you |
|---|
| | 2191 | would with a regular form:: |
|---|
| | 2192 | |
|---|
| | 2193 | >>> formset = ArticleFormSet() |
|---|
| | 2194 | >>> for form in formset.forms: |
|---|
| | 2195 | ... print form.as_table() |
|---|
| | 2196 | <tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" id="id_form-0-title" /></td></tr> |
|---|
| | 2197 | <tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" id="id_form-0-pub_date" /></td></tr> |
|---|
| | 2198 | |
|---|
| | 2199 | As you can see it only displayed one form. This is because by default the |
|---|
| | 2200 | ``formset_factory`` defines one extra form. This can be controlled with the |
|---|
| | 2201 | ``extra`` parameter:: |
|---|
| | 2202 | |
|---|
| | 2203 | >>> ArticleFormSet = formset_factory(ArticleForm, extra=2) |
|---|
| | 2204 | |
|---|
| | 2205 | Using initial data with a formset |
|---|
| | 2206 | --------------------------------- |
|---|
| | 2207 | |
|---|
| | 2208 | Initial data is what drives the main usability of a formset. As shown above |
|---|
| | 2209 | you can define the number of extra forms. What this means is that you are |
|---|
| | 2210 | telling the formset how many additional forms to show in addition to the |
|---|
| | 2211 | number of forms it generates from the initial data. Lets take a look at an |
|---|
| | 2212 | example:: |
|---|
| | 2213 | |
|---|
| | 2214 | >>> ArticleFormSet = formset_factory(ArticleForm, extra=2) |
|---|
| | 2215 | >>> formset = ArticleFormSet(initial=[ |
|---|
| | 2216 | ... {'title': u'Django is now open source', |
|---|
| | 2217 | ... 'pub_date': datetime.date.today()}, |
|---|
| | 2218 | ... ]) |
|---|
| | 2219 | |
|---|
| | 2220 | >>> for form in formset.forms: |
|---|
| | 2221 | ... print form.as_table() |
|---|
| | 2222 | <tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" value="Django is now open source" id="id_form-0-title" /></td></tr> |
|---|
| | 2223 | <tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" value="2008-05-12" id="id_form-0-pub_date" /></td></tr> |
|---|
| | 2224 | <tr><th><label for="id_form-1-title">Title:</label></th><td><input type="text" name="form-1-title" id="id_form-1-title" /></td></tr> |
|---|
| | 2225 | <tr><th><label for="id_form-1-pub_date">Pub date:</label></th><td><input type="text" name="form-1-pub_date" id="id_form-1-pub_date" /></td></tr> |
|---|
| | 2226 | <tr><th><label for="id_form-2-title">Title:</label></th><td><input type="text" name="form-2-title" id="id_form-2-title" /></td></tr> |
|---|
| | 2227 | <tr><th><label for="id_form-2-pub_date">Pub date:</label></th><td><input type="text" name="form-2-pub_date" id="id_form-2-pub_date" /></td></tr> |
|---|
| | 2228 | |
|---|
| | 2229 | There are now a total of three forms showing above. One for the initial data |
|---|
| | 2230 | that was passed in and two extra forms. Also note that we are passing in a |
|---|
| | 2231 | list of dictionaries as the initial data. |
|---|
| | 2232 | |
|---|
| | 2233 | Formset validation |
|---|
| | 2234 | ------------------ |
|---|
| | 2235 | |
|---|
| | 2236 | Validation with a formset is about identical to a regular ``Form``. There is |
|---|
| | 2237 | an ``is_valid`` method on the formset to provide a convenient way to validate |
|---|
| | 2238 | each form in the formset:: |
|---|
| | 2239 | |
|---|
| | 2240 | >>> ArticleFormSet = formset_factory(ArticleForm) |
|---|
| | 2241 | >>> formset = ArticleFormSet({}) |
|---|
| | 2242 | >>> formset.is_valid() |
|---|
| | 2243 | True |
|---|
| | 2244 | |
|---|
| | 2245 | We passed in no data to the formset which is resulting in a valid form. The |
|---|
| | 2246 | formset is smart enough to ignore extra forms that were not changed. If we |
|---|
| | 2247 | attempt to provide an article, but fail to do so:: |
|---|
| | 2248 | |
|---|
| | 2249 | >>> data = { |
|---|
| | 2250 | ... 'form-TOTAL_FORMS': u'1', |
|---|
| | 2251 | ... 'form-INITIAL_FORMS': u'1', |
|---|
| | 2252 | ... 'form-0-title': u'Test', |
|---|
| | 2253 | ... 'form-0-pub_date': u'', |
|---|
| | 2254 | ... } |
|---|
| | 2255 | >>> formset = ArticleFormSet(data) |
|---|
| | 2256 | >>> formset.is_valid() |
|---|
| | 2257 | False |
|---|
| | 2258 | >>> formset.errors |
|---|
| | 2259 | [{'pub_date': [u'This field is required.']}] |
|---|
| | 2260 | |
|---|
| | 2261 | As we can see the formset properly performed validation and gave us the |
|---|
| | 2262 | expected errors. |
|---|
| | 2263 | |
|---|
| | 2264 | Understanding the ManagementForm |
|---|
| | 2265 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| | 2266 | |
|---|
| | 2267 | You may have noticed the additional data that was required in the formset's |
|---|
| | 2268 | data above. This data is coming from the ``ManagementForm``. This form is |
|---|
| | 2269 | dealt with internally to the formset. If you don't use it, it will result in |
|---|
| | 2270 | an exception:: |
|---|
| | 2271 | |
|---|
| | 2272 | >>> data = { |
|---|
| | 2273 | ... 'form-0-title': u'Test', |
|---|
| | 2274 | ... 'form-0-pub_date': u'', |
|---|
| | 2275 | ... } |
|---|
| | 2276 | >>> formset = ArticleFormSet(data) |
|---|
| | 2277 | Traceback (most recent call last): |
|---|
| | 2278 | ... |
|---|
| | 2279 | django.newforms.util.ValidationError: [u'ManagementForm data is missing or has been tampered with'] |
|---|
| | 2280 | |
|---|
| | 2281 | It is used to keep track of how many form instances are being displayed. If |
|---|
| | 2282 | you are adding new forms via javascript, you should increment the count fields |
|---|
| | 2283 | in this form as well. |
|---|
| | 2284 | |
|---|
| | 2285 | Custom formset validation |
|---|
| | 2286 | ~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| | 2287 | |
|---|
| | 2288 | A formset has a ``clean`` method similar to the one on a ``Form`` class. This |
|---|
| | 2289 | is where you define your own validation that deals at the formset level:: |
|---|
| | 2290 | |
|---|
| | 2291 | >>> from django.newforms.formsets import BaseFormSet |
|---|
| | 2292 | |
|---|
| | 2293 | >>> class BaseArticleFormSet(BaseFormSet): |
|---|
| | 2294 | ... def clean(self): |
|---|
| | 2295 | ... raise forms.ValidationError, u'An error occured.' |
|---|
| | 2296 | |
|---|
| | 2297 | >>> ArticleFormSet = formset_factory(ArticleForm, formset=BaseArticleFormSet) |
|---|
| | 2298 | >>> formset = ArticleFormSet({}) |
|---|
| | 2299 | >>> formset.is_valid() |
|---|
| | 2300 | False |
|---|
| | 2301 | >>> formset.non_form_errors() |
|---|
| | 2302 | [u'An error occured.'] |
|---|
| | 2303 | |
|---|
| | 2304 | The formset ``clean`` method is called after all the ``Form.clean`` methods |
|---|
| | 2305 | have been called. The errors will be found using the ``non_form_errors()`` |
|---|
| | 2306 | method on the formset. |
|---|
| | 2307 | |
|---|
| | 2308 | Dealing with ordering and deletion of forms |
|---|
| | 2309 | ------------------------------------------- |
|---|
| | 2310 | |
|---|
| | 2311 | Common use cases with a formset is dealing with ordering and deletion of the |
|---|
| | 2312 | form instances. This has been dealt with for you. The ``formset_factory`` |
|---|
| | 2313 | provides two optional parameters ``can_order`` and ``can_delete`` that will do |
|---|
| | 2314 | the extra work of adding the extra fields and providing simpler ways of |
|---|
| | 2315 | getting to that data. |
|---|
| | 2316 | |
|---|
| | 2317 | ``can_order`` |
|---|
| | 2318 | ~~~~~~~~~~~~~ |
|---|
| | 2319 | |
|---|
| | 2320 | Default: ``False`` |
|---|
| | 2321 | |
|---|
| | 2322 | Lets create a formset with the ability to order:: |
|---|
| | 2323 | |
|---|
| | 2324 | >>> ArticleFormSet = formset_factory(ArticleForm, can_order=True) |
|---|
| | 2325 | >>> formset = ArticleFormSet(initial=[ |
|---|
| | 2326 | ... {'title': u'Article #1', 'pub_date': datetime.date(2008, 5, 10)}, |
|---|
| | 2327 | ... {'title': u'Article #2', 'pub_date': datetime.date(2008, 5, 11)}, |
|---|
| | 2328 | ... ]) |
|---|
| | 2329 | >>> for form in formset.forms: |
|---|
| | 2330 | ... print form.as_table() |
|---|
| | 2331 | <tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" value="Article #1" id="id_form-0-title" /></td></tr> |
|---|
| | 2332 | <tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" value="2008-05-10" id="id_form-0-pub_date" /></td></tr> |
|---|
| | 2333 | <tr><th><label for="id_form-0-ORDER">Order:</label></th><td><input type="text" name="form-0-ORDER" value="1" id="id_form-0-ORDER" /></td></tr> |
|---|
| | 2334 | <tr><th><label for="id_form-1-title">Title:</label></th><td><input type="text" name="form-1-title" value="Article #2" id="id_form-1-title" /></td></tr> |
|---|
| | 2335 | <tr><th><label for="id_form-1-pub_date">Pub date:</label></th><td><input type="text" name="form-1-pub_date" value="2008-05-11" id="id_form-1-pub_date" /></td></tr> |
|---|
| | 2336 | <tr><th><label for="id_form-1-ORDER">Order:</label></th><td><input type="text" name="form-1-ORDER" value="2" id="id_form-1-ORDER" /></td></tr> |
|---|
| | 2337 | <tr><th><label for="id_form-2-title">Title:</label></th><td><input type="text" name="form-2-title" id="id_form-2-title" /></td></tr> |
|---|
| | 2338 | <tr><th><label for="id_form-2-pub_date">Pub date:</label></th><td><input type="text" name="form-2-pub_date" id="id_form-2-pub_date" /></td></tr> |
|---|
| | 2339 | <tr><th><label for="id_form-2-ORDER">Order:</label></th><td><input type="text" name="form-2-ORDER" id="id_form-2-ORDER" /></td></tr> |
|---|
| | 2340 | |
|---|
| | 2341 | This adds an additional field to each form. This new field is named ``ORDER`` |
|---|
| | 2342 | and is an ``forms.IntegerField``. For the forms that came from the initial |
|---|
| | 2343 | data it automatically assigned them a numeric value. Lets look at what will |
|---|
| | 2344 | happen when the user changes these values:: |
|---|
| | 2345 | |
|---|
| | 2346 | >>> data = { |
|---|
| | 2347 | ... 'form-TOTAL_FORMS': u'3', |
|---|
| | 2348 | ... 'form-INITIAL_FORMS': u'2', |
|---|
| | 2349 | ... 'form-0-title': u'Article #1', |
|---|
| | 2350 | ... 'form-0-pub_date': u'2008-05-10', |
|---|
| | 2351 | ... 'form-0-ORDER': u'2', |
|---|
| | 2352 | ... 'form-1-title': u'Article #2', |
|---|
| | 2353 | ... 'form-1-pub_date': u'2008-05-11', |
|---|
| | 2354 | ... 'form-1-ORDER': u'1', |
|---|
| | 2355 | ... 'form-2-title': u'Article #3', |
|---|
| | 2356 | ... 'form-2-pub_date': u'2008-05-01', |
|---|
| | 2357 | ... 'form-2-ORDER': u'0', |
|---|
| | 2358 | ... } |
|---|
| | 2359 | |
|---|
| | 2360 | >>> formset = ArticleFormSet(data, initial=[ |
|---|
| | 2361 | ... {'title': u'Article #1', 'pub_date': datetime.date(2008, 5, 10)}, |
|---|
| | 2362 | ... {'title': u'Article #2', 'pub_date': datetime.date(2008, 5, 11)}, |
|---|
| | 2363 | ... ]) |
|---|
| | 2364 | >>> formset.is_valid() |
|---|
| | 2365 | True |
|---|
| | 2366 | >>> for form in formset.ordered_forms: |
|---|
| | 2367 | ... print form.cleaned_data |
|---|
| | 2368 | {'pub_date': datetime.date(2008, 5, 1), 'ORDER': 0, 'title': u'Article #3'} |
|---|
| | 2369 | {'pub_date': datetime.date(2008, 5, 11), 'ORDER': 1, 'title': u'Article #2'} |
|---|
| | 2370 | {'pub_date': datetime.date(2008, 5, 10), 'ORDER': 2, 'title': u'Article #1'} |
|---|
| | 2371 | |
|---|
| | 2372 | ``can_delete`` |
|---|
| | 2373 | ~~~~~~~~~~~~~~ |
|---|
| | 2374 | |
|---|
| | 2375 | Default: ``False`` |
|---|
| | 2376 | |
|---|
| | 2377 | Lets create a formset with the ability to delete:: |
|---|
| | 2378 | |
|---|
| | 2379 | >>> ArticleFormSet = formset_factory(ArticleForm, can_delete=True) |
|---|
| | 2380 | >>> formset = ArticleFormSet(initial=[ |
|---|
| | 2381 | ... {'title': u'Article #1', 'pub_date': datetime.date(2008, 5, 10)}, |
|---|
| | 2382 | ... {'title': u'Article #2', 'pub_date': datetime.date(2008, 5, 11)}, |
|---|
| | 2383 | ... ]) |
|---|
| | 2384 | >>> for form in formset.forms: |
|---|
| | 2385 | .... print form.as_table() |
|---|
| | 2386 | <input type="hidden" name="form-TOTAL_FORMS" value="3" id="id_form-TOTAL_FORMS" /><input type="hidden" name="form-INITIAL_FORMS" value="2" id="id_form-INITIAL_FORMS" /> |
|---|
| | 2387 | <tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" value="Article #1" id="id_form-0-title" /></td></tr> |
|---|
| | 2388 | <tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" value="2008-05-10" id="id_form-0-pub_date" /></td></tr> |
|---|
| | 2389 | <tr><th><label for="id_form-0-DELETE">Delete:</label></th><td><input type="checkbox" name="form-0-DELETE" id="id_form-0-DELETE" /></td></tr> |
|---|
| | 2390 | <tr><th><label for="id_form-1-title">Title:</label></th><td><input type="text" name="form-1-title" value="Article #2" id="id_form-1-title" /></td></tr> |
|---|
| | 2391 | <tr><th><label for="id_form-1-pub_date">Pub date:</label></th><td><input type="text" name="form-1-pub_date" value="2008-05-11" id="id_form-1-pub_date" /></td></tr> |
|---|
| | 2392 | <tr><th><label for="id_form-1-DELETE">Delete:</label></th><td><input type="checkbox" name="form-1-DELETE" id="id_form-1-DELETE" /></td></tr> |
|---|
| | 2393 | <tr><th><label for="id_form-2-title">Title:</label></th><td><input type="text" name="form-2-title" id="id_form-2-title" /></td></tr> |
|---|
| | 2394 | <tr><th><label for="id_form-2-pub_date">Pub date:</label></th><td><input type="text" name="form-2-pub_date" id="id_form-2-pub_date" /></td></tr> |
|---|
| | 2395 | <tr><th><label for="id_form-2-DELETE">Delete:</label></th><td><input type="checkbox" name="form-2-DELETE" id="id_form-2-DELETE" /></td></tr> |
|---|
| | 2396 | |
|---|
| | 2397 | Similar to ``can_order`` this adds a new field to each form named ``DELETE`` |
|---|
| | 2398 | and is a ``forms.BooleanField``. When data comes through marking any of the |
|---|
| | 2399 | delete fields you can access them with ``deleted_forms``:: |
|---|
| | 2400 | |
|---|
| | 2401 | >>> data = { |
|---|
| | 2402 | ... 'form-TOTAL_FORMS': u'3', |
|---|
| | 2403 | ... 'form-INITIAL_FORMS': u'2', |
|---|
| | 2404 | ... 'form-0-title': u'Article #1', |
|---|
| | 2405 | ... 'form-0-pub_date': u'2008-05-10', |
|---|
| | 2406 | ... 'form-0-DELETE': u'on', |
|---|
| | 2407 | ... 'form-1-title': u'Article #2', |
|---|
| | 2408 | ... 'form-1-pub_date': u'2008-05-11', |
|---|
| | 2409 | ... 'form-1-DELETE': u'', |
|---|
| | 2410 | ... 'form-2-title': u'', |
|---|
| | 2411 | ... 'form-2-pub_date': u'', |
|---|
| | 2412 | ... 'form-2-DELETE': u'', |
|---|
| | 2413 | ... } |
|---|
| | 2414 | |
|---|
| | 2415 | >>> formset = ArticleFormSet(data, initial=[ |
|---|
| | 2416 | ... {'title': u'Article #1', 'pub_date': datetime.date(2008, 5, 10)}, |
|---|
| | 2417 | ... {'title': u'Article #2', 'pub_date': datetime.date(2008, 5, 11)}, |
|---|
| | 2418 | ... ]) |
|---|
| | 2419 | >>> [form.cleaned_data for form in formset.deleted_forms] |
|---|
| | 2420 | [{'DELETE': True, 'pub_date': datetime.date(2008, 5, 10), 'title': u'Article #1'}] |
|---|
| | 2421 | |
|---|
| | 2422 | Adding additional fields to a formset |
|---|
| | 2423 | ------------------------------------- |
|---|
| | 2424 | |
|---|
| | 2425 | If you need to add additional fields to the formset this can be easily |
|---|
| | 2426 | accomplished. The formset base class provides an ``add_fields`` method. You |
|---|
| | 2427 | can simply override this method to add your own fields or even redefine the |
|---|
| | 2428 | default fields/attributes of the order and deletion fields:: |
|---|
| | 2429 | |
|---|
| | 2430 | >>> class BaseArticleFormSet(BaseFormSet): |
|---|
| | 2431 | ... def add_fields(self, form, index): |
|---|
| | 2432 | ... super(BaseArticleFormSet, self).add_fields(form, index) |
|---|
| | 2433 | ... form.fields["my_field"] = forms.CharField() |
|---|
| | 2434 | |
|---|
| | 2435 | >>> ArticleFormSet = formset_factory(ArticleForm, formset=BaseArticleFormSet) |
|---|
| | 2436 | >>> formset = ArticleFormSet() |
|---|
| | 2437 | >>> for form in formset.forms: |
|---|
| | 2438 | ... print form.as_table() |
|---|
| | 2439 | <tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" id="id_form-0-title" /></td></tr> |
|---|
| | 2440 | <tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" id="id_form-0-pub_date" /></td></tr> |
|---|
| | 2441 | <tr><th><label for="id_form-0-my_field">My field:</label></th><td><input type="text" name="form-0-my_field" id="id_form-0-my_field" /></td></tr> |
|---|
| | 2442 | |
|---|
| | 2443 | Using a formsets in views and templates |
|---|
| | 2444 | --------------------------------------- |
|---|
| | 2445 | |
|---|
| | 2446 | Using a formset inside a view is as easy as using a regular ``Form`` class. |
|---|
| | 2447 | The only thing you will want to be aware of is making sure to use the |
|---|
| | 2448 | management form inside the template. Lets look at a sample view:: |
|---|
| | 2449 | |
|---|
| | 2450 | def manage_articles(request): |
|---|
| | 2451 | ArticleFormSet = formset_factory(ArticleForm) |
|---|
| | 2452 | if request.method == 'POST': |
|---|
| | 2453 | formset = ArticleFormSet(request.POST, request.FILES) |
|---|
| | 2454 | if formset.is_valid(): |
|---|
| | 2455 | # do something with the formset.cleaned_data |
|---|
| | 2456 | else: |
|---|
| | 2457 | formset = ArticleFormSet() |
|---|
| | 2458 | return render_to_response('manage_articles.html', {'formset': formset}) |
|---|
| | 2459 | |
|---|
| | 2460 | The ``manage_articles.html`` template might look like this:: |
|---|
| | 2461 | |
|---|
| | 2462 | <form method="POST" action=""> |
|---|
| | 2463 | {{ formset.management_form }} |
|---|
| | 2464 | <table> |
|---|
| | 2465 | {% for form in formset.forms %} |
|---|
| | 2466 | {{ form }} |
|---|
| | 2467 | {% endfor %} |
|---|
| | 2468 | </table> |
|---|
| | 2469 | </form> |
|---|
| | 2470 | |
|---|
| | 2471 | However the above can be slightly shortcutted and let the formset itself deal |
|---|
| | 2472 | with the management form:: |
|---|
| | 2473 | |
|---|
| | 2474 | <form method="POST" action=""> |
|---|
| | 2475 | <table> |
|---|
| | 2476 | {{ formset }} |
|---|
| | 2477 | </table> |
|---|
| | 2478 | </form> |
|---|
| | 2479 | |
|---|
| | 2480 | The above ends up calling the ``as_table`` method on the formset class. |
|---|
| | 2481 | |
|---|