Ticket #3297: filefield_with_newforms_admin_integration_2.diff

File filefield_with_newforms_admin_integration_2.diff, 20.3 KB (added by Øyvind Saltvik <oyvind@…>, 12 years ago)

Patch that fixes the issues i mentioned

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

     
    342342            return self._choices
    343343    choices = property(_get_choices)
    344344
     345    def save_form_data(self, instance, data):
     346        setattr(instance, self.name, data)
     347       
    345348    def formfield(self, form_class=forms.CharField, **kwargs):
    346349        "Returns a django.newforms.Field instance for this database Field."
    347350        defaults = {'required': not self.blank, 'label': capfirst(self.verbose_name), 'help_text': self.help_text}
     
    657660        self.upload_to = upload_to
    658661        Field.__init__(self, verbose_name, name, **kwargs)
    659662
     663    def get_db_prep_save(self, value):
     664        "Returns field's value prepared for saving into a database."
     665        # Need to convert UploadedFile objects provided via a form to unicode for database insertion
     666        return value and unicode(value) or None
     667
    660668    def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False, follow=True):
    661669        field_list = Field.get_manipulator_fields(self, opts, manipulator, change, name_prefix, rel, follow)
    662670        if not self.blank:
     
    733741        f = os.path.join(self.get_directory_name(), get_valid_filename(os.path.basename(filename)))
    734742        return os.path.normpath(f)
    735743
     744    def save_form_data(self, instance, data):
     745        if data:
     746            getattr(instance, "save_%s_file" % self.name)(os.path.join(self.upload_to, data.filename), data.content, save=False)
     747       
     748    def formfield(self, **kwargs):
     749        defaults = {'form_class': forms.FileField}
     750        # If a file has been provided previously, then the form doesn't require
     751        # that a new file is provided this time.
     752        if 'initial' in kwargs:
     753            defaults['required'] = False
     754        defaults.update(kwargs)
     755        return super(FileField, self).formfield(**defaults)
     756
    736757class FilePathField(Field):
    737758    def __init__(self, verbose_name=None, name=None, path='', match=None, recursive=False, **kwargs):
    738759        self.path, self.match, self.recursive = path, match, recursive
     
    781802                setattr(new_object, self.height_field, getattr(original_object, self.height_field))
    782803            new_object.save()
    783804
     805    def formfield(self, **kwargs):
     806        defaults = {'form_class': forms.ImageField}
     807        return super(ImageField, self).formfield(**defaults)
     808
    784809class IntegerField(Field):
    785810    empty_strings_allowed = False
    786811    def get_manipulator_field_objs(self):
  • django/db/models/fields/related.py

     
    711711        "Returns the value of this field in the given model instance."
    712712        return getattr(obj, self.attname).all()
    713713
     714    def save_form_data(self, instance, data):
     715        setattr(instance, self.attname, data)
     716       
    714717    def formfield(self, **kwargs):
    715718        defaults = {'form_class': forms.ModelMultipleChoiceField, 'queryset': self.rel.to._default_manager.all()}
    716719        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
     
    197198            return
    198199        self.cleaned_data = {}
    199200        for name, field in self.fields.items():
    200             # value_from_datadict() gets the data from the dictionary.
     201            # value_from_datadict() gets the data from the data dictionaries.
    201202            # Each widget type knows how to retrieve its own data, because some
    202203            # widgets split data over several HTML fields.
    203             value = field.widget.value_from_datadict(self.data, self.add_prefix(name))
     204            value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
    204205            try:
    205206                value = field.clean(value)
    206207                self.cleaned_data[name] = value
     
    308309        """
    309310        Returns the data for this BoundField, or None if it wasn't given.
    310311        """
    311         return self.field.widget.value_from_datadict(self.form.data, self.html_name)
     312        return self.field.widget.value_from_datadict(self.form.data, self.form.files, self.html_name)
    312313    data = property(_data)
    313314
    314315    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)
  • django/contrib/admin/options.py

     
    395395        ModelForm = forms.form_for_model(model, formfield_callback=self.formfield_for_dbfield)
    396396
    397397        if request.POST:
    398             new_data = request.POST.copy()
     398            new_data = [request.POST.copy()]
    399399            if opts.has_field_type(models.FileField):
    400                 new_data.update(request.FILES)
    401             form = ModelForm(new_data)
     400                new_data.append(request.FILES)
     401            form = ModelForm(*new_data)
    402402            if form.is_valid():
    403403                return self.save_add(request, model, form, '../%s/')
    404404        else:
     
    440440        ModelForm = forms.form_for_instance(obj, formfield_callback=self.formfield_for_dbfield)
    441441
    442442        if request.POST:
    443             new_data = request.POST.copy()
     443            new_data = [request.POST.copy()]
    444444            if opts.has_field_type(models.FileField):
    445                 new_data.update(request.FILES)
    446             form = ModelForm(new_data)
     445                new_data.append(request.FILES)
     446            form = ModelForm(*new_data)
    447447
    448448            if form.is_valid():
    449449                return self.save_change(request, model, form)
  • django/contrib/admin/urls.py

     
    11from django.conf.urls.defaults import *
    22
    33urlpatterns = patterns('',
    4     #('^$', 'django.contrib.admin.views.main.index'),
     4    ('^$', 'django.contrib.admin.views.main.index'),
    55    ('^r/(\d+)/(.*)/$', 'django.views.defaults.shortcut'),
    66    #('^jsi18n/$', i18n_view, {'packages': 'django.conf'}),
    7     #('^logout/$', 'django.contrib.auth.views.logout'),
     7    ('^logout/$', 'django.contrib.auth.views.logout'),
    88    #('^password_change/$', 'django.contrib.auth.views.password_change'),
    99    #('^password_change/done/$', 'django.contrib.auth.views.password_change_done'),
    1010    ('^template_validator/$', 'django.contrib.admin.views.template.template_validator'),
  • tests/regressiontests/forms/tests.py

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