Author: russellm
Date: 2007-05-12 09:42:46 -0500 (Sat, 12 May 2007)
New Revision: 5202

Modified:
   django/trunk/django/newforms/models.py
   django/trunk/docs/newforms.txt
   django/trunk/tests/modeltests/model_forms/models.py
Log:
Added docs for form_for_model and form_for_instance, and added a fields 
argument so it is easy to create forms from a subset of model fields.


Modified: django/trunk/django/newforms/models.py
===================================================================
--- django/trunk/django/newforms/models.py      2007-05-12 12:22:50 UTC (rev 
5201)
+++ django/trunk/django/newforms/models.py      2007-05-12 14:42:46 UTC (rev 
5202)
@@ -12,18 +12,8 @@
 __all__ = ('save_instance', 'form_for_model', 'form_for_instance', 
'form_for_fields',
            'ModelChoiceField', 'ModelMultipleChoiceField')
 
-def model_save(self, commit=True):
+def save_instance(form, instance, fields=None, fail_message='saved', 
commit=True):
     """
-    Creates and returns model instance according to self.clean_data.
-
-    This method is created for any form_for_model Form.
-    """
-    if self.errors:
-        raise ValueError("The %s could not be created because the data didn't 
validate." % self._model._meta.object_name)
-    return save_instance(self, self._model(), commit)
-
-def save_instance(form, instance, commit=True):
-    """
     Saves bound Form ``form``'s clean_data into model instance ``instance``.
 
     Assumes ``form`` has a field for every non-AutoField database field in
@@ -33,15 +23,19 @@
     from django.db import models
     opts = instance.__class__._meta
     if form.errors:
-        raise ValueError("The %s could not be changed because the data didn't 
validate." % opts.object_name)
+        raise ValueError("The %s could not be %s because the data didn't 
validate." % (opts.object_name, fail_message))
     clean_data = form.clean_data
     for f in opts.fields:
         if not f.editable or isinstance(f, models.AutoField) or not f.name in 
clean_data:
             continue
+        if fields and f.name not in fields:
+            continue
         setattr(instance, f.name, clean_data[f.name])
     if commit:
         instance.save()
         for f in opts.many_to_many:
+            if fields and f.name not in fields:
+                continue
             if f.name in clean_data:
                 setattr(instance, f.attname, clean_data[f.name])
     # GOTCHA: If many-to-many data is given and commit=False, the many-to-many
@@ -50,13 +44,19 @@
     # exception in that case.
     return instance
 
-def make_instance_save(instance):
-    "Returns the save() method for a form_for_instance Form."
+def make_model_save(model, fields, fail_message):
+    "Returns the save() method for a Form."
     def save(self, commit=True):
-        return save_instance(self, instance, commit)
+        return save_instance(self, model(), fields, fail_message, commit)
     return save
+    
+def make_instance_save(instance, fields, fail_message):
+    "Returns the save() method for a Form."
+    def save(self, commit=True):
+        return save_instance(self, instance, fields, fail_message, commit)
+    return save
 
-def form_for_model(model, form=BaseForm, formfield_callback=lambda f: 
f.formfield()):
+def form_for_model(model, form=BaseForm, fields=None, 
formfield_callback=lambda f: f.formfield()):
     """
     Returns a Form class for the given Django model class.
 
@@ -71,13 +71,16 @@
     for f in opts.fields + opts.many_to_many:
         if not f.editable:
             continue
+        if fields and not f.name in fields:
+            continue
         formfield = formfield_callback(f)
         if formfield:
             field_list.append((f.name, formfield))
-    fields = SortedDictFromList(field_list)
-    return type(opts.object_name + 'Form', (form,), {'base_fields': fields, 
'_model': model, 'save': model_save})
+    base_fields = SortedDictFromList(field_list)
+    return type(opts.object_name + 'Form', (form,), 
+        {'base_fields': base_fields, '_model': model, 'save': 
make_model_save(model, fields, 'created')})
 
-def form_for_instance(instance, form=BaseForm, formfield_callback=lambda f, 
**kwargs: f.formfield(**kwargs)):
+def form_for_instance(instance, form=BaseForm, fields=None, 
formfield_callback=lambda f, **kwargs: f.formfield(**kwargs)):
     """
     Returns a Form class for the given Django model instance.
 
@@ -94,13 +97,15 @@
     for f in opts.fields + opts.many_to_many:
         if not f.editable:
             continue
+        if fields and not f.name in fields:
+            continue
         current_value = f.value_from_object(instance)
         formfield = formfield_callback(f, initial=current_value)
         if formfield:
             field_list.append((f.name, formfield))
-    fields = SortedDictFromList(field_list)
+    base_fields = SortedDictFromList(field_list)
     return type(opts.object_name + 'InstanceForm', (form,),
-        {'base_fields': fields, '_model': model, 'save': 
make_instance_save(instance)})
+        {'base_fields': base_fields, '_model': model, 'save': 
make_instance_save(instance, fields, 'changed')})
 
 def form_for_fields(field_list):
     "Returns a Form class for the given list of Django database field 
instances."

Modified: django/trunk/docs/newforms.txt
===================================================================
--- django/trunk/docs/newforms.txt      2007-05-12 12:22:50 UTC (rev 5201)
+++ django/trunk/docs/newforms.txt      2007-05-12 14:42:46 UTC (rev 5202)
@@ -870,6 +870,161 @@
 mentioned above (``required``, ``label``, ``initial``, ``widget``,
 ``help_text``).
 
+Generating forms for models
+===========================
+
+Although you can build customized forms by specifying the fields manually,
+in many cases you won't need to. Django provides helper methods to simplify the
+common cases of form creation.
+
+``form_for_model()``
+--------------------
+
+This method creates a form based upon the definition for a specific model. 
+``form_for_model()`` examines the model definition, and creates a new form
+class that contains a form field for each model field that is defined.
+
+The type of fields produced on the generated form is determined by the type
+of the model fields. For example, a ``CharField`` on a model will be 
+represented with a ``CharField`` on the form. Each ``ManyToManyField`` 
+on the model will be represented with a ``MultipleChoiceField`` on the 
+form. Each ``ForeignKey`` will be represented with a ``ChoiceField``.
+A ``ChoiceField`` is also used for any model field that has a ``choices`` 
+attribute specified.
+
+``form_for_model()`` returns a generated class. This class must then be 
+instantiated::
+
+    # Create the form class
+    >>> ArticleForm = form_for_model(Article)
+    
+    # Create an empty form instance
+    >>> f = ArticleForm()
+ 
+The form produced by ``form_for_model`` also has a ``save()`` method. Once the
+form contains valid data, the ``save()`` method can be used to create a model
+instance with the attribute values described on the form::
+
+    # Create a form instance populated with POST data
+    >>> f = ArticleForm(request.POST)
+
+    # Save the new instance
+    >>> new_article = f.save()
+
+Using an alternate base class
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If you want to add other methods to the generated form, you can put those 
+methods onto a base class, and instruct ``form_for_model()`` to use that
+base class. 
+
+By default, every form produced by ``form_for_model()`` extends 
+``django.newforms.forms.BaseForm``. However, if you provide a ``forms`` 
+argument to ``form_for_model()``, Django will use that class as the base 
+for the form it generates::
+
+    # Create the new base class:
+    >>> class MyBase(BaseForm):
+    ...     def fiddle(self):
+    ...         # Do whatever the method does
+
+    # Create the form class with a different base class
+    >>> ArticleForm = form_for_model(Article, form=MyBase)
+
+    # Instantiate the form
+    >>> f = ArticleForm()
+    
+    # Use the base class method
+    >>> f.fiddle()
+
+Putting a subset of fields on the form
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+**New in Django development version**
+
+In some cases, you may not want all the model fields to appear on the form. 
+One option is to set ``editable=False`` on the model field. 
``form_for_model()``
+will not include any non-editable fields on a generated form instance.
+
+However, if you just want to exclude a field from one specific form, you
+can use the ``fields`` argument. If you provide a fields argument to
+``form_for_model()``, only the fields named will be included on the form. 
+For example, if you only want the 'title' and 'pub_date' attributes to be 
+included on the Article form, you would call::
+
+    >>> PartialArticleForm = form_for_model(Article, fields=('title', 
'pub_date'))
+
+.. note::
+    If you specify ``fields`` when creating a form with ``form_for_model()``
+    make sure that the fields that are *not* specified can provide default 
+    values, or are allowed to have a value of ``None``. If a field isn't 
+    specified on a form, the object created from the form can't provide 
+    a value for that attribute, which will prevent the new instance from 
+    being saved.
+
+Overriding the default field types
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Although the form field types generated by ``form_for_model()`` are suitable
+for most general purposes, you may have need to override the default field
+types on a specific form. In order to do this, ``form_for_model()`` provides 
+access to the *formfield callback*.
+
+The formfield callback is a function that, when provided with a model field,
+returns a form field instance. When constructing a form, ``form_for_model()``
+asks the formfield callback to provide form field types. The default 
+implementation asks the model field for an appropriate field type; however, 
+any other strategy may be employed. If you need to use an alternate strategy, 
+you can define your own callback, and provide it to ``form_for_model()`` using 
+the ``formfield_callback`` argument. 
+
+For example, if you wanted to use ``MyDateFormField`` for any ``DateField``
+fields on the model, you could define the callback::
+
+    >>> def my_fields(field, **kwargs):
+    ...     if isinstance(field, models.DateField):
+    ...         return MyDateFormField(**kwargs)
+    ...     else:     
+    ...         return field.formfield(**kwargs)
+    
+    >>> ArticleForm = form_for_model(formfield_callback=my_fields)
+
+Note that your callback needs to handle *all* possible model field types, not 
+just the ones that you want to behave differently to the default.
+
+Finding the model associated with a form
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The model class that was used to construct the form is available
+using the ``_model`` property of the generated form.
+     
+``form_for_instance()``
+-----------------------
+
+``form_for_instance()`` is very similar to ``form_for_model()``. However,
+rather than using a model class to generate a form, it uses an instance of a 
+model::
+
+    # Create an article
+    >>> art = Article(... some data ...)
+    >>> art.save()
+    
+    # Create a form
+    >>> ArticleForm = form_for_instance(art)
+    
+    # Instantiate the form
+    >>> f = ArticleForm()
+
+When a form created by ``form_for_instance()`` is created, the initial
+data values for the form fields are drawn from the instance. However, 
+this data is not bound to the form. You will need to bind data to the 
+form before the form can be saved.
+
+When you call ``save()`` on a form created by ``form_for_instance()``, 
+the database instance will be updated.
+
+``form_for_instance()`` has ``form``, ``fields`` and ``formfield_callback`` 
+arguments that behave the same way as they do for ``form_for_model()``.
+
 More coming soon
 ================
 

Modified: django/trunk/tests/modeltests/model_forms/models.py
===================================================================
--- django/trunk/tests/modeltests/model_forms/models.py 2007-05-12 12:22:50 UTC 
(rev 5201)
+++ django/trunk/tests/modeltests/model_forms/models.py 2007-05-12 14:42:46 UTC 
(rev 5202)
@@ -179,6 +179,18 @@
 <option value="3">Third test</option>
 </select><br /> Hold down "Control", or "Command" on a Mac, to select more 
than one.</td></tr>
 
+You can restrict a form to a subset of the complete list of fields
+by providing a 'fields' argument. If you try to save a
+model created with such a form, you need to ensure that the fields
+that are _not_ on the form have default values, or are allowed to have
+a value of None. If a field isn't specified on a form, the object created
+from the form can't provide a value for that field!
+>>> PartialArticleForm = form_for_model(Article, 
fields=('headline','pub_date'))
+>>> f = PartialArticleForm(auto_id=False)
+>>> print f
+<tr><th>Headline:</th><td><input type="text" name="headline" maxlength="50" 
/></td></tr>
+<tr><th>Pub date:</th><td><input type="text" name="pub_date" /></td></tr>
+
 You can pass a custom Form class to form_for_model. Make sure it's a
 subclass of BaseForm, not Form.
 >>> class CustomForm(BaseForm):
@@ -224,9 +236,25 @@
 <option value="2">It&#39;s a test</option>
 <option value="3">Third test</option>
 </select>  Hold down "Control", or "Command" on a Mac, to select more than 
one.</li>
->>> f = TestArticleForm({'headline': u'New headline', 'pub_date': 
u'1988-01-04', 'writer': u'1', 'article': 'Hello.'})
+>>> f = TestArticleForm({'headline': u'Test headline', 'pub_date': 
u'1984-02-06', 'writer': u'1', 'article': 'Hello.'})
 >>> f.is_valid()
 True
+>>> test_art = f.save()
+>>> test_art.id
+1
+>>> test_art = Article.objects.get(id=1)
+>>> test_art.headline
+'Test headline'
+
+You can create a form over a subset of the available fields 
+by specifying a 'fields' argument to form_for_instance. 
+>>> PartialArticleForm = form_for_instance(art, fields=('headline','pub_date'))
+>>> f = PartialArticleForm({'headline': u'New headline', 'pub_date': 
u'1988-01-04'}, auto_id=False)
+>>> print f.as_ul()
+<li>Headline: <input type="text" name="headline" value="New headline" 
maxlength="50" /></li>
+<li>Pub date: <input type="text" name="pub_date" value="1988-01-04" /></li>
+>>> f.is_valid()
+True
 >>> new_art = f.save()
 >>> new_art.id
 1


--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at 
http://groups.google.com/group/django-updates?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to