diff --git a/django/newforms/models.py b/django/newforms/models.py index 591fb9c045..7c6e6ad0ab 100644 --- a/django/newforms/models.py +++ b/django/newforms/models.py @@ -5,7 +5,7 @@ and database field objects. from forms import BaseForm, DeclarativeFieldsMetaclass, SortedDictFromList -__all__ = ('form_for_model', 'form_for_instance', 'form_for_fields') +__all__ = ('save_instance', 'form_for_model', 'form_for_instance', 'form_for_fields') def model_save(self, commit=True): """ @@ -20,27 +20,38 @@ def model_save(self, commit=True): obj.save() return obj -def make_instance_save(opts, instance): - "Returns the save() method for a form_for_instance Form." +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 + ``instance``. If commit=True, then the changes to ``instance`` will be + saved to the database. Returns ``instance``. + """ from django.db import models - def apply_changes(self, commit=True): - if self.errors: - raise ValueError("The %s could not be changed because the data didn't validate." % opts.object_name) - clean_data = self.clean_data - for f in opts.fields + opts.many_to_many: - if isinstance(f, models.AutoField): - continue - setattr(instance, f.attname, clean_data[f.name]) - if commit: - instance.save() - return instance - return apply_changes + opts = instance.__class__._meta + if form.errors: + raise ValueError("The %s could not be changed because the data didn't validate." % opts.object_name) + clean_data = form.clean_data + for f in opts.fields + opts.many_to_many: + if isinstance(f, models.AutoField): + continue + setattr(instance, f.attname, clean_data[f.name]) + if commit: + instance.save() + return instance + +def make_instance_save(instance): + "Returns the save() method for a form_for_instance Form." + def save(self, commit=True): + return save_instance(self, instance, commit) + return save def form_for_model(model, form=BaseForm): """ Returns a Form class for the given Django model class. - Provide 'form' if you want to use a custom BaseForm subclass. + Provide ``form`` if you want to use a custom BaseForm subclass. """ opts = model._meta field_list = [] @@ -55,7 +66,7 @@ def form_for_instance(instance, form=BaseForm): """ Returns a Form class for the given Django model instance. - Provide 'form' if you want to use a custom BaseForm subclass. + Provide ``form`` if you want to use a custom BaseForm subclass. """ model = instance.__class__ opts = model._meta @@ -67,7 +78,7 @@ def form_for_instance(instance, form=BaseForm): field_list.append((f.name, formfield)) fields = SortedDictFromList(field_list) return type(opts.object_name + 'InstanceForm', (form,), - {'fields': fields, '_model': model, 'save': make_instance_save(opts, instance)}) + {'fields': fields, '_model': model, 'save': make_instance_save(instance)}) def form_for_fields(field_list): "Returns a Form class for the given list of Django database field instances." diff --git a/tests/modeltests/model_forms/models.py b/tests/modeltests/model_forms/models.py index 4d52256fed..e2335c9f3e 100644 --- a/tests/modeltests/model_forms/models.py +++ b/tests/modeltests/model_forms/models.py @@ -15,9 +15,11 @@ 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 saves the changes 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. +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 clean_data into the instance. It also takes +a commit=True parameter. """ from django.db import models @@ -45,7 +47,7 @@ class Article(models.Model): return self.headline __test__ = {'API_TESTS': """ ->>> from django.newforms import form_for_model, form_for_instance, BaseForm +>>> from django.newforms import form_for_model, form_for_instance, save_instance, BaseForm, Form, CharField >>> import datetime >>> Category.objects.all() @@ -218,4 +220,20 @@ Add some categories and test the many-to-many form output. +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 +existing Category instance. +>>> class ShortCategory(Form): +... name = CharField(max_length=5) +... url = CharField(max_length=3) +>>> cat = Category.objects.get(name='Third test') +>>> cat + +>>> cat.id +3 +>>> sc = ShortCategory({'name': 'Third', 'url': '3rd'}) +>>> save_instance(sc, cat) + +>>> Category.objects.get(id=3) + """}