Django

Code

Changeset 7606

Show
Ignore:
Timestamp:
06/09/08 23:40:13 (6 months ago)
Author:
brosner
Message:

newforms-admin: Added documentation on formsets.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • django/branches/newforms-admin/docs/modelforms.txt

    r7436 r7606  
    370370Chances are these notes won't affect you unless you're trying to do something 
    371371tricky with subclassing. 
     372 
     373Model Formsets 
     374============== 
     375 
     376Similar to regular formsets there are a couple enhanced formset classes that 
     377provide all the right things to work with your models. Lets reuse the 
     378``Author`` model from above:: 
     379 
     380    >>> from django.newforms.models import modelformset_factory 
     381    >>> AuthorFormSet = modelformset_factory(Author) 
     382 
     383This will create a formset that is capable of working with the data associated 
     384to the ``Author`` model. It works just like a regular formset:: 
     385 
     386    >>> formset = AuthorFormSet() 
     387    >>> print formset 
     388    <input type="hidden" name="form-TOTAL_FORMS" value="1" id="id_form-TOTAL_FORMS" /><input type="hidden" name="form-INITIAL_FORMS" value="0" id="id_form-INITIAL_FORMS" /> 
     389    <tr><th><label for="id_form-0-name">Name:</label></th><td><input id="id_form-0-name" type="text" name="form-0-name" maxlength="100" /></td></tr> 
     390    <tr><th><label for="id_form-0-title">Title:</label></th><td><select name="form-0-title" id="id_form-0-title"> 
     391    <option value="" selected="selected">---------</option> 
     392    <option value="MR">Mr.</option> 
     393    <option value="MRS">Mrs.</option> 
     394    <option value="MS">Ms.</option> 
     395    </select></td></tr> 
     396    <tr><th><label for="id_form-0-birth_date">Birth date:</label></th><td><input type="text" name="form-0-birth_date" id="id_form-0-birth_date" /><input type="hidden" name="form-0-id" id="id_form-0-id" /></td></tr> 
     397 
     398.. note:: 
     399    One thing to note is that ``modelformset_factory`` uses ``formset_factory`` 
     400    and by default uses ``can_delete=True``. 
     401 
     402Changing the queryset 
     403~~~~~~~~~~~~~~~~~~~~~ 
     404 
     405By default when you create a formset from a model the queryset will be all 
     406objects in the model. This is best shown as ``Author.objects.all()``. This is 
     407configurable:: 
     408 
     409    >>> formset = AuthorFormSet(queryset=Author.objects.filter(name__startswith='O')) 
     410 
     411Alternatively, you can use a subclassing based approach:: 
     412 
     413    from django.newforms.models import BaseModelFormSet 
     414     
     415    class BaseAuthorFormSet(BaseModelFormSet): 
     416        def get_queryset(self): 
     417            return super(BaseAuthorFormSet, self).get_queryset().filter(name__startswith='O') 
     418 
     419Then your ``BaseAuthorFormSet`` would be passed into the factory function to 
     420be used as a base:: 
     421 
     422    >>> AuthorFormSet = modelformset_factory(Author, formset=BaseAuthorFormSet) 
     423 
     424Saving objects in the formset 
     425----------------------------- 
     426 
     427Similar to a ``ModelForm`` you can save the data into the model. This is done 
     428with the ``save()`` method on the formset:: 
     429 
     430    # create a formset instance with POST data. 
     431    >>> formset = AuthorFormSet(request.POST) 
     432     
     433    # assuming all is valid, save the data 
     434    >>> instances = formset.save() 
     435 
     436The ``save()`` method will return the instances that have been saved to the 
     437database. If an instance did not change in the bound data it will not be 
     438saved to the database and not found in ``instances`` in the above example. 
     439 
     440You can optionally pass in ``commit=False`` to ``save()`` to only return the 
     441model instances without any database interaction:: 
     442 
     443    # don't save to the database 
     444    >>> instances = formset.save(commit=False) 
     445    >>> for instance in instances: 
     446    ...     # do something with instance 
     447    ...     instance.save() 
     448 
     449This gives you the ability to attach data to the instances before saving them 
     450to the database. 
     451 
     452Using ``inlineformset_factory`` 
     453------------------------------- 
     454 
     455The ``inlineformset_factory`` is a helper to a common usage pattern of working 
     456with related objects through a foreign key. Suppose you have two models 
     457``Author`` and ``Book``. You want to create a formset that works with the 
     458books of a specific author. Here is how you could accomplish this:: 
     459 
     460    >>> from django.newforms.models import inlineformset_factory 
     461    >>> BookFormSet = inlineformset_factory(Author, Book) 
     462    >>> author = Author.objects.get(name=u'Orson Scott Card') 
     463    >>> formset = BookFormSet(instance=author) 
  • django/branches/newforms-admin/docs/newforms.txt

    r7436 r7606  
    21692169    <script type="text/javascript" src="http://media.example.com/whizbang.js"></script> 
    21702170 
     2171Formsets 
     2172======== 
     2173 
     2174A formset is a layer of abstraction to working with multiple forms on the same 
     2175page. It can be best compared to a data grid. Let's say you have the following 
     2176form:: 
     2177 
     2178    >>> from django import newforms as forms 
     2179    >>> class ArticleForm(forms.Form): 
     2180    ...     title = forms.CharField() 
     2181    ...     pub_date = forms.DateField() 
     2182 
     2183You might want to allow the user to create several articles at once. To create 
     2184a formset of ``ArticleForm``s you would do:: 
     2185 
     2186    >>> from django.newforms.formsets import formset_factory 
     2187    >>> ArticleFormSet = formset_factory(ArticleForm) 
     2188 
     2189You now have created a formset named ``ArticleFormSet``. The formset gives you 
     2190the ability to iterate over the forms in the formset and display them as you 
     2191would 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 
     2199As 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 
     2205Using initial data with a formset 
     2206--------------------------------- 
     2207 
     2208Initial data is what drives the main usability of a formset. As shown above 
     2209you can define the number of extra forms. What this means is that you are 
     2210telling the formset how many additional forms to show in addition to the 
     2211number of forms it generates from the initial data. Lets take a look at an 
     2212example:: 
     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 
     2229There are now a total of three forms showing above. One for the initial data 
     2230that was passed in and two extra forms. Also note that we are passing in a 
     2231list of dictionaries as the initial data. 
     2232 
     2233Formset validation 
     2234------------------ 
     2235 
     2236Validation with a formset is about identical to a regular ``Form``. There is 
     2237an ``is_valid`` method on the formset to provide a convenient way to validate 
     2238each form in the formset:: 
     2239 
     2240    >>> ArticleFormSet = formset_factory(ArticleForm) 
     2241    >>> formset = ArticleFormSet({}) 
     2242    >>> formset.is_valid() 
     2243    True 
     2244 
     2245We passed in no data to the formset which is resulting in a valid form. The 
     2246formset is smart enough to ignore extra forms that were not changed. If we 
     2247attempt 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 
     2261As we can see the formset properly performed validation and gave us the 
     2262expected errors. 
     2263 
     2264Understanding the ManagementForm 
     2265~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
     2266 
     2267You may have noticed the additional data that was required in the formset's 
     2268data above. This data is coming from the ``ManagementForm``. This form is 
     2269dealt with internally to the formset. If you don't use it, it will result in 
     2270an 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 
     2281It is used to keep track of how many form instances are being displayed. If 
     2282you are adding new forms via javascript, you should increment the count fields 
     2283in this form as well. 
     2284 
     2285Custom formset validation 
     2286~~~~~~~~~~~~~~~~~~~~~~~~~ 
     2287 
     2288A formset has a ``clean`` method similar to the one on a ``Form`` class. This 
     2289is 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 
     2304The formset ``clean`` method is called after all the ``Form.clean`` methods 
     2305have been called. The errors will be found using the ``non_form_errors()`` 
     2306method on the formset. 
     2307 
     2308Dealing with ordering and deletion of forms 
     2309------------------------------------------- 
     2310 
     2311Common use cases with a formset is dealing with ordering and deletion of the 
     2312form instances. This has been dealt with for you. The ``formset_factory`` 
     2313provides two optional parameters ``can_order`` and ``can_delete`` that will do 
     2314the extra work of adding the extra fields and providing simpler ways of 
     2315getting to that data. 
     2316 
     2317``can_order`` 
     2318~~~~~~~~~~~~~ 
     2319 
     2320Default: ``False`` 
     2321 
     2322Lets 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 
     2341This adds an additional field to each form. This new field is named ``ORDER`` 
     2342and is an ``forms.IntegerField``. For the forms that came from the initial 
     2343data it automatically assigned them a numeric value. Lets look at what will 
     2344happen 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 
     2375Default: ``False`` 
     2376 
     2377Lets 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 
     2397Similar to ``can_order`` this adds a new field to each form named ``DELETE`` 
     2398and is a ``forms.BooleanField``. When data comes through marking any of the 
     2399delete 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 
     2422Adding additional fields to a formset 
     2423------------------------------------- 
     2424 
     2425If you need to add additional fields to the formset this can be easily 
     2426accomplished. The formset base class provides an ``add_fields`` method. You 
     2427can simply override this method to add your own fields or even redefine the 
     2428default 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 
     2443Using a formsets in views and templates 
     2444--------------------------------------- 
     2445 
     2446Using a formset inside a view is as easy as using a regular ``Form`` class. 
     2447The only thing you will want to be aware of is making sure to use the 
     2448management 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 
     2460The ``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 
     2471However the above can be slightly shortcutted and let the formset itself deal 
     2472with the management form:: 
     2473 
     2474    <form method="POST" action=""> 
     2475        <table> 
     2476            {{ formset }} 
     2477        </table> 
     2478    </form> 
     2479 
     2480The above ends up calling the ``as_table`` method on the formset class. 
     2481 
    21712482More coming soon 
    21722483================