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 , 17 years ago) |
---|
-
django/db/models/fields/__init__.py
349 349 return self._choices 350 350 choices = property(_get_choices) 351 351 352 def save_form_data(self, instance, data): 353 setattr(instance, self.name, data) 354 352 355 def formfield(self, form_class=forms.CharField, **kwargs): 353 356 "Returns a django.newforms.Field instance for this database Field." 354 357 defaults = {'required': not self.blank, 'label': capfirst(self.verbose_name), 'help_text': self.help_text} … … 660 663 return super(EmailField, self).formfield(**defaults) 661 664 662 665 class 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): 664 667 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) 665 670 Field.__init__(self, verbose_name, name, **kwargs) 666 671 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 667 677 def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False, follow=True): 668 678 field_list = Field.get_manipulator_fields(self, opts, manipulator, change, name_prefix, rel, follow) 669 679 if not self.blank: … … 707 717 setattr(cls, 'get_%s_size' % self.name, curry(cls._get_FIELD_size, field=self)) 708 718 setattr(cls, 'save_%s_file' % self.name, lambda instance, filename, raw_contents, save=True: instance._save_FIELD_file(self, filename, raw_contents, save)) 709 719 dispatcher.connect(self.delete_file, signal=signals.post_delete, sender=cls) 720 710 721 711 722 def delete_file(self, instance): 712 723 if getattr(instance, self.attname): … … 740 751 f = os.path.join(self.get_directory_name(), get_valid_filename(os.path.basename(filename))) 741 752 return os.path.normpath(f) 742 753 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 743 781 class FilePathField(Field): 744 782 def __init__(self, verbose_name=None, name=None, path='', match=None, recursive=False, **kwargs): 745 783 self.path, self.match, self.recursive = path, match, recursive … … 788 826 setattr(new_object, self.height_field, getattr(original_object, self.height_field)) 789 827 new_object.save() 790 828 829 def formfield(self, **kwargs): 830 defaults = {'form_class': forms.ImageField} 831 return super(ImageField, self).formfield(**defaults) 832 791 833 class IntegerField(Field): 792 834 empty_strings_allowed = False 793 835 def get_manipulator_field_objs(self): -
django/db/models/fields/related.py
710 710 "Returns the value of this field in the given model instance." 711 711 return getattr(obj, self.attname).all() 712 712 713 def save_form_data(self, instance, data): 714 setattr(instance, self.attname, data) 715 713 716 def formfield(self, **kwargs): 714 717 defaults = {'form_class': forms.ModelMultipleChoiceField, 'queryset': self.rel.to._default_manager.all()} 715 718 defaults.update(kwargs) -
django/newforms/extras/widgets.py
53 53 54 54 return u'\n'.join(output) 55 55 56 def value_from_datadict(self, data, name):56 def value_from_datadict(self, data, files, name): 57 57 y, m, d = data.get(self.year_field % name), data.get(self.month_field % name), data.get(self.day_field % name) 58 58 if y and m and d: 59 59 return '%s-%s-%s' % (y, m, d) -
django/newforms/models.py
35 35 continue 36 36 if fields and f.name not in fields: 37 37 continue 38 setattr(instance, f.name, cleaned_data[f.name])38 f.save_form_data(instance, cleaned_data[f.name]) 39 39 if commit: 40 40 instance.save() 41 41 for f in opts.many_to_many: 42 42 if fields and f.name not in fields: 43 43 continue 44 44 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]) 46 46 # GOTCHA: If many-to-many data is given and commit=False, the many-to-many 47 47 # data will be lost. This happens because a many-to-many options cannot be 48 48 # set on an object until after it's saved. Maybe we should raise an -
django/newforms/fields.py
7 7 import time 8 8 9 9 from django.utils.translation import ugettext 10 from django.utils.encoding import smart_unicode 10 from django.utils.encoding import smart_unicode, StrAndUnicode 11 11 12 12 from util import ErrorList, ValidationError 13 from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple 13 from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, FileInput 14 14 15 15 __all__ = ( 16 16 'Field', 'CharField', 'IntegerField', … … 20 20 'RegexField', 'EmailField', 'URLField', 'BooleanField', 21 21 'ChoiceField', 'NullBooleanField', 'MultipleChoiceField', 22 22 'ComboField', 'MultiValueField', 'FloatField', 'DecimalField', 23 'SplitDateTimeField', 23 'SplitDateTimeField', 'FileField', 'ImageField' 24 24 ) 25 25 26 26 # These values, if given to to_python(), will trigger the self.required check. … … 351 351 # It's OK if Django settings aren't configured. 352 352 URL_VALIDATOR_USER_AGENT = 'Django (http://www.djangoproject.com/)' 353 353 354 class 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 367 class 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 386 class 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 354 403 class URLField(RegexField): 355 404 def __init__(self, max_length=None, min_length=None, verify_exists=False, 356 405 validator_user_agent=URL_VALIDATOR_USER_AGENT, *args, **kwargs): -
django/newforms/forms.py
57 57 # class is different than Form. See the comments by the Form class for more 58 58 # information. Any improvements to the form API should be made to *this* 59 59 # 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 62 62 self.data = data or {} 63 self.files = files or {} 63 64 self.auto_id = auto_id 64 65 self.prefix = prefix 65 66 self.initial = initial or {} … … 88 89 return BoundField(self, field, name) 89 90 90 91 def _get_errors(self): 91 "Returns an ErrorDict for self.data"92 "Returns an ErrorDict for the data provided for the form" 92 93 if self._errors is None: 93 94 self.full_clean() 94 95 return self._errors … … 198 199 return 199 200 self.cleaned_data = {} 200 201 for name, field in self.fields.items(): 201 # value_from_datadict() gets the data from the d ictionary.202 # value_from_datadict() gets the data from the data dictionaries. 202 203 # Each widget type knows how to retrieve its own data, because some 203 204 # 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)) 205 206 try: 206 207 value = field.clean(value) 207 208 self.cleaned_data[name] = value … … 309 310 """ 310 311 Returns the data for this BoundField, or None if it wasn't given. 311 312 """ 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) 313 314 data = property(_data) 314 315 315 316 def label_tag(self, contents=None, attrs=None): -
django/newforms/widgets.py
47 47 attrs.update(extra_attrs) 48 48 return attrs 49 49 50 def value_from_datadict(self, data, name):50 def value_from_datadict(self, data, files, name): 51 51 """ 52 52 Given a dictionary of data and this widget's name, returns the value 53 53 of this widget. Returns None if it's not provided. … … 113 113 final_attrs = self.build_attrs(attrs, type=self.input_type, name=name) 114 114 return u'\n'.join([(u'<input%s />' % flatatt(dict(value=force_unicode(v), **final_attrs))) for v in value]) 115 115 116 def value_from_datadict(self, data, name):116 def value_from_datadict(self, data, files, name): 117 117 if isinstance(data, MultiValueDict): 118 118 return data.getlist(name) 119 119 return data.get(name, None) … … 121 121 class FileInput(Input): 122 122 input_type = 'file' 123 123 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 124 131 class Textarea(Widget): 125 132 def __init__(self, attrs=None): 126 133 # The 'rows' and 'cols' attributes are required for HTML correctness. … … 188 195 value = u'1' 189 196 return super(NullBooleanSelect, self).render(name, value, attrs, choices) 190 197 191 def value_from_datadict(self, data, name):198 def value_from_datadict(self, data, files, name): 192 199 value = data.get(name, None) 193 200 return {u'2': True, u'3': False, True: True, False: False}.get(value, None) 194 201 … … 210 217 output.append(u'</select>') 211 218 return u'\n'.join(output) 212 219 213 def value_from_datadict(self, data, name):220 def value_from_datadict(self, data, files, name): 214 221 if isinstance(data, MultiValueDict): 215 222 return data.getlist(name) 216 223 return data.get(name, None) … … 356 363 return id_ 357 364 id_for_label = classmethod(id_for_label) 358 365 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)] 361 368 362 369 def format_output(self, rendered_widgets): 363 370 """ -
django/contrib/admin/options.py
418 418 419 419 inline_formsets = [] 420 420 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) 425 422 for FormSet in self.get_inline_formsets(): 426 423 inline_formset = FormSet(data=new_data) 427 424 inline_formsets.append(inline_formset) … … 471 468 472 469 inline_formsets = [] 473 470 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) 478 472 for FormSet in self.get_inline_formsets(): 479 473 inline_formset = FormSet(obj, new_data) 480 474 inline_formsets.append(inline_formset) -
django/contrib/admin/urls.py
1 1 from django.conf.urls.defaults import * 2 2 3 3 urlpatterns = patterns('', 4 #('^$', 'django.contrib.admin.views.main.index'),4 ('^$', 'django.contrib.admin.views.main.index'), 5 5 ('^r/(\d+)/(.*)/$', 'django.views.defaults.shortcut'), 6 6 #('^jsi18n/$', i18n_view, {'packages': 'django.conf'}), 7 #('^logout/$', 'django.contrib.auth.views.logout'),7 ('^logout/$', 'django.contrib.auth.views.logout'), 8 8 #('^password_change/$', 'django.contrib.auth.views.password_change'), 9 9 #('^password_change/done/$', 'django.contrib.auth.views.password_change_done'), 10 10 ('^template_validator/$', 'django.contrib.admin.views.template.template_validator'), -
tests/regressiontests/forms/tests.py
173 173 174 174 # FileInput Widget ############################################################ 175 175 176 FileInput widgets don't ever show the value, because the old value is of no use 177 if you are updating the form or if the provided file generated an error. 176 178 >>> w = FileInput() 177 179 >>> w.render('email', '') 178 180 u'<input type="file" name="email" />' 179 181 >>> w.render('email', None) 180 182 u'<input type="file" name="email" />' 181 183 >>> w.render('email', 'test@example.com') 182 u'<input type="file" name="email" value="test@example.com"/>'184 u'<input type="file" name="email" />' 183 185 >>> w.render('email', 'some "quoted" & ampersanded value') 184 u'<input type="file" name="email" value="some "quoted" & ampersanded value"/>'186 u'<input type="file" name="email" />' 185 187 >>> w.render('email', 'test@example.com', attrs={'class': 'fun'}) 186 u'<input type="file" name="email" value="test@example.com"class="fun" />'188 u'<input type="file" name="email" class="fun" />' 187 189 188 190 You can also pass 'attrs' to the constructor: 189 191 >>> w = FileInput(attrs={'class': 'fun'}) 190 192 >>> w.render('email', '') 191 193 u'<input type="file" class="fun" name="email" />' 192 194 >>> w.render('email', 'foo@example.com') 193 u'<input type="file" class="fun" value="foo@example.com"name="email" />'195 u'<input type="file" class="fun" name="email" />' 194 196 195 197 >>> w.render('email', 'ŠĐĆŽćžšđ', attrs={'class': 'fun'}) 196 u'<input type="file" class="fun" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111"name="email" />'198 u'<input type="file" class="fun" name="email" />' 197 199 198 200 # Textarea Widget ############################################################# 199 201 … … 1517 1519 ... 1518 1520 ValidationError: [u'Ensure this value has at most 15 characters.'] 1519 1521 1522 # FileField ################################################################## 1523 1524 >>> f = FileField() 1525 >>> f.clean('') 1526 Traceback (most recent call last): 1527 ... 1528 ValidationError: [u'This field is required.'] 1529 1530 >>> f.clean(None) 1531 Traceback (most recent call last): 1532 ... 1533 ValidationError: [u'This field is required.'] 1534 1535 >>> f.clean({}) 1536 Traceback (most recent call last): 1537 ... 1538 ValidationError: [u'No file was submitted.'] 1539 1540 >>> f.clean('some content that is not a file') 1541 Traceback (most recent call last): 1542 ... 1543 ValidationError: [u'No file was submitted. Check the encoding type on the form.'] 1544 1545 >>> f.clean({'filename': 'name', 'content':None}) 1546 Traceback (most recent call last): 1547 ... 1548 ValidationError: [u'The submitted file is empty.'] 1549 1550 >>> f.clean({'filename': 'name', 'content':''}) 1551 Traceback (most recent call last): 1552 ... 1553 ValidationError: [u'The submitted file is empty.'] 1554 1555 >>> type(f.clean({'filename': 'name', 'content':'Some File Content'})) 1556 <class 'django.newforms.fields.UploadedFile'> 1557 1520 1558 # URLField ################################################################## 1521 1559 1522 1560 >>> f = URLField() … … 2558 2596 the next. 2559 2597 >>> class MyForm(Form): 2560 2598 ... 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) 2562 2600 ... for field in field_list: 2563 2601 ... self.fields[field[0]] = field[1] 2564 2602 >>> field_list = [('field1', CharField()), ('field2', CharField())] … … 2576 2614 ... default_field_1 = CharField() 2577 2615 ... default_field_2 = CharField() 2578 2616 ... 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) 2580 2618 ... for field in field_list: 2581 2619 ... self.fields[field[0]] = field[1] 2582 2620 >>> field_list = [('field1', CharField()), ('field2', CharField())] … … 3231 3269 <option value="3" selected="selected">No</option> 3232 3270 </select> 3233 3271 3272 # Forms with FileFields ################################################ 3273 3274 FileFields are a special case because they take their data from the request.FILES, 3275 not 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() 3299 True 3300 3234 3301 # Basic form processing in a view ############################################# 3235 3302 3236 3303 >>> from django.template import Template, Context