From 1515b13d1a83a713d7a2fffb661a1251b483ed37 Mon Sep 17 00:00:00 2001 From: Karen Tracey Date: Tue, 4 Nov 2008 19:48:35 +0000 Subject: [PATCH] Fixed #9418 -- When saving a model form, defer saving of file-type fields until after other fields, so that callable upload_to methods can use data from the other fields. Thanks to Bernd Schlapsi for the report and initial patch. git-svn-id: http://code.djangoproject.com/svn/django/trunk@9334 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- AUTHORS | 1 + django/forms/models.py | 10 ++++++++++ tests/modeltests/model_forms/models.py | 18 ++++++++++++++++-- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/AUTHORS b/AUTHORS index c173a202a5..93f3684a25 100644 --- a/AUTHORS +++ b/AUTHORS @@ -344,6 +344,7 @@ answer newbie questions, and generally made Django that much better: Ivan Sagalaev (Maniac) Vinay Sajip David Schein + Bernd Schlapsi scott@staplefish.com Ilya Semenov serbaut@gmail.com diff --git a/django/forms/models.py b/django/forms/models.py index 99f7ef5bfb..cd2191307e 100644 --- a/django/forms/models.py +++ b/django/forms/models.py @@ -41,6 +41,7 @@ def save_instance(form, instance, fields=None, fail_message='saved', raise ValueError("The %s could not be %s because the data didn't" " validate." % (opts.object_name, fail_message)) cleaned_data = form.cleaned_data + file_field_list = [] for f in opts.fields: if not f.editable or isinstance(f, models.AutoField) \ or not f.name in cleaned_data: @@ -49,7 +50,16 @@ def save_instance(form, instance, fields=None, fail_message='saved', continue if exclude and f.name in exclude: continue + # Defer saving file-type fields until after the other fields, so a + # callable upload_to can use the values from other fields. + if isinstance(f, models.FileField): + file_field_list.append(f) + else: + f.save_form_data(instance, cleaned_data[f.name]) + + for f in file_field_list: f.save_form_data(instance, cleaned_data[f.name]) + # Wrap up the saving of m2m data as a function. def save_m2m(): opts = instance._meta diff --git a/tests/modeltests/model_forms/models.py b/tests/modeltests/model_forms/models.py index 0df21b6aa2..01728a997e 100644 --- a/tests/modeltests/model_forms/models.py +++ b/tests/modeltests/model_forms/models.py @@ -99,6 +99,10 @@ class TextFile(models.Model): return self.description class ImageFile(models.Model): + def custom_upload_path(self, filename): + path = self.path or 'tests' + return '%s/%s' % (path, filename) + description = models.CharField(max_length=20) try: # If PIL is available, try testing PIL. @@ -106,9 +110,10 @@ class ImageFile(models.Model): # for PyPy, you need to check for the underlying modules # If PIL is not available, this test is equivalent to TextFile above. from PIL import Image, _imaging - image = models.ImageField(storage=temp_storage, upload_to='tests') + image = models.ImageField(storage=temp_storage, upload_to=custom_upload_path) except ImportError: - image = models.FileField(storage=temp_storage, upload_to='tests') + image = models.FileField(storage=temp_storage, upload_to=custom_upload_path) + path = models.CharField(max_length=16, blank=True, default='') def __unicode__(self): return self.description @@ -1122,6 +1127,15 @@ True <...FieldFile: tests/test3.png> >>> instance.delete() +# Test callable upload_to behavior that's dependent on the value of another field in the model +>>> f = ImageFileForm(data={'description': u'And a final one', 'path': 'foo'}, files={'image': SimpleUploadedFile('test4.png', image_data)}) +>>> f.is_valid() +True +>>> instance = f.save() +>>> instance.image +<...FieldFile: foo/test4.png> +>>> instance.delete() + # Media on a ModelForm ######################################################## # Similar to a regular Form class you can define custom media to be used on