Fixed #4001 -- Added dynamic save_m2m method() to forms created with form_for_model and form_for_instance on save(commit=False).

git-svn-id: http://code.djangoproject.com/svn/django/trunk@5804 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Russell Keith-Magee 2007-08-05 07:39:36 +00:00
parent 212ee65be7
commit f96e933534
3 changed files with 64 additions and 6 deletions

View File

@ -35,17 +35,23 @@ def save_instance(form, instance, fields=None, fail_message='saved', commit=True
if fields and f.name not in fields: if fields and f.name not in fields:
continue continue
setattr(instance, f.name, cleaned_data[f.name]) setattr(instance, f.name, cleaned_data[f.name])
if commit: # Wrap up the saving of m2m data as a function
instance.save() def save_m2m():
opts = instance.__class__._meta
cleaned_data = form.cleaned_data
for f in opts.many_to_many: for f in opts.many_to_many:
if fields and f.name not in fields: if fields and f.name not in fields:
continue continue
if f.name in cleaned_data: if f.name in cleaned_data:
setattr(instance, f.attname, cleaned_data[f.name]) setattr(instance, f.attname, cleaned_data[f.name])
# GOTCHA: If many-to-many data is given and commit=False, the many-to-many if commit:
# data will be lost. This happens because a many-to-many options cannot be # If we are committing, save the instance and the m2m data immediately
# set on an object until after it's saved. Maybe we should raise an instance.save()
# exception in that case. save_m2m()
else:
# We're not committing. Add a method to the form to allow deferred
# saving of m2m data
form.save_m2m = save_m2m
return instance return instance
def make_model_save(model, fields, fail_message): def make_model_save(model, fields, fail_message):

View File

@ -1502,6 +1502,36 @@ 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 model instance. This is useful if you want to do custom processing on the
object before saving it. ``commit`` is ``True`` by default. 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 have manually saved the instance produced by the form, you can invoke
``save_m2m()`` to save the many-to-many form data::
# 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
...
# 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 - include
many-to-many data - is saved without the need for any additional method calls.
Using an alternate base class Using an alternate base class
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -332,6 +332,28 @@ Create a new article, with no categories, via the form.
>>> new_art.categories.all() >>> new_art.categories.all()
[] []
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', 'pub_date': u'1967-11-01',
... 'writer': u'1', 'article': u'Test.', 'categories': [u'1', u'2']})
>>> new_art = f.save(commit=False)
# Manually save the instance
>>> new_art.save()
>>> new_art.id
4
# The instance doesn't have m2m data yet
>>> new_art = Article.objects.get(id=4)
>>> new_art.categories.all()
[]
# Save the m2m data on the form
>>> f.save_m2m()
>>> new_art.categories.all()
[<Category: Entertainment>, <Category: It's a test>]
Here, we define a custom Form. Because it happens to have the same fields as 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 the Category model, we can use save_instance() to apply its changes to an
existing Category instance. existing Category instance.