Ticket #3297: 4722-newforms-file-imagefield.diff

File 4722-newforms-file-imagefield.diff, 11.3 KB (added by David Danier <goliath.mailinglist@…>, 12 years ago)

Alternative implementation, without the need to have some hidden field containing the current value

  • django/db/models/fields/__init__.py

     
    662662            else:
    663663                func(new_data[upload_field_name]["filename"], new_data[upload_field_name]["content"], save)
    664664
     665    def formfield_save_file(self, field_name, new_data, new_object, save=True):
     666        if new_data[field_name]:
     667            func = getattr(new_object, 'save_%s_file' % self.name)
     668            func(new_data[field_name]["filename"], new_data[field_name]["content"], save)
     669
    665670    def get_directory_name(self):
    666671        return os.path.normpath(datetime.datetime.now().strftime(self.upload_to))
    667672
     
    670675        f = os.path.join(self.get_directory_name(), get_valid_filename(os.path.basename(filename)))
    671676        return os.path.normpath(f)
    672677
     678    def formfield(self, **kwargs):
     679        defaults = {'required': not self.blank, 'label': capfirst(self.verbose_name), 'widget': forms.FileInput}
     680        defaults.update(kwargs)
     681        return forms.FileField(**defaults)
     682
    673683class FilePathField(Field):
    674684    def __init__(self, verbose_name=None, name=None, path='', match=None, recursive=False, **kwargs):
    675685        self.path, self.match, self.recursive = path, match, recursive
     
    716726                setattr(new_object, self.height_field, getattr(original_object, self.height_field))
    717727            new_object.save()
    718728
     729    def formfield(self, **kwargs):
     730        defaults = {'required': not self.blank, 'label': capfirst(self.verbose_name), 'widget': forms.FileInput}
     731        defaults.update(kwargs)
     732        return forms.ImageField(**defaults)
     733
    719734class IntegerField(Field):
    720735    empty_strings_allowed = False
    721736    def get_manipulator_field_objs(self):
  • django/newforms/models.py

     
    1818
    1919    This method is created for any form_for_model Form.
    2020    """
     21    from django.db import models
    2122    if self.errors:
    2223        raise ValueError("The %s could not be created because the data didn't validate." % self._model._meta.object_name)
    2324    return save_instance(self, self._model(), commit)
     
    3637        raise ValueError("The %s could not be changed because the data didn't validate." % opts.object_name)
    3738    clean_data = form.clean_data
    3839    for f in opts.fields:
    39         if not f.editable or isinstance(f, models.AutoField):
    40             continue
    41         setattr(instance, f.name, clean_data[f.name])
     40        if clean_data.has_key(f.name):
     41            if not f.editable or isinstance(f, models.AutoField):
     42                continue
     43            if isinstance(f, models.FileField):
     44                continue
     45            setattr(instance, f.name, clean_data[f.name])
     46
     47    # FileField may need more info to save to specific paths
     48    for f in opts.fields:
     49        if isinstance(f, models.FileField) and clean_data.has_key(f.name):
     50            f.formfield_save_file(f.name, clean_data, instance, save=False)
     51
    4252    if commit:
    4353        instance.save()
    4454        for f in opts.many_to_many:
     
    8797    takes a database Field instance, plus **kwargs, and returns a form Field
    8898    instance with the given kwargs (i.e. 'initial').
    8999    """
     100    from django.db import models
    90101    model = instance.__class__
    91102    opts = model._meta
    92103    field_list = []
     
    96107        current_value = f.value_from_object(instance)
    97108        formfield = formfield_callback(f, initial=current_value)
    98109        if formfield:
     110            # FileFields are only required once, because we cannot print the
     111            # existing data into the <form>
     112            if isinstance(f, models.FileField) and current_value:
     113                formfield.has_file_uploaded = False
    99114            field_list.append((f.name, formfield))
    100115    fields = SortedDictFromList(field_list)
    101116    return type(opts.object_name + 'InstanceForm', (form,),
  • django/newforms/fields.py

     
    1010import time
    1111
    1212__all__ = (
    13     'Field', 'CharField', 'IntegerField',
     13    'Field', 'CharField', 'FileField', 'ImageField', 'IntegerField',
    1414    'DEFAULT_DATE_INPUT_FORMATS', 'DateField',
    1515    'DEFAULT_TIME_INPUT_FORMATS', 'TimeField',
    1616    'DEFAULT_DATETIME_INPUT_FORMATS', 'DateTimeField',
     
    107107        if self.max_length is not None and isinstance(widget, (TextInput, PasswordInput)):
    108108            return {'maxlength': str(self.max_length)}
    109109
     110class FileField(Field):
     111    prev_error = False
     112    has_file_uploaded = False
     113   
     114    def clean(self, value):
     115        super(FileField, self).clean(value)
     116        if value:
     117            try:
     118                content = value['content']
     119            except TypeError:
     120                self.prev_error = True
     121                raise ValidationError(gettext("No file was submitted. Check the encoding type on the form."))
     122            if not content:
     123                self.prev_error = True
     124                raise ValidationError(gettext(u'The submitted file is empty.'))
     125        elif self.required and not self.has_file_uploaded:
     126            if not value:
     127                self.prev_error = True
     128                raise ValidationError(gettext(u'This field is required.'))
     129        return value
     130
     131class ImageField(FileField):
     132    def clean(self, value):
     133        super(ImageField, self).clean(value)
     134        if value and not self.prev_error:
     135            from PIL import Image
     136            from cStringIO import StringIO
     137            try:
     138                Image.open(StringIO(value['content']))
     139            except IOError: # Python Imaging Library doesn't recognize it as an image
     140                raise ValidationError, gettext(u'Upload a valid image. The file you uploaded was either not an image or a corrupted image.')
     141        return value
     142
    110143class IntegerField(Field):
    111144    def __init__(self, max_value=None, min_value=None, *args, **kwargs):
    112145        self.max_value, self.min_value = max_value, min_value
  • tests/modeltests/model_forms/models.py

     
    6161    def __str__(self):
    6262        return self.phone
    6363
     64class NotRequiredFile(models.Model):
     65    description = models.CharField(maxlength=50)
     66    file = models.FileField(upload_to='files', blank=True)
     67
     68    def __str__(self):
     69        return self.description
     70
     71class RequiredFile(models.Model):
     72    description = models.CharField(maxlength=50)
     73    file = models.FileField(upload_to='files')
     74
     75    def __str__(self):
     76        return self.description
     77
    6478__test__ = {'API_TESTS': """
    6579>>> from django.newforms import form_for_model, form_for_instance, save_instance, BaseForm, Form, CharField
    6680>>> import datetime
     
    377391
    378392>>> f = ModelChoiceField(Category.objects.filter(pk=1), required=False)
    379393>>> print f.clean('')
     394
     395Test for filefield
     396
     397>>> form_data = {'description': 'FileField test', 'file_file': {'content': 'FileField test', 'filename': 'filetest.txt'} }
     398>>> no_file_form_data = {'description': 'FileField test'}
     399>>> just_path_form_data = {'description': 'FileField test', 'file': 'files/filetest.txt'}
     400>>> bad_encoding_form_data = {'description': 'FileField test', 'file': 'files/filetest.txt', 'file_file': 'wrong encoding'}
     401>>> TestRequiredFileForm = form_for_model(RequiredFile)
     402>>> empty_required_fileform = TestRequiredFileForm(auto_id=False)
     403>>> print empty_required_fileform.as_ul()
     404<li>Description: <input type="text" name="description" maxlength="50" /></li>
     405<li>File: <input type="file" name="file_file" /></li>
     406
     407Test with data
     408>>> filled_required_fileform= TestRequiredFileForm(form_data, auto_id=False)
     409>>> filled_required_fileform.is_valid()
     410True
     411
     412>>> required_file_instance = filled_required_fileform.save()
     413>>> print required_file_instance.file
     414files/filetest.txt
     415>>> TestInstanceRequiredFileForm = form_for_instance(required_file_instance)
     416>>> instance_required_fileform = TestInstanceRequiredFileForm(auto_id=False)
     417>>> instance_required_fileform.as_ul()
     418u'<li>Description: <input type="text" name="description" value="FileField test" maxlength="50" /></li>\\n<li>File: Currently: files/filetest.txt<br/>Change: <input type="file" name="file_file" /> <input type="hidden" name="file" value="files/filetest.txt" /></li>'
     419>>> required_file_instance.delete()
     420
     421Bad data test
     422>>> filled_required_fileform=TestRequiredFileForm(no_file_form_data)
     423>>> filled_required_fileform.is_valid()
     424False
     425>>> print filled_required_fileform.errors
     426<ul class="errorlist"><li>file<ul class="errorlist"><li>This field is required.</li></ul></li></ul>
     427>>> TestNotRequiredFileForm = form_for_model(NotRequiredFile)
     428>>> filled_not_required_fileform=TestNotRequiredFileForm(no_file_form_data)
     429>>> filled_not_required_fileform.is_valid()
     430True
     431>>> filled_not_required_fileform=TestNotRequiredFileForm(bad_encoding_form_data)
     432>>> print filled_not_required_fileform.errors
     433<ul class="errorlist"><li>file<ul class="errorlist"><li>No file was submitted. Check the encoding type on the form.</li></ul></li></ul>
     434>>> filled_not_required_fileform=TestNotRequiredFileForm(just_path_form_data)
     435>>> filled_not_required_fileform.is_valid()
     436True
     437>>> filled_required_fileform=TestRequiredFileForm(just_path_form_data)
     438>>> filled_required_fileform.is_valid()
     439True
    380440None
    381441>>> f.clean('')
    382442>>> f.clean('1')
  • tests/regressiontests/forms/tests.py

     
    165165# FileInput Widget ############################################################
    166166
    167167>>> w = FileInput()
    168 >>> w.render('email', '')
    169 u'<input type="file" name="email" />'
    170 >>> w.render('email', None)
    171 u'<input type="file" name="email" />'
    172 >>> w.render('email', 'test@example.com')
    173 u'<input type="file" name="email" value="test@example.com" />'
    174 >>> w.render('email', 'some "quoted" & ampersanded value')
    175 u'<input type="file" name="email" value="some &quot;quoted&quot; &amp; ampersanded value" />'
    176 >>> w.render('email', 'test@example.com', attrs={'class': 'fun'})
    177 u'<input type="file" name="email" value="test@example.com" class="fun" />'
     168>>> w.render('file', '')
     169u'<input type="file" name="file_file" />'
     170>>> w.render('file', 'file/filetest.txt')
     171u'Currently: file/filetest.txt<br/>Change: <input type="file" name="file_file" /> <input type="hidden" name="file" value="file/filetest.txt" />'
    178172
    179 You can also pass 'attrs' to the constructor:
    180 >>> w = FileInput(attrs={'class': 'fun'})
    181 >>> w.render('email', '')
    182 u'<input type="file" class="fun" name="email" />'
    183 >>> w.render('email', 'foo@example.com')
    184 u'<input type="file" class="fun" value="foo@example.com" name="email" />'
    185 
    186 >>> w.render('email', 'ŠĐĆŽćžšđ', attrs={'class': 'fun'})
    187 u'<input type="file" class="fun" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" name="email" />'
    188 
    189173# Textarea Widget #############################################################
    190174
    191175>>> w = Textarea()
Back to Top