diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 139a096b68..f78c81ae39 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -797,13 +797,17 @@ class FileField(Field): return os.path.normpath(f) def save_form_data(self, instance, data): - if data: + from django.newforms.fields import UploadedFile + if data and isinstance(data, UploadedFile): getattr(instance, "save_%s_file" % self.name)(data.filename, data.content, save=False) def formfield(self, **kwargs): defaults = {'form_class': forms.FileField} # If a file has been provided previously, then the form doesn't require # that a new file is provided this time. + # The code to mark the form field as not required is used by + # form_for_instance, but can probably be removed once form_for_instance + # is gone. ModelForm uses a different method to check for an existing file. if 'initial' in kwargs: defaults['required'] = False defaults.update(kwargs) diff --git a/django/newforms/fields.py b/django/newforms/fields.py index 3b8f4195b0..0761b8f2af 100644 --- a/django/newforms/fields.py +++ b/django/newforms/fields.py @@ -437,10 +437,12 @@ class FileField(Field): def __init__(self, *args, **kwargs): super(FileField, self).__init__(*args, **kwargs) - def clean(self, data): - super(FileField, self).clean(data) + def clean(self, data, initial=None): + super(FileField, self).clean(initial or data) if not self.required and data in EMPTY_VALUES: return None + elif not data and initial: + return initial try: f = UploadedFile(data['filename'], data['content']) except TypeError: @@ -456,12 +458,12 @@ class ImageField(FileField): 'invalid_image': _(u"Upload a valid image. The file you uploaded was either not an image or a corrupted image."), } - def clean(self, data): + def clean(self, data, initial=None): """ Checks that the file-upload field data contains a valid image (GIF, JPG, PNG, possibly others -- whatever the Python Imaging Library supports). """ - f = super(ImageField, self).clean(data) + f = super(ImageField, self).clean(data, initial) if f is None: return None from PIL import Image diff --git a/django/newforms/forms.py b/django/newforms/forms.py index 556c00a777..04bf62e445 100644 --- a/django/newforms/forms.py +++ b/django/newforms/forms.py @@ -9,7 +9,7 @@ from django.utils.html import escape from django.utils.encoding import StrAndUnicode, smart_unicode, force_unicode from django.utils.safestring import mark_safe -from fields import Field +from fields import Field, FileField from widgets import TextInput, Textarea from util import flatatt, ErrorDict, ErrorList, ValidationError @@ -182,7 +182,11 @@ class BaseForm(StrAndUnicode): # widgets split data over several HTML fields. value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name)) try: - value = field.clean(value) + if isinstance(field, FileField): + initial = self.initial.get(name, field.initial) + value = field.clean(value, initial) + else: + value = field.clean(value) self.cleaned_data[name] = value if hasattr(self, 'clean_%s' % name): value = getattr(self, 'clean_%s' % name)() diff --git a/tests/modeltests/model_forms/models.py b/tests/modeltests/model_forms/models.py index 17c3b3551c..ff3f20e197 100644 --- a/tests/modeltests/model_forms/models.py +++ b/tests/modeltests/model_forms/models.py @@ -7,6 +7,9 @@ examples are probably a poor fit for the ModelForm syntax. In other words, most of these tests should be rewritten. """ +import os +import tempfile + from django.db import models ARTICLE_STATUS = ( @@ -55,6 +58,13 @@ class PhoneNumber(models.Model): def __unicode__(self): return self.phone +class TextFile(models.Model): + description = models.CharField(max_length=20) + file = models.FileField(upload_to=tempfile.gettempdir()) + + def __unicode__(self): + return self.description + __test__ = {'API_TESTS': """ >>> from django import newforms as forms >>> from django.newforms.models import ModelForm @@ -701,4 +711,75 @@ ValidationError: [u'Select a valid choice. 4 is not one of the available choices True >>> f.cleaned_data {'phone': u'312-555-1212', 'description': u'Assistance'} + +# FileField ################################################################### + +>>> class TextFileForm(ModelForm): +... class Meta: +... model = TextFile + +Test conditions when files is either not given or empty. + +>>> f = TextFileForm(data={'description': u'Assistance'}) +>>> f.is_valid() +False +>>> f = TextFileForm(data={'description': u'Assistance'}, files={}) +>>> f.is_valid() +False + +Upload a file and ensure it all works as expected. + +>>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': {'filename': 'test1.txt', 'content': 'hello world'}}) +>>> f.is_valid() +True +>>> type(f.cleaned_data['file']) + +>>> instance = f.save() +>>> instance.file +u'.../test1.txt' + +Edit an instance that already has the file defined in the model. This will not +save the file again, but leave it exactly as it is. + +>>> f = TextFileForm(data={'description': u'Assistance'}, instance=instance) +>>> f.is_valid() +True +>>> f.cleaned_data['file'] +u'.../test1.txt' +>>> instance = f.save() +>>> instance.file +u'.../test1.txt' + +Delete the current file since this is not done by Django. + +>>> os.unlink(instance.get_file_filename()) + +Override the file by uploading a new one. + +>>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': {'filename': 'test2.txt', 'content': 'hello world'}}, instance=instance) +>>> f.is_valid() +True +>>> instance = f.save() +>>> instance.file +u'.../test2.txt' + +>>> instance.delete() + +Test the non-required FileField + +>>> f = TextFileForm(data={'description': u'Assistance'}) +>>> f.fields['file'].required = False +>>> f.is_valid() +True +>>> instance = f.save() +>>> instance.file +'' + +>>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': {'filename': 'test3.txt', 'content': 'hello world'}}, instance=instance) +>>> f.is_valid() +True +>>> instance = f.save() +>>> instance.file +u'.../test3.txt' +>>> instance.delete() """} diff --git a/tests/regressiontests/forms/fields.py b/tests/regressiontests/forms/fields.py index cff5db6fca..9216210e09 100644 --- a/tests/regressiontests/forms/fields.py +++ b/tests/regressiontests/forms/fields.py @@ -749,32 +749,59 @@ Traceback (most recent call last): ... ValidationError: [u'This field is required.'] +>>> f.clean('', '') +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] + +>>> f.clean('', 'files/test1.pdf') +'files/test1.pdf' + >>> f.clean(None) Traceback (most recent call last): ... ValidationError: [u'This field is required.'] +>>> f.clean(None, '') +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] + +>>> f.clean(None, 'files/test2.pdf') +'files/test2.pdf' + >>> f.clean({}) Traceback (most recent call last): ... ValidationError: [u'No file was submitted.'] +>>> f.clean({}, '') +Traceback (most recent call last): +... +ValidationError: [u'No file was submitted.'] + +>>> f.clean({}, 'files/test3.pdf') +'files/test3.pdf' + >>> f.clean('some content that is not a file') Traceback (most recent call last): ... ValidationError: [u'No file was submitted. Check the encoding type on the form.'] ->>> f.clean({'filename': 'name', 'content':None}) +>>> f.clean({'filename': 'name', 'content': None}) Traceback (most recent call last): ... ValidationError: [u'The submitted file is empty.'] ->>> f.clean({'filename': 'name', 'content':''}) +>>> f.clean({'filename': 'name', 'content': ''}) Traceback (most recent call last): ... ValidationError: [u'The submitted file is empty.'] ->>> type(f.clean({'filename': 'name', 'content':'Some File Content'})) +>>> type(f.clean({'filename': 'name', 'content': 'Some File Content'})) + + +>>> type(f.clean({'filename': 'name', 'content': 'Some File Content'}, 'files/test4.pdf')) # URLField ##################################################################