Ticket #3297: 5741-newforms_admin_filefield_with_newforms_admin_integration_save_to_function_proposal.diff

File 5741-newforms_admin_filefield_with_newforms_admin_integration_save_to_function_proposal.diff, 21.9 KB (added by Øyvind Saltvik <oyvind@…>, 12 years ago)

Save to using a function

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

     
    349349            return self._choices
    350350    choices = property(_get_choices)
    351351
     352    def save_form_data(self, instance, data):
     353        setattr(instance, self.name, data)
     354       
    352355    def formfield(self, form_class=forms.CharField, **kwargs):
    353356        "Returns a django.newforms.Field instance for this database Field."
    354357        defaults = {'required': not self.blank, 'label': capfirst(self.verbose_name), 'help_text': self.help_text}
     
    660663        return super(EmailField, self).formfield(**defaults)
    661664
    662665class FileField(Field):
    663     def __init__(self, verbose_name=None, name=None, upload_to='', **kwargs):
     666    def __init__(self, verbose_name=None, name=None, upload_to='', save_to_function=None, **kwargs):
    664667        self.upload_to = upload_to
     668        self.has_save_to_function = save_to_function and True or False
     669        self.save_to_function = save_to_function or (lambda field, instance, filename: '%s' % filename)
    665670        Field.__init__(self, verbose_name, name, **kwargs)
    666671
     672    def get_db_prep_save(self, value):
     673        "Returns field's value prepared for saving into a database."
     674        # Need to convert UploadedFile objects provided via a form to unicode for database insertion
     675        return value and unicode(value) or None
     676
    667677    def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False, follow=True):
    668678        field_list = Field.get_manipulator_fields(self, opts, manipulator, change, name_prefix, rel, follow)
    669679        if not self.blank:
     
    707717        setattr(cls, 'get_%s_size' % self.name, curry(cls._get_FIELD_size, field=self))
    708718        setattr(cls, 'save_%s_file' % self.name, lambda instance, filename, raw_contents, save=True: instance._save_FIELD_file(self, filename, raw_contents, save))
    709719        dispatcher.connect(self.delete_file, signal=signals.post_delete, sender=cls)
     720           
    710721
    711722    def delete_file(self, instance):
    712723        if getattr(instance, self.attname):
     
    740751        f = os.path.join(self.get_directory_name(), get_valid_filename(os.path.basename(filename)))
    741752        return os.path.normpath(f)
    742753
     754    def save_form_data(self, instance, data):
     755        if data:
     756            # Make it possible to save the file after the model instance is saved
     757            # using a non-weak post_save signal so the data exists when the signal
     758            # calls the save_file function. This disconnects the signal and saves
     759            # the model instance again if a signal exists.
     760            def save_form_file(sender, instance, signal):
     761                if signal:
     762                    dispatcher.disconnect(save_form_file, signal=signals.post_save, sender=instance.__class__, weak=False)   
     763                func = getattr(instance, "save_%s_file" % self.name)
     764                func(os.path.join(self.upload_to, self.save_to_function(self, instance, data.filename)),
     765                     data.content, save=signal and True or False)
     766            # Save later or at once
     767            if self.has_save_to_function:
     768                dispatcher.connect(save_form_file, signal=signals.post_save, sender=instance.__class__, weak=False)
     769            else:
     770                save_form_file(None, instance, None)
     771       
     772    def formfield(self, **kwargs):
     773        defaults = {'form_class': forms.FileField}
     774        # If a file has been provided previously, then the form doesn't require
     775        # that a new file is provided this time.
     776        if 'initial' in kwargs:
     777            defaults['required'] = False
     778        defaults.update(kwargs)
     779        return super(FileField, self).formfield(**defaults)
     780
    743781class FilePathField(Field):
    744782    def __init__(self, verbose_name=None, name=None, path='', match=None, recursive=False, **kwargs):
    745783        self.path, self.match, self.recursive = path, match, recursive
     
    788826                setattr(new_object, self.height_field, getattr(original_object, self.height_field))
    789827            new_object.save()
    790828
     829    def formfield(self, **kwargs):
     830        defaults = {'form_class': forms.ImageField}
     831        return super(ImageField, self).formfield(**defaults)
     832
    791833class IntegerField(Field):
    792834    empty_strings_allowed = False
    793835    def get_manipulator_field_objs(self):
  • django/db/models/fields/related.py

     
    710710        "Returns the value of this field in the given model instance."
    711711        return getattr(obj, self.attname).all()
    712712
     713    def save_form_data(self, instance, data):
     714        setattr(instance, self.attname, data)
     715       
    713716    def formfield(self, **kwargs):
    714717        defaults = {'form_class': forms.ModelMultipleChoiceField, 'queryset': self.rel.to._default_manager.all()}
    715718        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

     
    3535            continue
    3636        if fields and f.name not in fields:
    3737            continue
    38         setattr(instance, f.name, cleaned_data[f.name])
     38        f.save_form_data(instance, cleaned_data[f.name])
    3939    if commit:
    4040        instance.save()
    4141        for f in opts.many_to_many:
    4242            if fields and f.name not in fields:
    4343                continue
    4444            if f.name in cleaned_data:
    45                 setattr(instance, f.attname, cleaned_data[f.name])
     45                f.save_form_data(instance, cleaned_data[f.name])
    4646    # GOTCHA: If many-to-many data is given and commit=False, the many-to-many
    4747    # data will be lost. This happens because a many-to-many options cannot be
    4848    # 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 ugettext
    10 from django.utils.encoding import smart_unicode
     10from django.utils.encoding import smart_unicode, StrAndUnicode
    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, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, FileInput
    1414
    1515__all__ = (
    1616    'Field', 'CharField', 'IntegerField',
     
    2020    'RegexField', 'EmailField', 'URLField', 'BooleanField',
    2121    'ChoiceField', 'NullBooleanField', 'MultipleChoiceField',
    2222    'ComboField', 'MultiValueField', 'FloatField', 'DecimalField',
    23     'SplitDateTimeField',
     23    'SplitDateTimeField', 'FileField', 'ImageField'
    2424)
    2525
    2626# These values, if given to to_python(), will trigger the self.required check.
     
    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(ugettext(u"No file was submitted. Check the encoding type on the form."))
     380        except KeyError:
     381            raise ValidationError(ugettext(u"No file was submitted."))
     382        if not f.content:
     383            raise ValidationError(ugettext(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        if f is None:
     394            return None
     395        from PIL import Image
     396        from cStringIO import StringIO
     397        try:
     398            Image.open(StringIO(f.content))
     399        except IOError: # Python Imaging Library doesn't recognize it as an image
     400            raise ValidationError(ugettext(u"Upload a valid image. The file you uploaded was either not an image or a corrupted image."))
     401        return f
     402       
    354403class URLField(RegexField):
    355404    def __init__(self, max_length=None, min_length=None, verify_exists=False,
    356405            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
     
    198199            return
    199200        self.cleaned_data = {}
    200201        for name, field in self.fields.items():
    201             # value_from_datadict() gets the data from the dictionary.
     202            # value_from_datadict() gets the data from the data dictionaries.
    202203            # Each widget type knows how to retrieve its own data, because some
    203204            # widgets split data over several HTML fields.
    204             value = field.widget.value_from_datadict(self.data, self.add_prefix(name))
     205            value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
    205206            try:
    206207                value = field.clean(value)
    207208                self.cleaned_data[name] = value
     
    309310        """
    310311        Returns the data for this BoundField, or None if it wasn't given.
    311312        """
    312         return self.field.widget.value_from_datadict(self.form.data, self.html_name)
     313        return self.field.widget.value_from_datadict(self.form.data, self.form.files, self.html_name)
    313314    data = property(_data)
    314315
    315316    def label_tag(self, contents=None, attrs=None):
  • django/newforms/widgets.py

     
    4747            attrs.update(extra_attrs)
    4848        return attrs
    4949
    50     def value_from_datadict(self, data, name):
     50    def value_from_datadict(self, data, files, name):
    5151        """
    5252        Given a dictionary of data and this widget's name, returns the value
    5353        of this widget. Returns None if it's not provided.
     
    113113        final_attrs = self.build_attrs(attrs, type=self.input_type, name=name)
    114114        return u'\n'.join([(u'<input%s />' % flatatt(dict(value=force_unicode(v), **final_attrs))) for v in value])
    115115
    116     def value_from_datadict(self, data, name):
     116    def value_from_datadict(self, data, files, name):
    117117        if isinstance(data, MultiValueDict):
    118118            return data.getlist(name)
    119119        return data.get(name, None)
     
    121121class FileInput(Input):
    122122    input_type = 'file'
    123123
     124    def render(self, name, value, attrs=None):
     125        return super(FileInput, self).render(name, None, attrs=attrs)
     126       
     127    def value_from_datadict(self, data, files, name):
     128        "File widgets take data from FILES, not POST"
     129        return files.get(name, None)
     130
    124131class Textarea(Widget):
    125132    def __init__(self, attrs=None):
    126133        # The 'rows' and 'cols' attributes are required for HTML correctness.
     
    188195            value = u'1'
    189196        return super(NullBooleanSelect, self).render(name, value, attrs, choices)
    190197
    191     def value_from_datadict(self, data, name):
     198    def value_from_datadict(self, data, files, name):
    192199        value = data.get(name, None)
    193200        return {u'2': True, u'3': False, True: True, False: False}.get(value, None)
    194201
     
    210217        output.append(u'</select>')
    211218        return u'\n'.join(output)
    212219
    213     def value_from_datadict(self, data, name):
     220    def value_from_datadict(self, data, files, name):
    214221        if isinstance(data, MultiValueDict):
    215222            return data.getlist(name)
    216223        return data.get(name, None)
     
    356363        return id_
    357364    id_for_label = classmethod(id_for_label)
    358365
    359     def value_from_datadict(self, data, name):
    360         return [widget.value_from_datadict(data, name + '_%s' % i) for i, widget in enumerate(self.widgets)]
     366    def value_from_datadict(self, data, files, name):
     367        return [widget.value_from_datadict(data, files, name + '_%s' % i) for i, widget in enumerate(self.widgets)]
    361368
    362369    def format_output(self, rendered_widgets):
    363370        """
  • django/contrib/admin/options.py

     
    418418
    419419        inline_formsets = []
    420420        if request.POST:
    421             new_data = request.POST.copy()
    422             if opts.has_field_type(models.FileField):
    423                 new_data.update(request.FILES)
    424             form = ModelForm(new_data)
     421            form = ModelForm(request.POST, request.FILES)
    425422            for FormSet in self.get_inline_formsets():
    426423                inline_formset = FormSet(data=new_data)
    427424                inline_formsets.append(inline_formset)
     
    471468
    472469        inline_formsets = []
    473470        if request.POST:
    474             new_data = request.POST.copy()
    475             if opts.has_field_type(models.FileField):
    476                 new_data.update(request.FILES)
    477             form = ModelForm(new_data)
     471            form = ModelForm(request.POST, request.FILES)
    478472            for FormSet in self.get_inline_formsets():
    479473                inline_formset = FormSet(obj, new_data)
    480474                inline_formsets.append(inline_formset)
  • 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()
     
    25582596the next.
    25592597>>> class MyForm(Form):
    25602598...     def __init__(self, data=None, auto_id=False, field_list=[]):
    2561 ...         Form.__init__(self, data, auto_id)
     2599...         Form.__init__(self, data, auto_id=auto_id)
    25622600...         for field in field_list:
    25632601...             self.fields[field[0]] = field[1]
    25642602>>> field_list = [('field1', CharField()), ('field2', CharField())]
     
    25762614...     default_field_1 = CharField()
    25772615...     default_field_2 = CharField()
    25782616...     def __init__(self, data=None, auto_id=False, field_list=[]):
    2579 ...         Form.__init__(self, data, auto_id)
     2617...         Form.__init__(self, data, auto_id=auto_id)
    25802618...         for field in field_list:
    25812619...             self.fields[field[0]] = field[1]
    25822620>>> field_list = [('field1', CharField()), ('field2', CharField())]
     
    32313269<option value="3" selected="selected">No</option>
    32323270</select>
    32333271
     3272# Forms with FileFields ################################################
     3273
     3274FileFields are a special case because they take their data from the request.FILES,
     3275not request.POST.
     3276
     3277>>> class FileForm(Form):
     3278...     file1 = FileField()
     3279>>> f = FileForm(auto_id=False)
     3280>>> print f
     3281<tr><th>File1:</th><td><input type="file" name="file1" /></td></tr>
     3282
     3283>>> f = FileForm(data={}, files={}, auto_id=False)
     3284>>> print f
     3285<tr><th>File1:</th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="file" name="file1" /></td></tr>
     3286
     3287>>> f = FileForm(data={}, files={'file1': {'filename': 'name', 'content':''}}, auto_id=False)
     3288>>> print f
     3289<tr><th>File1:</th><td><ul class="errorlist"><li>The submitted file is empty.</li></ul><input type="file" name="file1" /></td></tr>
     3290
     3291>>> f = FileForm(data={}, files={'file1': 'something that is not a file'}, auto_id=False)
     3292>>> print f
     3293<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>
     3294
     3295>>> f = FileForm(data={}, files={'file1': {'filename': 'name', 'content':'some content'}}, auto_id=False)
     3296>>> print f
     3297<tr><th>File1:</th><td><input type="file" name="file1" /></td></tr>
     3298>>> f.is_valid()
     3299True
     3300
    32343301# Basic form processing in a view #############################################
    32353302
    32363303>>> from django.template import Template, Context
Back to Top