diff --git a/django/newforms/models.py b/django/newforms/models.py index 51ed16ff7f4..77b2b321e4d 100644 --- a/django/newforms/models.py +++ b/django/newforms/models.py @@ -6,13 +6,15 @@ and database field objects. from django.utils.translation import ugettext_lazy as _ from django.utils.encoding import smart_unicode from django.utils.datastructures import SortedDict +from django.core.exceptions import ImproperlyConfigured -from util import ValidationError +from util import ValidationError, ErrorList from forms import BaseForm from fields import Field, ChoiceField, EMPTY_VALUES from widgets import Select, SelectMultiple, MultipleHiddenInput __all__ = ( + 'ModelForm', 'BaseModelForm', 'model_to_dict', 'fields_for_model', 'save_instance', 'form_for_model', 'form_for_instance', 'form_for_fields', 'ModelChoiceField', 'ModelMultipleChoiceField' ) @@ -132,6 +134,155 @@ def form_for_fields(field_list): for f in field_list if f.editable]) return type('FormForFields', (BaseForm,), {'base_fields': fields}) + +# ModelForms ################################################################# + +def model_to_dict(instance, fields=None, exclude=None): + """ + Returns a dict containing the data in ``instance`` suitable for passing as + a Form's ``initial`` keyword argument. + + ``fields`` is an optional list of field names. If provided, only the named + fields will be included in the returned dict. + + ``exclude`` is an optional list of field names. If provided, the named + fields will be excluded from the returned dict, even if they are listed in + the ``fields`` argument. + """ + # avoid a circular import + from django.db.models.fields.related import ManyToManyField + opts = instance._meta + data = {} + for f in opts.fields + opts.many_to_many: + if not f.editable: + continue + if fields and not f.name in fields: + continue + if exclude and f.name in exclude: + continue + if isinstance(f, ManyToManyField): + # If the object doesn't have a primry key yet, just use an empty + # list for its m2m fields. Calling f.value_from_object will raise + # an exception. + if instance.pk is None: + data[f.name] = [] + else: + # MultipleChoiceWidget needs a list of pks, not object instances. + data[f.name] = [obj.pk for obj in f.value_from_object(instance)] + else: + data[f.name] = f.value_from_object(instance) + return data + +def fields_for_model(model, fields=None, exclude=None, formfield_callback=lambda f: f.formfield()): + """ + Returns a ``SortedDict`` containing form fields for the given model. + + ``fields`` is an optional list of field names. If provided, only the named + fields will be included in the returned fields. + + ``exclude`` is an optional list of field names. If provided, the named + fields will be excluded from the returned fields, even if they are listed + in the ``fields`` argument. + """ + # TODO: if fields is provided, it would be nice to return fields in that order + field_list = [] + opts = model._meta + for f in opts.fields + opts.many_to_many: + if not f.editable: + continue + if fields and not f.name in fields: + continue + if exclude and f.name in exclude: + continue + formfield = formfield_callback(f) + if formfield: + field_list.append((f.name, formfield)) + return SortedDict(field_list) + +class ModelFormOptions(object): + def __init__(self, options=None): + self.model = getattr(options, 'model', None) + self.fields = getattr(options, 'fields', None) + self.exclude = getattr(options, 'exclude', None) + +class ModelFormMetaclass(type): + def __new__(cls, name, bases, attrs): + # TODO: no way to specify formfield_callback yet, do we need one, or + # should it be a special case for the admin? + fields = [(field_name, attrs.pop(field_name)) for field_name, obj in attrs.items() if isinstance(obj, Field)] + fields.sort(lambda x, y: cmp(x[1].creation_counter, y[1].creation_counter)) + + # If this class is subclassing another Form, add that Form's fields. + # Note that we loop over the bases in *reverse*. This is necessary in + # order to preserve the correct order of fields. + for base in bases[::-1]: + if hasattr(base, 'base_fields'): + fields = base.base_fields.items() + fields + declared_fields = SortedDict(fields) + + opts = ModelFormOptions(attrs.get('Meta', None)) + attrs['_meta'] = opts + + # Don't allow more than one Meta model defenition in bases. The fields + # would be generated correctly, but the save method won't deal with + # more than one object. + base_models = [] + for base in bases: + base_opts = getattr(base, '_meta', None) + base_model = getattr(base_opts, 'model', None) + if base_model is not None: + base_models.append(base_model) + if len(base_models) > 1: + raise ImproperlyConfigured("%s's base classes define more than one model." % name) + + # If a model is defined, extract form fields from it and add them to base_fields + if attrs['_meta'].model is not None: + # Don't allow a subclass to define a Meta model if a parent class has. + # Technically the right fields would be generated, but the save + # method will not deal with more than one model. + for base in bases: + base_opts = getattr(base, '_meta', None) + base_model = getattr(base_opts, 'model', None) + if base_model is not None: + raise ImproperlyConfigured('%s defines more than one model.' % name) + model_fields = fields_for_model(opts.model, opts.fields, opts.exclude) + # fields declared in base classes override fields from the model + model_fields.update(declared_fields) + attrs['base_fields'] = model_fields + else: + attrs['base_fields'] = declared_fields + return type.__new__(cls, name, bases, attrs) + +class BaseModelForm(BaseForm): + def __init__(self, instance, data=None, files=None, auto_id='id_%s', prefix=None, + initial=None, error_class=ErrorList, label_suffix=':'): + self.instance = instance + opts = self._meta + object_data = model_to_dict(instance, opts.fields, opts.exclude) + # if initial was provided, it should override the values from instance + if initial is not None: + object_data.update(initial) + BaseForm.__init__(self, data, files, auto_id, prefix, object_data, error_class, label_suffix) + + def save(self, commit=True): + """ + Saves this ``form``'s cleaned_data into model instance ``self.instance``. + + If commit=True, then the changes to ``instance`` will be saved to the + database. Returns ``instance``. + """ + if self.instance.pk is None: + fail_message = 'created' + else: + fail_message = 'changed' + return save_instance(self, self.instance, self._meta.fields, fail_message, commit) + +class ModelForm(BaseModelForm): + __metaclass__ = ModelFormMetaclass + + +# Fields ##################################################################### + class QuerySetIterator(object): def __init__(self, queryset, empty_label, cache_choices): self.queryset = queryset @@ -142,7 +293,7 @@ class QuerySetIterator(object): if self.empty_label is not None: yield (u"", self.empty_label) for obj in self.queryset: - yield (obj._get_pk_val(), smart_unicode(obj)) + yield (obj.pk, smart_unicode(obj)) # Clear the QuerySet cache if required. if not self.cache_choices: self.queryset._result_cache = None diff --git a/docs/form_for_model.txt b/docs/form_for_model.txt new file mode 100644 index 00000000000..6761c15331b --- /dev/null +++ b/docs/form_for_model.txt @@ -0,0 +1,418 @@ +Generating forms for models +=========================== + +If you're building a database-driven app, chances are you'll have forms that +map closely to Django models. For instance, you might have a ``BlogComment`` +model, and you want to create a form that lets people submit comments. In this +case, it would be redundant to define the field types in your form, because +you've already defined the fields in your model. + +For this reason, Django provides a few helper functions that let you create a +``Form`` class from a Django model. + +``form_for_model()`` +-------------------- + +The method ``django.newforms.form_for_model()`` creates a form based on the +definition of a specific model. Pass it the model class, and it will return a +``Form`` class that contains a form field for each model field. + +For example:: + + >>> from django.newforms import form_for_model + + # Create the form class. + >>> ArticleForm = form_for_model(Article) + + # Create an empty form instance. + >>> f = ArticleForm() + +It bears repeating that ``form_for_model()`` takes the model *class*, not a +model instance, and it returns a ``Form`` *class*, not a ``Form`` instance. + +Field types +~~~~~~~~~~~ + +The generated ``Form`` class will have a form field for every model field. Each +model field has a corresponding default form field. For example, a +``CharField`` on a model is represented as a ``CharField`` on a form. A +model ``ManyToManyField`` is represented as a ``MultipleChoiceField``. Here is +the full list of conversions: + + =============================== ======================================== + Model field Form field + =============================== ======================================== + ``AutoField`` Not represented in the form + ``BooleanField`` ``BooleanField`` + ``CharField`` ``CharField`` with ``max_length`` set to + the model field's ``max_length`` + ``CommaSeparatedIntegerField`` ``CharField`` + ``DateField`` ``DateField`` + ``DateTimeField`` ``DateTimeField`` + ``DecimalField`` ``DecimalField`` + ``EmailField`` ``EmailField`` + ``FileField`` ``FileField`` + ``FilePathField`` ``CharField`` + ``FloatField`` ``FloatField`` + ``ForeignKey`` ``ModelChoiceField`` (see below) + ``ImageField`` ``ImageField`` + ``IntegerField`` ``IntegerField`` + ``IPAddressField`` ``IPAddressField`` + ``ManyToManyField`` ``ModelMultipleChoiceField`` (see + below) + ``NullBooleanField`` ``CharField`` + ``PhoneNumberField`` ``USPhoneNumberField`` + (from ``django.contrib.localflavor.us``) + ``PositiveIntegerField`` ``IntegerField`` + ``PositiveSmallIntegerField`` ``IntegerField`` + ``SlugField`` ``CharField`` + ``SmallIntegerField`` ``IntegerField`` + ``TextField`` ``CharField`` with ``widget=Textarea`` + ``TimeField`` ``TimeField`` + ``URLField`` ``URLField`` with ``verify_exists`` set + to the model field's ``verify_exists`` + ``USStateField`` ``CharField`` with + ``widget=USStateSelect`` + (``USStateSelect`` is from + ``django.contrib.localflavor.us``) + ``XMLField`` ``CharField`` with ``widget=Textarea`` + =============================== ======================================== + + +.. note:: + The ``FloatField`` form field and ``DecimalField`` model and form fields + are new in the development version. + +As you might expect, the ``ForeignKey`` and ``ManyToManyField`` model field +types are special cases: + + * ``ForeignKey`` is represented by ``django.newforms.ModelChoiceField``, + which is a ``ChoiceField`` whose choices are a model ``QuerySet``. + + * ``ManyToManyField`` is represented by + ``django.newforms.ModelMultipleChoiceField``, which is a + ``MultipleChoiceField`` whose choices are a model ``QuerySet``. + +In addition, each generated form field has attributes set as follows: + + * If the model field has ``blank=True``, then ``required`` is set to + ``False`` on the form field. Otherwise, ``required=True``. + + * The form field's ``label`` is set to the ``verbose_name`` of the model + field, with the first character capitalized. + + * The form field's ``help_text`` is set to the ``help_text`` of the model + field. + + * If the model field has ``choices`` set, then the form field's ``widget`` + will be set to ``Select``, with choices coming from the model field's + ``choices``. The choices will normally include the blank choice which is + selected by default. If the field is required, this forces the user to + make a selection. The blank choice will not be included if the model + field has ``blank=False`` and an explicit ``default`` value (the + ``default`` value will be initially selected instead). + +Finally, note that you can override the form field used for a given model +field. See "Overriding the default field types" below. + +A full example +~~~~~~~~~~~~~~ + +Consider this set of models:: + + from django.db import models + + TITLE_CHOICES = ( + ('MR', 'Mr.'), + ('MRS', 'Mrs.'), + ('MS', 'Ms.'), + ) + + class Author(models.Model): + name = models.CharField(max_length=100) + title = models.CharField(max_length=3, choices=TITLE_CHOICES) + birth_date = models.DateField(blank=True, null=True) + + def __unicode__(self): + return self.name + + class Book(models.Model): + name = models.CharField(max_length=100) + authors = models.ManyToManyField(Author) + +With these models, a call to ``form_for_model(Author)`` would return a ``Form`` +class equivalent to this:: + + class AuthorForm(forms.Form): + name = forms.CharField(max_length=100) + title = forms.CharField(max_length=3, + widget=forms.Select(choices=TITLE_CHOICES)) + birth_date = forms.DateField(required=False) + +A call to ``form_for_model(Book)`` would return a ``Form`` class equivalent to +this:: + + class BookForm(forms.Form): + name = forms.CharField(max_length=100) + authors = forms.ModelMultipleChoiceField(queryset=Author.objects.all()) + +The ``save()`` method +~~~~~~~~~~~~~~~~~~~~~ + +Every form produced by ``form_for_model()`` also has a ``save()`` method. This +method creates and saves a database object from the data bound to the form. For +example:: + + # Create a form instance from POST data. + >>> f = ArticleForm(request.POST) + + # Save a new Article object from the form's data. + >>> new_article = f.save() + +Note that ``save()`` will raise a ``ValueError`` if the data in the form +doesn't validate -- i.e., ``if form.errors``. + +This ``save()`` method accepts an optional ``commit`` keyword argument, which +accepts either ``True`` or ``False``. If you call ``save()`` with +``commit=False``, then it will return an object that hasn't yet been saved to +the database. In this case, it's up to you to call ``save()`` on the resulting +model instance. This is useful if you want to do custom processing on the +object before saving it. ``commit`` is ``True`` by default. + +Another side effect of using ``commit=False`` is seen when your model has +a many-to-many relation with another model. If your model has a many-to-many +relation and you specify ``commit=False`` when you save a form, Django cannot +immediately save the form data for the many-to-many relation. This is because +it isn't possible to save many-to-many data for an instance until the instance +exists in the database. + +To work around this problem, every time you save a form using ``commit=False``, +Django adds a ``save_m2m()`` method to the form created by ``form_for_model``. +After you've manually saved the instance produced by the form, you can invoke +``save_m2m()`` to save the many-to-many form data. For example:: + + # Create a form instance with POST data. + >>> f = AuthorForm(request.POST) + + # Create, but don't save the new author instance. + >>> new_author = f.save(commit=False) + + # Modify the author in some way. + >>> new_author.some_field = 'some_value' + + # Save the new instance. + >>> new_author.save() + + # Now, save the many-to-many data for the form. + >>> f.save_m2m() + +Calling ``save_m2m()`` is only required if you use ``save(commit=False)``. +When you use a simple ``save()`` on a form, all data -- including +many-to-many data -- is saved without the need for any additional method calls. +For example:: + + # Create a form instance with POST data. + >>> f = AuthorForm(request.POST) + + # Create and save the new author instance. There's no need to do anything else. + >>> new_author = f.save() + +Using an alternate base class +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you want to add custom methods to the form generated by +``form_for_model()``, write a class that extends ``django.newforms.BaseForm`` +and contains your custom methods. Then, use the ``form`` argument to +``form_for_model()`` to tell it to use your custom form as its base class. +For example:: + + # Create the new base class. + >>> class MyBase(BaseForm): + ... def my_method(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.my_method() + +Using 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 generated +form. There are two ways of telling ``form_for_model()`` to use only a subset +of the model fields: + + 1. Set ``editable=False`` on the model field. As a result, *any* form + created from the model via ``form_for_model()`` will not include that + field. + + 2. Use the ``fields`` argument to ``form_for_model()``. This argument, if + given, should be a list of field names to include in the form. + + For example, if you want a form for the ``Author`` model (defined above) + that includes only the ``name`` and ``title`` fields, you would specify + ``fields`` like this:: + + PartialArticleForm = form_for_model(Author, fields=('name', 'title')) + +.. note:: + + If you specify ``fields`` when creating a form with ``form_for_model()``, + then the fields that are *not* specified will not be set by the form's + ``save()`` method. Django will prevent any attempt to save an incomplete + model, so if the model does not allow the missing fields to be empty, and + does not provide a default value for the missing fields, any attempt to + ``save()`` a ``form_for_model`` with missing fields will fail. To avoid + this failure, you must use ``save(commit=False)`` and manually set any + extra required fields:: + + instance = form.save(commit=False) + instance.required_field = 'new value' + instance.save() + + See the `section on saving forms`_ for more details on using + ``save(commit=False)``. + +.. _section on saving forms: `The save() method`_ + +Overriding the default field types +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The default field types, as described in the "Field types" table above, are +sensible defaults; if you have a ``DateField`` in your model, chances are you'd +want that to be represented as a ``DateField`` in your form. But +``form_for_model()`` gives you the flexibility of changing the form field type +for a given model field. You do this by specifying a **formfield callback**. + +A 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. + +By default, ``form_for_model()`` calls the ``formfield()`` method on the model +field:: + + def default_callback(field, **kwargs): + return field.formfield(**kwargs) + +The ``kwargs`` are any keyword arguments that might be passed to the form +field, such as ``required=True`` or ``label='Foo'``. + +For example, if you wanted to use ``MyDateFormField`` for any ``DateField`` +field on the model, you could define the callback:: + + >>> def my_callback(field, **kwargs): + ... if isinstance(field, models.DateField): + ... return MyDateFormField(**kwargs) + ... else: + ... return field.formfield(**kwargs) + + >>> ArticleForm = form_for_model(Article, formfield_callback=my_callback) + +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. That's why +this example has an ``else`` clause that implements the default behavior. + +.. warning:: + The field that is passed into the ``formfield_callback`` function in + ``form_for_model()`` and ``form_for_instance`` is the field instance from + your model's class. You **must not** alter that object at all; treat it + as read-only! + + If you make any alterations to that object, it will affect any future + users of the model class, because you will have changed the field object + used to construct the class. This is almost certainly what you don't want + to have happen. + +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:: + + >>> ArticleForm = form_for_model(Article) + >>> ArticleForm._model + + +``form_for_instance()`` +----------------------- + +``form_for_instance()`` is like ``form_for_model()``, but it takes a model +instance instead of a model class:: + + # Create an Author. + >>> a = Author(name='Joe Smith', title='MR', birth_date=None) + >>> a.save() + + # Create a form for this particular Author. + >>> AuthorForm = form_for_instance(a) + + # Instantiate the form. + >>> f = AuthorForm() + +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. + +Unlike ``form_for_model()``, a choice field in form created by +``form_for_instance()`` will not include the blank choice if the respective +model field has ``blank=False``. The initial choice is drawn from the instance. + +When you call ``save()`` on a form created by ``form_for_instance()``, +the database instance will be updated. As in ``form_for_model()``, ``save()`` +will raise ``ValueError`` if the data doesn't validate. + +``form_for_instance()`` has ``form``, ``fields`` and ``formfield_callback`` +arguments that behave the same way as they do for ``form_for_model()``. + +Let's modify the earlier `contact form`_ view example a little bit. Suppose we +have a ``Message`` model that holds each contact submission. Something like:: + + class Message(models.Model): + subject = models.CharField(max_length=100) + message = models.TextField() + sender = models.EmailField() + cc_myself = models.BooleanField(required=False) + +You could use this model to create a form (using ``form_for_model()``). You +could also use existing ``Message`` instances to create a form for editing +messages. The `simple example view`_ can be changed slightly to accept the ``id`` value +of an existing ``Message`` and present it for editing:: + + def contact_edit(request, msg_id): + # Create the form from the message id. + message = get_object_or_404(Message, id=msg_id) + ContactForm = form_for_instance(message) + + if request.method == 'POST': + form = ContactForm(request.POST) + if form.is_valid(): + form.save() + return HttpResponseRedirect('/url/on_success/') + else: + form = ContactForm() + return render_to_response('contact.html', {'form': form}) + +Aside from how we create the ``ContactForm`` class here, the main point to +note is that the form display in the ``GET`` branch of the function +will use the values from the ``message`` instance as initial values for the +form field. + +.. _contact form: ../newforms/#simple-view-example +.. _`simple example view`: ../newforms/#simple-view-example + +When should you use ``form_for_model()`` and ``form_for_instance()``? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``form_for_model()`` and ``form_for_instance()`` functions are meant to be +shortcuts for the common case. If you want to create a form whose fields map to +more than one model, or a form that contains fields that *aren't* on a model, +you shouldn't use these shortcuts. Creating a ``Form`` class the "long" way +isn't that difficult, after all. diff --git a/docs/modelforms.txt b/docs/modelforms.txt new file mode 100644 index 00000000000..5e94128ba33 --- /dev/null +++ b/docs/modelforms.txt @@ -0,0 +1,310 @@ +========================== +Using newforms with models +========================== + +``ModelForm`` +============= + +If you're building a database-driven app, chances are you'll have forms that +map closely to Django models. For instance, you might have a ``BlogComment`` +model, and you want to create a form that lets people submit comments. In this +case, it would be redundant to define the field types in your form, because +you've already defined the fields in your model. + +For this reason, Django provides a helper class that let you create a ``Form`` +class from a Django model. + +For example:: + + >>> from django.newforms import ModelForm + + # Create the form class. + >>> class ArticleForm(ModelForm): + ... class Meta: + ... model = Article + + # Creating a form to add an article. + >>> article\ = Article() + >>> form = ArticleForm(article) + + # Creating a form to change an existing article. + >>> article = Article.objects.get(pk=1) + >>> form = ArticleForm(article) + +Field types +----------- + +The generated ``Form`` class will have a form field for every model field. Each +model field has a corresponding default form field. For example, a +``CharField`` on a model is represented as a ``CharField`` on a form. A +model ``ManyToManyField`` is represented as a ``MultipleChoiceField``. Here is +the full list of conversions: + + =============================== ======================================== + Model field Form field + =============================== ======================================== + ``AutoField`` Not represented in the form + ``BooleanField`` ``BooleanField`` + ``CharField`` ``CharField`` with ``max_length`` set to + the model field's ``max_length`` + ``CommaSeparatedIntegerField`` ``CharField`` + ``DateField`` ``DateField`` + ``DateTimeField`` ``DateTimeField`` + ``DecimalField`` ``DecimalField`` + ``EmailField`` ``EmailField`` + ``FileField`` ``FileField`` + ``FilePathField`` ``CharField`` + ``FloatField`` ``FloatField`` + ``ForeignKey`` ``ModelChoiceField`` (see below) + ``ImageField`` ``ImageField`` + ``IntegerField`` ``IntegerField`` + ``IPAddressField`` ``IPAddressField`` + ``ManyToManyField`` ``ModelMultipleChoiceField`` (see + below) + ``NullBooleanField`` ``CharField`` + ``PhoneNumberField`` ``USPhoneNumberField`` + (from ``django.contrib.localflavor.us``) + ``PositiveIntegerField`` ``IntegerField`` + ``PositiveSmallIntegerField`` ``IntegerField`` + ``SlugField`` ``CharField`` + ``SmallIntegerField`` ``IntegerField`` + ``TextField`` ``CharField`` with ``widget=Textarea`` + ``TimeField`` ``TimeField`` + ``URLField`` ``URLField`` with ``verify_exists`` set + to the model field's ``verify_exists`` + ``USStateField`` ``CharField`` with + ``widget=USStateSelect`` + (``USStateSelect`` is from + ``django.contrib.localflavor.us``) + ``XMLField`` ``CharField`` with ``widget=Textarea`` + =============================== ======================================== + + +.. note:: + The ``FloatField`` form field and ``DecimalField`` model and form fields + are new in the development version. + +As you might expect, the ``ForeignKey`` and ``ManyToManyField`` model field +types are special cases: + + * ``ForeignKey`` is represented by ``django.newforms.ModelChoiceField``, + which is a ``ChoiceField`` whose choices are a model ``QuerySet``. + + * ``ManyToManyField`` is represented by + ``django.newforms.ModelMultipleChoiceField``, which is a + ``MultipleChoiceField`` whose choices are a model ``QuerySet``. + +In addition, each generated form field has attributes set as follows: + + * If the model field has ``blank=True``, then ``required`` is set to + ``False`` on the form field. Otherwise, ``required=True``. + + * The form field's ``label`` is set to the ``verbose_name`` of the model + field, with the first character capitalized. + + * The form field's ``help_text`` is set to the ``help_text`` of the model + field. + + * If the model field has ``choices`` set, then the form field's ``widget`` + will be set to ``Select``, with choices coming from the model field's + ``choices``. The choices will normally include the blank choice which is + selected by default. If the field is required, this forces the user to + make a selection. The blank choice will not be included if the model + field has ``blank=False`` and an explicit ``default`` value (the + ``default`` value will be initially selected instead). + +Finally, note that you can override the form field used for a given model +field. See "Overriding the default field types" below. + +A full example +-------------- + +Consider this set of models:: + + from django.db import models + + TITLE_CHOICES = ( + ('MR', 'Mr.'), + ('MRS', 'Mrs.'), + ('MS', 'Ms.'), + ) + + class Author(models.Model): + name = models.CharField(max_length=100) + title = models.CharField(max_length=3, choices=TITLE_CHOICES) + birth_date = models.DateField(blank=True, null=True) + + def __unicode__(self): + return self.name + + class Book(models.Model): + name = models.CharField(max_length=100) + authors = models.ManyToManyField(Author) + + class AuthorForm(ModelForm): + class Meta: + model = Author + + class BookForm(ModelForm): + class Meta: + model = Book + +With these models, the ``ModelForm`` subclasses above would be roughly +equivalent to this (the only difference being the ``save()`` method, which +we'll discuss in a moment.):: + + class AuthorForm(forms.Form): + name = forms.CharField(max_length=100) + title = forms.CharField(max_length=3, + widget=forms.Select(choices=TITLE_CHOICES)) + birth_date = forms.DateField(required=False) + + class BookForm(forms.Form): + name = forms.CharField(max_length=100) + authors = forms.ModelMultipleChoiceField(queryset=Author.objects.all()) + +The ``save()`` method +--------------------- + +Every form produced by ``ModelForm`` also has a ``save()`` method. This +method creates and saves a database object from the data bound to the form. +A subclass of ``ModelForm`` also requires a model instance as the first +arument to its constructor. For example:: + + # Create a form instance from POST data. + >>> a = Article() + >>> f = ArticleForm(a, request.POST) + + # Save a new Article object from the form's data. + >>> new_article = f.save() + +Note that ``save()`` will raise a ``ValueError`` if the data in the form +doesn't validate -- i.e., ``if form.errors``. + +This ``save()`` method accepts an optional ``commit`` keyword argument, which +accepts either ``True`` or ``False``. If you call ``save()`` with +``commit=False``, then it will return an object that hasn't yet been saved to +the database. In this case, it's up to you to call ``save()`` on the resulting +model instance. This is useful if you want to do custom processing on the +object before saving it. ``commit`` is ``True`` by default. + +Another side effect of using ``commit=False`` is seen when your model has +a many-to-many relation with another model. If your model has a many-to-many +relation and you specify ``commit=False`` when you save a form, Django cannot +immediately save the form data for the many-to-many relation. This is because +it isn't possible to save many-to-many data for an instance until the instance +exists in the database. + +To work around this problem, every time you save a form using ``commit=False``, +Django adds a ``save_m2m()`` method to your ``ModelForm`` subclass. After +you've manually saved the instance produced by the form, you can invoke +``save_m2m()`` to save the many-to-many form data. For example:: + + # Create a form instance with POST data. + >>> a = Author() + >>> f = AuthorForm(a, request.POST) + + # Create, but don't save the new author instance. + >>> new_author = f.save(commit=False) + + # Modify the author in some way. + >>> new_author.some_field = 'some_value' + + # Save the new instance. + >>> new_author.save() + + # Now, save the many-to-many data for the form. + >>> f.save_m2m() + +Calling ``save_m2m()`` is only required if you use ``save(commit=False)``. +When you use a simple ``save()`` on a form, all data -- including +many-to-many data -- is saved without the need for any additional method calls. +For example:: + + # Create a form instance with POST data. + >>> a = Author() + >>> f = AuthorForm(a, request.POST) + + # Create and save the new author instance. There's no need to do anything else. + >>> new_author = f.save() + +Using a subset of fields on the form +------------------------------------ + +In some cases, you may not want all the model fields to appear on the generated +form. There are three ways of telling ``ModelForm`` to use only a subset of the +model fields: + + 1. Set ``editable=False`` on the model field. As a result, *any* form + created from the model via ``ModelForm`` will not include that + field. + + 2. Use the ``fields`` attribute of the ``ModelForm``'s inner ``Meta`` class. + This attribute, if given, should be a list of field names to include in + the form. + + 3. Use the ``exclude`` attribute of the ``ModelForm``'s inner ``Meta`` class. + This attribute, if given, should be a list of field names to exclude + the form. + + For example, if you want a form for the ``Author`` model (defined above) + that includes only the ``name`` and ``title`` fields, you would specify + ``fields`` or ``exclude`` like this:: + + class PartialAuthorForm(ModelForm): + class Meta: + model = Author + fields = ('name', 'title') + + class PartialAuthorForm(ModelForm): + class Meta: + model = Author + exclude = ('birth_date',) + + Since the Author model has only 3 fields, 'name', 'title', and + 'birth_date', the forms above will contain exactly the same fields. + +.. note:: + + If you specify ``fields`` or ``exclude`` when creating a form with + ``ModelForm``, then the fields that are not in the resulting form will not + be set by the form's ``save()`` method. Django will prevent any attempt to + save an incomplete model, so if the model does not allow the missing fields + to be empty, and does not provide a default value for the missing fields, + any attempt to ``save()`` a ``ModelForm`` with missing fields will fail. + To avoid this failure, you must instantiate your model with initial values + for the missing, but required fields, or use ``save(commit=False)`` and + manually set anyextra required fields:: + + instance = Instance(requiured_field='value') + form = InstanceForm(instance, request.POST) + new_instance = form.save() + + instance = form.save(commit=False) + instance.required_field = 'new value' + new_instance = instance.save() + + See the `section on saving forms`_ for more details on using + ``save(commit=False)``. + +.. _section on saving forms: `The save() method`_ + +Overriding the default field types +---------------------------------- + +The default field types, as described in the "Field types" table above, are +sensible defaults; if you have a ``DateField`` in your model, chances are you'd +want that to be represented as a ``DateField`` in your form. But +``ModelForm`` gives you the flexibility of changing the form field type +for a given model field. You do this by declaratively specifying fields like +you would in a regular ``Form``. Declared fields will override the default +ones generated by using the ``model`` attribute. + +For example, if you wanted to use ``MyDateFormField`` for the ``pub_date`` +field, you could do the following:: + + >>> class ArticleForm(ModelForm): + ... pub_date = MyDateFormField() + ... + ... class Meta: + ... model = Article diff --git a/docs/newforms.txt b/docs/newforms.txt index 6635063e3c3..ac2cfb1f72b 100644 --- a/docs/newforms.txt +++ b/docs/newforms.txt @@ -1770,423 +1770,14 @@ You can then use this field whenever you have a form that requires a comment:: Generating forms for models =========================== -If you're building a database-driven app, chances are you'll have forms that -map closely to Django models. For instance, you might have a ``BlogComment`` -model, and you want to create a form that lets people submit comments. In this -case, it would be redundant to define the field types in your form, because -you've already defined the fields in your model. +The prefered way of generating forms that work with models is explained in the +`ModelForms documentation`_. -For this reason, Django provides a few helper functions that let you create a -``Form`` class from a Django model. +Looking for the ``form_for_model`` and ``form_for_instance`` documentation? +They've been deprecated, but you can still `view the documentation`_. -``form_for_model()`` --------------------- - -The method ``django.newforms.form_for_model()`` creates a form based on the -definition of a specific model. Pass it the model class, and it will return a -``Form`` class that contains a form field for each model field. - -For example:: - - >>> from django.newforms import form_for_model - - # Create the form class. - >>> ArticleForm = form_for_model(Article) - - # Create an empty form instance. - >>> f = ArticleForm() - -It bears repeating that ``form_for_model()`` takes the model *class*, not a -model instance, and it returns a ``Form`` *class*, not a ``Form`` instance. - -Field types -~~~~~~~~~~~ - -The generated ``Form`` class will have a form field for every model field. Each -model field has a corresponding default form field. For example, a -``CharField`` on a model is represented as a ``CharField`` on a form. A -model ``ManyToManyField`` is represented as a ``MultipleChoiceField``. Here is -the full list of conversions: - - =============================== ======================================== - Model field Form field - =============================== ======================================== - ``AutoField`` Not represented in the form - ``BooleanField`` ``BooleanField`` - ``CharField`` ``CharField`` with ``max_length`` set to - the model field's ``max_length`` - ``CommaSeparatedIntegerField`` ``CharField`` - ``DateField`` ``DateField`` - ``DateTimeField`` ``DateTimeField`` - ``DecimalField`` ``DecimalField`` - ``EmailField`` ``EmailField`` - ``FileField`` ``FileField`` - ``FilePathField`` ``CharField`` - ``FloatField`` ``FloatField`` - ``ForeignKey`` ``ModelChoiceField`` (see below) - ``ImageField`` ``ImageField`` - ``IntegerField`` ``IntegerField`` - ``IPAddressField`` ``IPAddressField`` - ``ManyToManyField`` ``ModelMultipleChoiceField`` (see - below) - ``NullBooleanField`` ``CharField`` - ``PhoneNumberField`` ``USPhoneNumberField`` - (from ``django.contrib.localflavor.us``) - ``PositiveIntegerField`` ``IntegerField`` - ``PositiveSmallIntegerField`` ``IntegerField`` - ``SlugField`` ``CharField`` - ``SmallIntegerField`` ``IntegerField`` - ``TextField`` ``CharField`` with ``widget=Textarea`` - ``TimeField`` ``TimeField`` - ``URLField`` ``URLField`` with ``verify_exists`` set - to the model field's ``verify_exists`` - ``USStateField`` ``CharField`` with - ``widget=USStateSelect`` - (``USStateSelect`` is from - ``django.contrib.localflavor.us``) - ``XMLField`` ``CharField`` with ``widget=Textarea`` - =============================== ======================================== - - -.. note:: - The ``FloatField`` form field and ``DecimalField`` model and form fields - are new in the development version. - -As you might expect, the ``ForeignKey`` and ``ManyToManyField`` model field -types are special cases: - - * ``ForeignKey`` is represented by ``django.newforms.ModelChoiceField``, - which is a ``ChoiceField`` whose choices are a model ``QuerySet``. - - * ``ManyToManyField`` is represented by - ``django.newforms.ModelMultipleChoiceField``, which is a - ``MultipleChoiceField`` whose choices are a model ``QuerySet``. - -In addition, each generated form field has attributes set as follows: - - * If the model field has ``blank=True``, then ``required`` is set to - ``False`` on the form field. Otherwise, ``required=True``. - - * The form field's ``label`` is set to the ``verbose_name`` of the model - field, with the first character capitalized. - - * The form field's ``help_text`` is set to the ``help_text`` of the model - field. - - * If the model field has ``choices`` set, then the form field's ``widget`` - will be set to ``Select``, with choices coming from the model field's - ``choices``. - - The choices will include the "blank" choice, which is selected by - default. If the field is required, this forces the user to make a - selection. The blank choice will not be included if the model - field has ``blank=False`` and an explicit ``default`` value, in which - case the ``default`` value will be initially selected instead. - -Finally, note that you can override the form field used for a given model -field. See "Overriding the default field types" below. - -A full example -~~~~~~~~~~~~~~ - -Consider this set of models:: - - from django.db import models - - TITLE_CHOICES = ( - ('MR', 'Mr.'), - ('MRS', 'Mrs.'), - ('MS', 'Ms.'), - ) - - class Author(models.Model): - name = models.CharField(max_length=100) - title = models.CharField(max_length=3, choices=TITLE_CHOICES) - birth_date = models.DateField(blank=True, null=True) - - def __unicode__(self): - return self.name - - class Book(models.Model): - name = models.CharField(max_length=100) - authors = models.ManyToManyField(Author) - -With these models, a call to ``form_for_model(Author)`` would return a ``Form`` -class equivalent to this:: - - class AuthorForm(forms.Form): - name = forms.CharField(max_length=100) - title = forms.CharField(max_length=3, - widget=forms.Select(choices=TITLE_CHOICES)) - birth_date = forms.DateField(required=False) - -A call to ``form_for_model(Book)`` would return a ``Form`` class equivalent to -this:: - - class BookForm(forms.Form): - name = forms.CharField(max_length=100) - authors = forms.ModelMultipleChoiceField(queryset=Author.objects.all()) - -The ``save()`` method -~~~~~~~~~~~~~~~~~~~~~ - -Every form produced by ``form_for_model()`` also has a ``save()`` method. This -method creates and saves a database object from the data bound to the form. For -example:: - - # Create a form instance from POST data. - >>> f = ArticleForm(request.POST) - - # Save a new Article object from the form's data. - >>> new_article = f.save() - -Note that ``save()`` will raise a ``ValueError`` if the data in the form -doesn't validate -- i.e., ``if form.errors``. - -This ``save()`` method accepts an optional ``commit`` keyword argument, which -accepts either ``True`` or ``False``. If you call ``save()`` with -``commit=False``, then it will return an object that hasn't yet been saved to -the database. In this case, it's up to you to call ``save()`` on the resulting -model instance. This is useful if you want to do custom processing on the -object before saving it. ``commit`` is ``True`` by default. - -Another side effect of using ``commit=False`` is seen when your model has -a many-to-many relation with another model. If your model has a many-to-many -relation and you specify ``commit=False`` when you save a form, Django cannot -immediately save the form data for the many-to-many relation. This is because -it isn't possible to save many-to-many data for an instance until the instance -exists in the database. - -To work around this problem, every time you save a form using ``commit=False``, -Django adds a ``save_m2m()`` method to the form created by ``form_for_model``. -After you've manually saved the instance produced by the form, you can invoke -``save_m2m()`` to save the many-to-many form data. For example:: - - # Create a form instance with POST data. - >>> f = AuthorForm(request.POST) - - # Create, but don't save the new author instance. - >>> new_author = f.save(commit=False) - - # Modify the author in some way. - >>> new_author.some_field = 'some_value' - - # Save the new instance. - >>> new_author.save() - - # Now, save the many-to-many data for the form. - >>> f.save_m2m() - -Calling ``save_m2m()`` is only required if you use ``save(commit=False)``. -When you use a simple ``save()`` on a form, all data -- including -many-to-many data -- is saved without the need for any additional method calls. -For example:: - - # Create a form instance with POST data. - >>> f = AuthorForm(request.POST) - - # Create and save the new author instance. There's no need to do anything else. - >>> new_author = f.save() - -Using an alternate base class -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -If you want to add custom methods to the form generated by -``form_for_model()``, write a class that extends ``django.newforms.BaseForm`` -and contains your custom methods. Then, use the ``form`` argument to -``form_for_model()`` to tell it to use your custom form as its base class. -For example:: - - # Create the new base class. - >>> class MyBase(BaseForm): - ... def my_method(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.my_method() - -Using 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 generated -form. There are two ways of telling ``form_for_model()`` to use only a subset -of the model fields: - - 1. Set ``editable=False`` on the model field. As a result, *any* form - created from the model via ``form_for_model()`` will not include that - field. - - 2. Use the ``fields`` argument to ``form_for_model()``. This argument, if - given, should be a list of field names to include in the form. - - For example, if you want a form for the ``Author`` model (defined above) - that includes only the ``name`` and ``title`` fields, you would specify - ``fields`` like this:: - - PartialArticleForm = form_for_model(Author, fields=('name', 'title')) - -.. note:: - - If you specify ``fields`` when creating a form with ``form_for_model()``, - then the fields that are *not* specified will not be set by the form's - ``save()`` method. Django will prevent any attempt to save an incomplete - model, so if the model does not allow the missing fields to be empty, and - does not provide a default value for the missing fields, any attempt to - ``save()`` a ``form_for_model`` with missing fields will fail. To avoid - this failure, you must use ``save(commit=False)`` and manually set any - extra required fields:: - - instance = form.save(commit=False) - instance.required_field = 'new value' - instance.save() - - See the `section on saving forms`_ for more details on using - ``save(commit=False)``. - -.. _section on saving forms: `The save() method`_ - -Overriding the default field types -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The default field types, as described in the "Field types" table above, are -sensible defaults; if you have a ``DateField`` in your model, chances are you'd -want that to be represented as a ``DateField`` in your form. But -``form_for_model()`` gives you the flexibility of changing the form field type -for a given model field. You do this by specifying a **formfield callback**. - -A 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. - -By default, ``form_for_model()`` calls the ``formfield()`` method on the model -field:: - - def default_callback(field, **kwargs): - return field.formfield(**kwargs) - -The ``kwargs`` are any keyword arguments that might be passed to the form -field, such as ``required=True`` or ``label='Foo'``. - -For example, if you wanted to use ``MyDateFormField`` for any ``DateField`` -field on the model, you could define the callback:: - - >>> def my_callback(field, **kwargs): - ... if isinstance(field, models.DateField): - ... return MyDateFormField(**kwargs) - ... else: - ... return field.formfield(**kwargs) - - >>> ArticleForm = form_for_model(Article, formfield_callback=my_callback) - -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. That's why -this example has an ``else`` clause that implements the default behavior. - -.. warning:: - The field that is passed into the ``formfield_callback`` function in - ``form_for_model()`` and ``form_for_instance`` is the field instance from - your model's class. You **must not** alter that object at all; treat it - as read-only! - - If you make any alterations to that object, it will affect any future - users of the model class, because you will have changed the field object - used to construct the class. This is almost certainly what you don't want - to have happen. - -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:: - - >>> ArticleForm = form_for_model(Article) - >>> ArticleForm._model - - -``form_for_instance()`` ------------------------ - -``form_for_instance()`` is like ``form_for_model()``, but it takes a model -instance instead of a model class:: - - # Create an Author. - >>> a = Author(name='Joe Smith', title='MR', birth_date=None) - >>> a.save() - - # Create a form for this particular Author. - >>> AuthorForm = form_for_instance(a) - - # Instantiate the form. - >>> f = AuthorForm() - -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. - -Unlike ``form_for_model()``, a choice field in form created by -``form_for_instance()`` will not include the blank choice if the respective -model field has ``blank=False``. The initial choice is drawn from the instance. - -When you call ``save()`` on a form created by ``form_for_instance()``, -the database instance will be updated. As in ``form_for_model()``, ``save()`` -will raise ``ValueError`` if the data doesn't validate. - -``form_for_instance()`` has ``form``, ``fields`` and ``formfield_callback`` -arguments that behave the same way as they do for ``form_for_model()``. - -Let's modify the earlier `contact form`_ view example a little bit. Suppose we -have a ``Message`` model that holds each contact submission. Something like:: - - class Message(models.Model): - subject = models.CharField(max_length=100) - message = models.TextField() - sender = models.EmailField() - cc_myself = models.BooleanField(required=False) - -You could use this model to create a form (using ``form_for_model()``). You -could also use existing ``Message`` instances to create a form for editing -messages. The earlier_ view can be changed slightly to accept the ``id`` value -of an existing ``Message`` and present it for editing:: - - def contact_edit(request, msg_id): - # Create the form from the message id. - message = get_object_or_404(Message, id=msg_id) - ContactForm = form_for_instance(message) - - if request.method == 'POST': - form = ContactForm(request.POST) - if form.is_valid(): - form.save() - return HttpResponseRedirect('/url/on_success/') - else: - form = ContactForm() - return render_to_response('contact.html', {'form': form}) - -Aside from how we create the ``ContactForm`` class here, the main point to -note is that the form display in the ``GET`` branch of the function -will use the values from the ``message`` instance as initial values for the -form field. - -.. _contact form: `Simple view example`_ -.. _earlier: `Simple view example`_ - -When should you use ``form_for_model()`` and ``form_for_instance()``? -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The ``form_for_model()`` and ``form_for_instance()`` functions are meant to be -shortcuts for the common case. If you want to create a form whose fields map to -more than one model, or a form that contains fields that *aren't* on a model, -you shouldn't use these shortcuts. Creating a ``Form`` class the "long" way -isn't that difficult, after all. +.. _ModelForms documentation: ../modelforms/ +.. _view the documentation: ../form_for_model/ More coming soon ================ diff --git a/tests/modeltests/model_forms/models.py b/tests/modeltests/model_forms/models.py index 9b0126ff4f5..cd929562707 100644 --- a/tests/modeltests/model_forms/models.py +++ b/tests/modeltests/model_forms/models.py @@ -1,25 +1,10 @@ """ -36. Generating HTML forms from models +XX. Generating HTML forms from models -Django provides shortcuts for creating Form objects from a model class and a -model instance. - -The function django.newforms.form_for_model() takes a model class and returns -a Form that is tied to the model. This Form works just like any other Form, -with one additional method: save(). The save() method creates an instance -of the model and returns that newly created instance. It saves the instance to -the database if save(commit=True), which is default. If you pass -commit=False, then you'll get the object without committing the changes to the -database. - -The function django.newforms.form_for_instance() takes a model instance and -returns a Form that is tied to the instance. This form works just like any -other Form, with one additional method: save(). The save() -method updates the model instance. It also takes a commit=True parameter. - -The function django.newforms.save_instance() takes a bound form instance and a -model instance and saves the form's cleaned_data into the instance. It also takes -a commit=True parameter. +This is mostly just a reworking of the form_for_model/form_for_instance tests +to use ModelForm. As such, the text may not make sense in all cases, and the +examples are probably a poor fit for the ModelForm syntax. In other words, +most of these tests should be rewritten. """ from django.db import models @@ -30,23 +15,6 @@ ARTICLE_STATUS = ( (3, 'Live'), ) -STEERING_TYPE = ( - ('left', 'Left steering wheel'), - ('right', 'Right steering wheel'), -) - -FUEL_TYPE = ( - ('gas', 'Gasoline'), - ('diesel', 'Diesel'), - ('other', 'Other'), -) - -TRANSMISSION_TYPE = ( - ('at', 'Automatic'), - ('mt', 'Manual'), - ('cvt', 'CVT'), -) - class Category(models.Model): name = models.CharField(max_length=20) slug = models.SlugField(max_length=20) @@ -87,21 +55,119 @@ class PhoneNumber(models.Model): def __unicode__(self): return self.phone -class Car(models.Model): - name = models.CharField(max_length=50) - steering = models.CharField(max_length=5, choices=STEERING_TYPE, default='left') - fuel = models.CharField(max_length=10, choices=FUEL_TYPE) - transmission = models.CharField(max_length=3, choices=TRANSMISSION_TYPE, blank=True, help_text='Leave empty if not applicable.') - __test__ = {'API_TESTS': """ ->>> from django.newforms import form_for_model, form_for_instance, save_instance, BaseForm, Form, CharField +>>> from django import newforms as forms +>>> from django.newforms.models import ModelForm + +The bare bones, absolutely nothing custom, basic case. + +>>> class CategoryForm(ModelForm): +... class Meta: +... model = Category +>>> CategoryForm.base_fields.keys() +['name', 'slug', 'url'] + + +Extra fields. + +>>> class CategoryForm(ModelForm): +... some_extra_field = forms.BooleanField() +... +... class Meta: +... model = Category + +>>> CategoryForm.base_fields.keys() +['name', 'slug', 'url', 'some_extra_field'] + + +Replacing a field. + +>>> class CategoryForm(ModelForm): +... url = forms.BooleanField() +... +... class Meta: +... model = Category + +>>> CategoryForm.base_fields['url'].__class__ + + + +Using 'fields'. + +>>> class CategoryForm(ModelForm): +... +... class Meta: +... model = Category +... fields = ['url'] + +>>> CategoryForm.base_fields.keys() +['url'] + + +Using 'exclude' + +>>> class CategoryForm(ModelForm): +... +... class Meta: +... model = Category +... exclude = ['url'] + +>>> CategoryForm.base_fields.keys() +['name', 'slug'] + + +Using 'fields' *and* 'exclude'. Not sure why you'd want to do this, but uh, +"be liberal in what you accept" and all. + +>>> class CategoryForm(ModelForm): +... +... class Meta: +... model = Category +... fields = ['name', 'url'] +... exclude = ['url'] + +>>> CategoryForm.base_fields.keys() +['name'] + +Don't allow more than one 'model' definition in the inheritance hierarchy. +Technically, it would generate a valid form, but the fact that the resulting +save method won't deal with multiple objects is likely to trip up people not +familiar with the mechanics. + +>>> class CategoryForm(ModelForm): +... class Meta: +... model = Category + +>>> class BadForm(CategoryForm): +... class Meta: +... model = Article +Traceback (most recent call last): +... +ImproperlyConfigured: BadForm defines more than one model. + +>>> class ArticleForm(ModelForm): +... class Meta: +... model = Article + +>>> class BadForm(ArticleForm, CategoryForm): +... pass +Traceback (most recent call last): +... +ImproperlyConfigured: BadForm's base classes define more than one model. + + +# Old form_for_x tests ####################################################### + +>>> from django.newforms import ModelForm, CharField >>> import datetime >>> Category.objects.all() [] ->>> CategoryForm = form_for_model(Category) ->>> f = CategoryForm() +>>> class CategoryForm(ModelForm): +... class Meta: +... model = Category +>>> f = CategoryForm(Category()) >>> print f @@ -113,13 +179,13 @@ __test__ = {'API_TESTS': """ >>> print f['name'] ->>> f = CategoryForm(auto_id=False) +>>> f = CategoryForm(Category(), auto_id=False) >>> print f.as_ul()
  • Name:
  • Slug:
  • The URL:
  • ->>> f = CategoryForm({'name': 'Entertainment', 'slug': 'entertainment', 'url': 'entertainment'}) +>>> f = CategoryForm(Category(), {'name': 'Entertainment', 'slug': 'entertainment', 'url': 'entertainment'}) >>> f.is_valid() True >>> f.cleaned_data @@ -130,7 +196,7 @@ True >>> Category.objects.all() [] ->>> f = CategoryForm({'name': "It's a test", 'slug': 'its-test', 'url': 'test'}) +>>> f = CategoryForm(Category(), {'name': "It's a test", 'slug': 'its-test', 'url': 'test'}) >>> f.is_valid() True >>> f.cleaned_data @@ -144,7 +210,7 @@ True If you call save() with commit=False, then it will return an object that hasn't yet been saved to the database. In this case, it's up to you to call save() on the resulting model instance. ->>> f = CategoryForm({'name': 'Third test', 'slug': 'third-test', 'url': 'third'}) +>>> f = CategoryForm(Category(), {'name': 'Third test', 'slug': 'third-test', 'url': 'third'}) >>> f.is_valid() True >>> f.cleaned_data @@ -159,7 +225,7 @@ True [, , ] If you call save() with invalid data, you'll get a ValueError. ->>> f = CategoryForm({'name': '', 'slug': '', 'url': 'foo'}) +>>> f = CategoryForm(Category(), {'name': '', 'slug': '', 'url': 'foo'}) >>> f.errors {'name': [u'This field is required.'], 'slug': [u'This field is required.']} >>> f.cleaned_data @@ -170,7 +236,7 @@ AttributeError: 'CategoryForm' object has no attribute 'cleaned_data' Traceback (most recent call last): ... ValueError: The Category could not be created because the data didn't validate. ->>> f = CategoryForm({'name': '', 'slug': '', 'url': 'foo'}) +>>> f = CategoryForm(Category(), {'name': '', 'slug': '', 'url': 'foo'}) >>> f.save() Traceback (most recent call last): ... @@ -184,8 +250,10 @@ Create a couple of Writers. ManyToManyFields are represented by a MultipleChoiceField, ForeignKeys and any fields with the 'choices' attribute are represented by a ChoiceField. ->>> ArticleForm = form_for_model(Article) ->>> f = ArticleForm(auto_id=False) +>>> class ArticleForm(ModelForm): +... class Meta: +... model = Article +>>> f = ArticleForm(Article(), auto_id=False) >>> print f Headline: Slug: @@ -214,28 +282,23 @@ 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) +>>> class PartialArticleForm(ModelForm): +... class Meta: +... model = Article +... fields = ('headline','pub_date') +>>> f = PartialArticleForm(Article(), auto_id=False) >>> print f Headline: Pub date: -You can pass a custom Form class to form_for_model. Make sure it's a -subclass of BaseForm, not Form. ->>> class CustomForm(BaseForm): -... def say_hello(self): -... print 'hello' ->>> CategoryForm = form_for_model(Category, form=CustomForm) ->>> f = CategoryForm() ->>> f.say_hello() -hello - Use form_for_instance to create a Form from a model instance. The difference between this Form and one created via form_for_model is that the object's current values are inserted as 'initial' data in each Field. >>> w = Writer.objects.get(name='Mike Royko') ->>> RoykoForm = form_for_instance(w) ->>> f = RoykoForm(auto_id=False) +>>> class RoykoForm(ModelForm): +... class Meta: +... model = Writer +>>> f = RoykoForm(w, auto_id=False) >>> print f Name:
    Use both first and last names. @@ -243,8 +306,10 @@ current values are inserted as 'initial' data in each Field. >>> art.save() >>> art.id 1 ->>> TestArticleForm = form_for_instance(art) ->>> f = TestArticleForm(auto_id=False) +>>> class TestArticleForm(ModelForm): +... class Meta: +... model = Article +>>> f = TestArticleForm(art, auto_id=False) >>> print f.as_ul()
  • Headline:
  • Slug:
  • @@ -266,7 +331,7 @@ current values are inserted as 'initial' data in each Field. Hold down "Control", or "Command" on a Mac, to select more than one. ->>> f = TestArticleForm({'headline': u'Test headline', 'slug': 'test-headline', 'pub_date': u'1984-02-06', 'writer': u'1', 'article': 'Hello.'}) +>>> f = TestArticleForm(art, {'headline': u'Test headline', 'slug': 'test-headline', 'pub_date': u'1984-02-06', 'writer': u'1', 'article': 'Hello.'}) >>> f.is_valid() True >>> test_art = f.save() @@ -278,8 +343,11 @@ u'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', 'slug', 'pub_date')) ->>> f = PartialArticleForm({'headline': u'New headline', 'slug': 'new-headline', 'pub_date': u'1988-01-04'}, auto_id=False) +>>> class PartialArticleForm(ModelForm): +... class Meta: +... model = Article +... fields=('headline', 'slug', 'pub_date') +>>> f = PartialArticleForm(art, {'headline': u'New headline', 'slug': 'new-headline', 'pub_date': u'1988-01-04'}, auto_id=False) >>> print f.as_ul()
  • Headline:
  • Slug:
  • @@ -299,8 +367,10 @@ Add some categories and test the many-to-many form output. >>> new_art.categories.add(Category.objects.get(name='Entertainment')) >>> new_art.categories.all() [] ->>> TestArticleForm = form_for_instance(new_art) ->>> f = TestArticleForm(auto_id=False) +>>> class TestArticleForm(ModelForm): +... class Meta: +... model = Article +>>> f = TestArticleForm(new_art, auto_id=False) >>> print f.as_ul()
  • Headline:
  • Slug:
  • @@ -323,7 +393,7 @@ Add some categories and test the many-to-many form output. Hold down "Control", or "Command" on a Mac, to select more than one. ->>> f = TestArticleForm({'headline': u'New headline', 'slug': u'new-headline', 'pub_date': u'1988-01-04', +>>> f = TestArticleForm(new_art, {'headline': u'New headline', 'slug': u'new-headline', 'pub_date': u'1988-01-04', ... 'writer': u'1', 'article': u'Hello.', 'categories': [u'1', u'2']}) >>> new_art = f.save() >>> new_art.id @@ -333,7 +403,7 @@ Add some categories and test the many-to-many form output. [, ] Now, submit form data with no categories. This deletes the existing categories. ->>> f = TestArticleForm({'headline': u'New headline', 'slug': u'new-headline', 'pub_date': u'1988-01-04', +>>> f = TestArticleForm(new_art, {'headline': u'New headline', 'slug': u'new-headline', 'pub_date': u'1988-01-04', ... 'writer': u'1', 'article': u'Hello.'}) >>> new_art = f.save() >>> new_art.id @@ -343,8 +413,10 @@ Now, submit form data with no categories. This deletes the existing categories. [] Create a new article, with categories, via the form. ->>> ArticleForm = form_for_model(Article) ->>> f = ArticleForm({'headline': u'The walrus was Paul', 'slug': u'walrus-was-paul', 'pub_date': u'1967-11-01', +>>> class ArticleForm(ModelForm): +... class Meta: +... model = Article +>>> f = ArticleForm(Article(), {'headline': u'The walrus was Paul', 'slug': u'walrus-was-paul', 'pub_date': u'1967-11-01', ... 'writer': u'1', 'article': u'Test.', 'categories': [u'1', u'2']}) >>> new_art = f.save() >>> new_art.id @@ -354,8 +426,10 @@ Create a new article, with categories, via the form. [, ] Create a new article, with no categories, via the form. ->>> ArticleForm = form_for_model(Article) ->>> f = ArticleForm({'headline': u'The walrus was Paul', 'slug': u'walrus-was-paul', 'pub_date': u'1967-11-01', +>>> class ArticleForm(ModelForm): +... class Meta: +... model = Article +>>> f = ArticleForm(Article(), {'headline': u'The walrus was Paul', 'slug': u'walrus-was-paul', 'pub_date': u'1967-11-01', ... 'writer': u'1', 'article': u'Test.'}) >>> new_art = f.save() >>> new_art.id @@ -366,8 +440,10 @@ Create a new article, with no categories, via the form. Create a new article, with categories, via the form, but use commit=False. The m2m data won't be saved until save_m2m() is invoked on the form. ->>> ArticleForm = form_for_model(Article) ->>> f = ArticleForm({'headline': u'The walrus was Paul', 'slug': 'walrus-was-paul', 'pub_date': u'1967-11-01', +>>> class ArticleForm(ModelForm): +... class Meta: +... model = Article +>>> f = ArticleForm(Article(), {'headline': u'The walrus was Paul', 'slug': 'walrus-was-paul', 'pub_date': u'1967-11-01', ... 'writer': u'1', 'article': u'Test.', 'categories': [u'1', u'2']}) >>> new_art = f.save(commit=False) @@ -386,10 +462,10 @@ The m2m data won't be saved until save_m2m() is invoked on the form. >>> new_art.categories.order_by('name') [, ] -Here, we define a custom Form. Because it happens to have the same fields as -the Category model, we can use save_instance() to apply its changes to an +Here, we define a custom ModelForm. Because it happens to have the same fields as +the Category model, we can just call the form's save() to apply its changes to an existing Category instance. ->>> class ShortCategory(Form): +>>> class ShortCategory(ModelForm): ... name = CharField(max_length=5) ... slug = CharField(max_length=5) ... url = CharField(max_length=3) @@ -398,8 +474,8 @@ existing Category instance. >>> cat.id 3 ->>> sc = ShortCategory({'name': 'Third', 'slug': 'third', 'url': '3rd'}) ->>> save_instance(sc, cat) +>>> form = ShortCategory(cat, {'name': 'Third', 'slug': 'third', 'url': '3rd'}) +>>> form.save() >>> Category.objects.get(id=3) @@ -407,8 +483,10 @@ existing Category instance. Here, we demonstrate that choices for a ForeignKey ChoiceField are determined at runtime, based on the data in the database when the form is displayed, not the data in the database when the form is instantiated. ->>> ArticleForm = form_for_model(Article) ->>> f = ArticleForm(auto_id=False) +>>> class ArticleForm(ModelForm): +... class Meta: +... model = Article +>>> f = ArticleForm(Article(), auto_id=False) >>> print f.as_ul()
  • Headline:
  • Slug:
  • @@ -609,60 +687,12 @@ ValidationError: [u'Select a valid choice. 4 is not one of the available choices # PhoneNumberField ############################################################ ->>> PhoneNumberForm = form_for_model(PhoneNumber) ->>> f = PhoneNumberForm({'phone': '(312) 555-1212', 'description': 'Assistance'}) +>>> class PhoneNumberForm(ModelForm): +... class Meta: +... model = PhoneNumber +>>> f = PhoneNumberForm(PhoneNumber(), {'phone': '(312) 555-1212', 'description': 'Assistance'}) >>> f.is_valid() True >>> f.cleaned_data {'phone': u'312-555-1212', 'description': u'Assistance'} - -# form_for_* blank choices #################################################### - -Show the form for a new Car. Note that steering field doesn't include the blank choice, -because the field is obligatory and has an explicit default. ->>> CarForm = form_for_model(Car) ->>> f = CarForm(auto_id=False) ->>> print f -Name: -Steering: -Fuel: -Transmission:
    Leave empty if not applicable. - -Create a Car, and display the form for modifying it. Note that now the fuel -selector doesn't include the blank choice as well, since the field is -obligatory and can not be changed to be blank. ->>> honda = Car(name='Honda Accord Wagon', steering='right', fuel='gas', transmission='at') ->>> honda.save() ->>> HondaForm = form_for_instance(honda) ->>> f = HondaForm(auto_id=False) ->>> print f -Name: -Steering: -Fuel: -Transmission:
    Leave empty if not applicable. """}