Ticket #3297: filefield.diff

File filefield.diff, 18.1 KB (added by Russell Keith-Magee, 12 years ago)

Slight modifications to FileField and ImageField implementation; against [5428]

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

     
    345345            return self._choices
    346346    choices = property(_get_choices)
    347347
     348    def save_form_data(self, instance, data):
     349        setattr(instance, self.name, data)
     350       
    348351    def formfield(self, form_class=forms.CharField, **kwargs):
    349352        "Returns a django.newforms.Field instance for this database Field."
    350353        defaults = {'required': not self.blank, 'label': capfirst(self.verbose_name), 'help_text': self.help_text}
     
    660663        self.upload_to = upload_to
    661664        Field.__init__(self, verbose_name, name, **kwargs)
    662665
     666    def get_db_prep_save(self, value):
     667        "Returns field's value prepared for saving into a database."
     668        # Need to convert UploadedFile objects provided via a form to unicode for database insertion
     669        return value and unicode(value) or None
     670
    663671    def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False, follow=True):
    664672        field_list = Field.get_manipulator_fields(self, opts, manipulator, change, name_prefix, rel, follow)
    665673        if not self.blank:
     
    736744        f = os.path.join(self.get_directory_name(), get_valid_filename(os.path.basename(filename)))
    737745        return os.path.normpath(f)
    738746
     747    def save_form_data(self, instance, data):
     748        if data:
     749            getattr(instance, "save_%s_file" % self.name)(os.path.join(self.upload_to, data.filename), data.content)
     750       
     751    def formfield(self, **kwargs):
     752        defaults = {'form_class': forms.FileField}
     753        # If a file has been provided previously, then the form doesn't require
     754        # that a new file is provided this time.
     755        if 'initial' in kwargs:
     756            defaults['required'] = False
     757        defaults.update(kwargs)
     758        return super(FileField, self).formfield(**defaults)
     759
    739760class FilePathField(Field):
    740761    def __init__(self, verbose_name=None, name=None, path='', match=None, recursive=False, **kwargs):
    741762        self.path, self.match, self.recursive = path, match, recursive
     
    784805                setattr(new_object, self.height_field, getattr(original_object, self.height_field))
    785806            new_object.save()
    786807
     808    def formfield(self, **kwargs):
     809        defaults = {'form_class': forms.ImageField}
     810        return super(ImageField, self).formfield(**defaults)
     811
    787812class IntegerField(Field):
    788813    empty_strings_allowed = False
    789814    def get_manipulator_field_objs(self):
  • django/db/models/fields/related.py

     
    737737        "Returns the value of this field in the given model instance."
    738738        return getattr(obj, self.attname).all()
    739739
     740    def save_form_data(self, instance, data):
     741        setattr(instance, self.attname, data)
     742       
    740743    def formfield(self, **kwargs):
    741744        defaults = {'form_class': forms.ModelMultipleChoiceField, 'queryset': self.rel.to._default_manager.all()}
    742745        defaults.update(kwargs)
  • django/newforms/extras/widgets.py

     
    5353
    5454        return u'\n'.join(output)
    5555
    56     def value_from_datadict(self, data, name):
     56    def value_from_datadict(self, data, files, name):
    5757        y, m, d = data.get(self.year_field % name), data.get(self.month_field % name), data.get(self.day_field % name)
    5858        if y and m and d:
    5959            return '%s-%s-%s' % (y, m, d)
  • django/newforms/models.py

     
    3333            continue
    3434        if fields and f.name not in fields:
    3535            continue
    36         setattr(instance, f.name, cleaned_data[f.name])
     36        f.save_form_data(instance, cleaned_data[f.name])       
    3737    if commit:
    3838        instance.save()
    3939        for f in opts.many_to_many:
    4040            if fields and f.name not in fields:
    4141                continue
    4242            if f.name in cleaned_data:
    43                 setattr(instance, f.attname, cleaned_data[f.name])
     43                f.save_form_data(instance, cleaned_data[f.name])
    4444    # GOTCHA: If many-to-many data is given and commit=False, the many-to-many
    4545    # data will be lost. This happens because a many-to-many options cannot be
    4646    # set on an object until after it's saved. Maybe we should raise an
  • django/newforms/fields.py

     
    77import time
    88
    99from django.utils.translation import gettext
    10 from django.utils.encoding import smart_unicode
     10from django.utils.encoding import StrAndUnicode, smart_unicode
    1111
    1212from util import ErrorList, ValidationError
    13 from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple
     13from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, FileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple
    1414
    1515__all__ = (
    1616    'Field', 'CharField', 'IntegerField',
    1717    'DEFAULT_DATE_INPUT_FORMATS', 'DateField',
    1818    'DEFAULT_TIME_INPUT_FORMATS', 'TimeField',
    1919    'DEFAULT_DATETIME_INPUT_FORMATS', 'DateTimeField',
    20     'RegexField', 'EmailField', 'URLField', 'BooleanField',
     20    'RegexField', 'EmailField', 'FileField', 'ImageField', 'URLField', 'BooleanField',
    2121    'ChoiceField', 'NullBooleanField', 'MultipleChoiceField',
    2222    'ComboField', 'MultiValueField', 'FloatField', 'DecimalField',
    2323    'SplitDateTimeField',
     
    351351    # It's OK if Django settings aren't configured.
    352352    URL_VALIDATOR_USER_AGENT = 'Django (http://www.djangoproject.com/)'
    353353
     354class UploadedFile(StrAndUnicode):
     355    "A wrapper for files uploaded in a FileField"
     356    def __init__(self, filename, content):
     357        self.filename = filename
     358        self.content = content
     359       
     360    def __unicode__(self):
     361        """
     362        The unicode representation is the filename, so that the pre-database-insertion
     363        logic can use UploadedFile objects
     364        """
     365        return self.filename
     366
     367class FileField(Field):
     368    widget = FileInput
     369    def __init__(self, *args, **kwargs):
     370        super(FileField, self).__init__(*args, **kwargs)
     371
     372    def clean(self, data):
     373        super(FileField, self).clean(data)
     374        if not self.required and data in EMPTY_VALUES:
     375            return None
     376        try:
     377            f = UploadedFile(data['filename'], data['content'])
     378        except TypeError:
     379            raise ValidationError(gettext(u"No file was submitted. Check the encoding type on the form."))
     380        except KeyError:
     381            raise ValidationError(gettext(u"No file was submitted."))
     382        if not f.content:
     383            raise ValidationError(gettext(u"The submitted file is empty."))
     384        return f
     385
     386class ImageField(FileField):
     387    def clean(self, data):
     388        """
     389        Checks that the file-upload field data contains a valid image (GIF, JPG,
     390        PNG, possibly others -- whatever the Python Imaging Library supports).
     391        """
     392        f = super(ImageField, self).clean(data)
     393        from PIL import Image
     394        from cStringIO import StringIO
     395        try:
     396            Image.open(StringIO(f.content))
     397        except IOError: # Python Imaging Library doesn't recognize it as an image
     398            raise ValidationError(gettext(u"Upload a valid image. The file you uploaded was either not an image or a corrupted image."))
     399        return f
     400       
    354401class URLField(RegexField):
    355402    def __init__(self, max_length=None, min_length=None, verify_exists=False,
    356403            validator_user_agent=URL_VALIDATOR_USER_AGENT, *args, **kwargs):
  • django/newforms/forms.py

     
    5757    # class is different than Form. See the comments by the Form class for more
    5858    # information. Any improvements to the form API should be made to *this*
    5959    # class, not to the Form class.
    60     def __init__(self, data=None, auto_id='id_%s', prefix=None, initial=None):
    61         self.is_bound = data is not None
     60    def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, initial=None):
     61        self.is_bound = data is not None or files is not None
    6262        self.data = data or {}
     63        self.files = files or {}
    6364        self.auto_id = auto_id
    6465        self.prefix = prefix
    6566        self.initial = initial or {}
     
    8889        return BoundField(self, field, name)
    8990
    9091    def _get_errors(self):
    91         "Returns an ErrorDict for self.data"
     92        "Returns an ErrorDict for the data provided for the form"
    9293        if self._errors is None:
    9394            self.full_clean()
    9495        return self._errors
     
    179180            return
    180181        self.cleaned_data = {}
    181182        for name, field in self.fields.items():
    182             # value_from_datadict() gets the data from the dictionary.
     183            # value_from_datadict() gets the data from the data dictionaries.
    183184            # Each widget type knows how to retrieve its own data, because some
    184185            # widgets split data over several HTML fields.
    185             value = field.widget.value_from_datadict(self.data, self.add_prefix(name))
     186            value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
    186187            try:
    187188                value = field.clean(value)
    188189                self.cleaned_data[name] = value
     
    284285        """
    285286        Returns the data for this BoundField, or None if it wasn't given.
    286287        """
    287         return self.field.widget.value_from_datadict(self.form.data, self.html_name)
     288        return self.field.widget.value_from_datadict(self.form.data, self.form.files, self.html_name)
    288289    data = property(_data)
    289290
    290291    def label_tag(self, contents=None, attrs=None):
  • django/newforms/widgets.py

     
    4848            attrs.update(extra_attrs)
    4949        return attrs
    5050
    51     def value_from_datadict(self, data, name):
     51    def value_from_datadict(self, data, files, name):
    5252        """
    5353        Given a dictionary of data and this widget's name, returns the value
    5454        of this widget. Returns None if it's not provided.
     
    114114        final_attrs = self.build_attrs(attrs, type=self.input_type, name=name)
    115115        return u'\n'.join([(u'<input%s />' % flatatt(dict(value=smart_unicode(v), **final_attrs))) for v in value])
    116116
    117     def value_from_datadict(self, data, name):
     117    def value_from_datadict(self, data, files, name):
    118118        if isinstance(data, MultiValueDict):
    119119            return data.getlist(name)
    120120        return data.get(name, None)
     
    122122class FileInput(Input):
    123123    input_type = 'file'
    124124
     125    def render(self, name, value, attrs=None):
     126        return super(FileInput, self).render(name, None, attrs=attrs)
     127       
     128    def value_from_datadict(self, data, files, name):
     129        "File widgets take data from FILES, not POST"
     130        return files.get(name, None)
     131
    125132class Textarea(Widget):
    126133    def __init__(self, attrs=None):
    127134        # The 'rows' and 'cols' attributes are required for HTML correctness.
     
    189196            value = u'1'
    190197        return super(NullBooleanSelect, self).render(name, value, attrs, choices)
    191198
    192     def value_from_datadict(self, data, name):
     199    def value_from_datadict(self, data, files, name):
    193200        value = data.get(name, None)
    194201        return {u'2': True, u'3': False, True: True, False: False}.get(value, None)
    195202
     
    211218        output.append(u'</select>')
    212219        return u'\n'.join(output)
    213220
    214     def value_from_datadict(self, data, name):
     221    def value_from_datadict(self, data, files, name):
    215222        if isinstance(data, MultiValueDict):
    216223            return data.getlist(name)
    217224        return data.get(name, None)
     
    348355        return id_
    349356    id_for_label = classmethod(id_for_label)
    350357
    351     def value_from_datadict(self, data, name):
    352         return [widget.value_from_datadict(data, name + '_%s' % i) for i, widget in enumerate(self.widgets)]
     358    def value_from_datadict(self, data, files, name):
     359        return [widget.value_from_datadict(data, files, name + '_%s' % i) for i, widget in enumerate(self.widgets)]
    353360
    354361    def format_output(self, rendered_widgets):
    355362        return u''.join(rendered_widgets)
  • tests/regressiontests/forms/tests.py

     
    172172
    173173# FileInput Widget ############################################################
    174174
     175FileInput widgets don't ever show the value, because the old value is of no use
     176if you are updating the form or if the provided file generated an error.
    175177>>> w = FileInput()
    176178>>> w.render('email', '')
    177179u'<input type="file" name="email" />'
    178180>>> w.render('email', None)
    179181u'<input type="file" name="email" />'
    180182>>> w.render('email', 'test@example.com')
    181 u'<input type="file" name="email" value="test@example.com" />'
     183u'<input type="file" name="email" />'
    182184>>> w.render('email', 'some "quoted" & ampersanded value')
    183 u'<input type="file" name="email" value="some &quot;quoted&quot; &amp; ampersanded value" />'
     185u'<input type="file" name="email" />'
    184186>>> w.render('email', 'test@example.com', attrs={'class': 'fun'})
    185 u'<input type="file" name="email" value="test@example.com" class="fun" />'
     187u'<input type="file" name="email" class="fun" />'
    186188
    187189You can also pass 'attrs' to the constructor:
    188190>>> w = FileInput(attrs={'class': 'fun'})
    189191>>> w.render('email', '')
    190192u'<input type="file" class="fun" name="email" />'
    191193>>> w.render('email', 'foo@example.com')
    192 u'<input type="file" class="fun" value="foo@example.com" name="email" />'
     194u'<input type="file" class="fun" name="email" />'
    193195
    194196>>> w.render('email', 'ŠĐĆŽćžšđ', attrs={'class': 'fun'})
    195 u'<input type="file" class="fun" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" name="email" />'
     197u'<input type="file" class="fun" name="email" />'
    196198
    197199# Textarea Widget #############################################################
    198200
     
    15161518...
    15171519ValidationError: [u'Ensure this value has at most 15 characters.']
    15181520
     1521# FileField ##################################################################
     1522
     1523>>> f = FileField()
     1524>>> f.clean('')
     1525Traceback (most recent call last):
     1526...
     1527ValidationError: [u'This field is required.']
     1528
     1529>>> f.clean(None)
     1530Traceback (most recent call last):
     1531...
     1532ValidationError: [u'This field is required.']
     1533
     1534>>> f.clean({})
     1535Traceback (most recent call last):
     1536...
     1537ValidationError: [u'No file was submitted.']
     1538
     1539>>> f.clean('some content that is not a file')
     1540Traceback (most recent call last):
     1541...
     1542ValidationError: [u'No file was submitted. Check the encoding type on the form.']
     1543
     1544>>> f.clean({'filename': 'name', 'content':None})
     1545Traceback (most recent call last):
     1546...
     1547ValidationError: [u'The submitted file is empty.']
     1548
     1549>>> f.clean({'filename': 'name', 'content':''})
     1550Traceback (most recent call last):
     1551...
     1552ValidationError: [u'The submitted file is empty.']
     1553
     1554>>> type(f.clean({'filename': 'name', 'content':'Some File Content'}))
     1555<class 'django.newforms.fields.UploadedFile'>
     1556
    15191557# URLField ##################################################################
    15201558
    15211559>>> f = URLField()
     
    25412579the next.
    25422580>>> class MyForm(Form):
    25432581...     def __init__(self, data=None, auto_id=False, field_list=[]):
    2544 ...         Form.__init__(self, data, auto_id)
     2582...         Form.__init__(self, data, auto_id=auto_id)
    25452583...         for field in field_list:
    25462584...             self.fields[field[0]] = field[1]
    25472585>>> field_list = [('field1', CharField()), ('field2', CharField())]
     
    25592597...     default_field_1 = CharField()
    25602598...     default_field_2 = CharField()
    25612599...     def __init__(self, data=None, auto_id=False, field_list=[]):
    2562 ...         Form.__init__(self, data, auto_id)
     2600...         Form.__init__(self, data, auto_id=auto_id)
    25632601...         for field in field_list:
    25642602...             self.fields[field[0]] = field[1]
    25652603>>> field_list = [('field1', CharField()), ('field2', CharField())]
     
    32143252<option value="3" selected="selected">No</option>
    32153253</select>
    32163254
     3255# Forms with FileFields ################################################
     3256
     3257FileFields are a special case because they take their data from the request.FILES,
     3258not request.POST.
     3259
     3260>>> class FileForm(Form):
     3261...     file1 = FileField()
     3262>>> f = FileForm(auto_id=False)
     3263>>> print f
     3264<tr><th>File1:</th><td><input type="file" name="file1" /></td></tr>
     3265
     3266>>> f = FileForm(data={}, files={}, auto_id=False)
     3267>>> print f
     3268<tr><th>File1:</th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="file" name="file1" /></td></tr>
     3269
     3270>>> f = FileForm(data={}, files={'file1': {'filename': 'name', 'content':''}}, auto_id=False)
     3271>>> print f
     3272<tr><th>File1:</th><td><ul class="errorlist"><li>The submitted file is empty.</li></ul><input type="file" name="file1" /></td></tr>
     3273
     3274>>> f = FileForm(data={}, files={'file1': 'something that is not a file'}, auto_id=False)
     3275>>> print f
     3276<tr><th>File1:</th><td><ul class="errorlist"><li>No file was submitted. Check the encoding type on the form.</li></ul><input type="file" name="file1" /></td></tr>
     3277
     3278>>> f = FileForm(data={}, files={'file1': {'filename': 'name', 'content':'some content'}}, auto_id=False)
     3279>>> print f
     3280<tr><th>File1:</th><td><input type="file" name="file1" /></td></tr>
     3281>>> f.is_valid()
     3282True
     3283
    32173284# Basic form processing in a view #############################################
    32183285
    32193286>>> from django.template import Template, Context
Back to Top