Changeset 5819
- Timestamp:
- 08/06/07 08:58:56 (1 year ago)
- Files:
-
- django/trunk/django/db/models/fields/__init__.py (modified) (4 diffs)
- django/trunk/django/db/models/fields/related.py (modified) (1 diff)
- django/trunk/django/newforms/extras/widgets.py (modified) (1 diff)
- django/trunk/django/newforms/fields.py (modified) (3 diffs)
- django/trunk/django/newforms/forms.py (modified) (4 diffs)
- django/trunk/django/newforms/models.py (modified) (2 diffs)
- django/trunk/django/newforms/widgets.py (modified) (6 diffs)
- django/trunk/docs/newforms.txt (modified) (3 diffs)
- django/trunk/tests/regressiontests/forms/tests.py (modified) (7 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
django/trunk/django/db/models/fields/__init__.py
r5803 r5819 381 381 choices = property(_get_choices) 382 382 383 def save_form_data(self, instance, data): 384 setattr(instance, self.name, data) 385 383 386 def formfield(self, form_class=forms.CharField, **kwargs): 384 387 "Returns a django.newforms.Field instance for this database Field." … … 696 699 self.upload_to = upload_to 697 700 Field.__init__(self, verbose_name, name, **kwargs) 701 702 def get_db_prep_save(self, value): 703 "Returns field's value prepared for saving into a database." 704 # Need to convert UploadedFile objects provided via a form to unicode for database insertion 705 if value is None: 706 return None 707 return unicode(value) 698 708 699 709 def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False, follow=True): … … 773 783 return os.path.normpath(f) 774 784 785 def save_form_data(self, instance, data): 786 if data: 787 getattr(instance, "save_%s_file" % self.name)(os.path.join(self.upload_to, data.filename), data.content, save=False) 788 789 def formfield(self, **kwargs): 790 defaults = {'form_class': forms.FileField} 791 # If a file has been provided previously, then the form doesn't require 792 # that a new file is provided this time. 793 if 'initial' in kwargs: 794 defaults['required'] = False 795 defaults.update(kwargs) 796 return super(FileField, self).formfield(**defaults) 797 775 798 class FilePathField(Field): 776 799 def __init__(self, verbose_name=None, name=None, path='', match=None, recursive=False, **kwargs): … … 820 843 setattr(new_object, self.height_field, getattr(original_object, self.height_field)) 821 844 new_object.save() 845 846 def formfield(self, **kwargs): 847 defaults = {'form_class': forms.ImageField} 848 return super(ImageField, self).formfield(**defaults) 822 849 823 850 class IntegerField(Field): django/trunk/django/db/models/fields/related.py
r5725 r5819 757 757 return getattr(obj, self.attname).all() 758 758 759 def save_form_data(self, instance, data): 760 setattr(instance, self.attname, data) 761 759 762 def formfield(self, **kwargs): 760 763 defaults = {'form_class': forms.ModelMultipleChoiceField, 'queryset': self.rel.to._default_manager.all()} django/trunk/django/newforms/extras/widgets.py
r5263 r5819 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: django/trunk/django/newforms/fields.py
r5803 r5819 8 8 9 9 from django.utils.translation import ugettext 10 from django.utils.encoding import smart_unicode10 from django.utils.encoding import StrAndUnicode, smart_unicode 11 11 12 12 from util import ErrorList, ValidationError 13 from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple13 from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, FileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple 14 14 15 15 try: … … 23 23 'DEFAULT_TIME_INPUT_FORMATS', 'TimeField', 24 24 'DEFAULT_DATETIME_INPUT_FORMATS', 'DateTimeField', 25 'RegexField', 'EmailField', ' URLField', 'BooleanField',25 'RegexField', 'EmailField', 'FileField', 'ImageField', 'URLField', 'BooleanField', 26 26 'ChoiceField', 'NullBooleanField', 'MultipleChoiceField', 27 27 'ComboField', 'MultiValueField', 'FloatField', 'DecimalField', … … 349 349 URL_VALIDATOR_USER_AGENT = 'Django (http://www.djangoproject.com/)' 350 350 351 class UploadedFile(StrAndUnicode): 352 "A wrapper for files uploaded in a FileField" 353 def __init__(self, filename, content): 354 self.filename = filename 355 self.content = content 356 357 def __unicode__(self): 358 """ 359 The unicode representation is the filename, so that the pre-database-insertion 360 logic can use UploadedFile objects 361 """ 362 return self.filename 363 364 class FileField(Field): 365 widget = FileInput 366 def __init__(self, *args, **kwargs): 367 super(FileField, self).__init__(*args, **kwargs) 368 369 def clean(self, data): 370 super(FileField, self).clean(data) 371 if not self.required and data in EMPTY_VALUES: 372 return None 373 try: 374 f = UploadedFile(data['filename'], data['content']) 375 except TypeError: 376 raise ValidationError(ugettext(u"No file was submitted. Check the encoding type on the form.")) 377 except KeyError: 378 raise ValidationError(ugettext(u"No file was submitted.")) 379 if not f.content: 380 raise ValidationError(ugettext(u"The submitted file is empty.")) 381 return f 382 383 class ImageField(FileField): 384 def clean(self, data): 385 """ 386 Checks that the file-upload field data contains a valid image (GIF, JPG, 387 PNG, possibly others -- whatever the Python Imaging Library supports). 388 """ 389 f = super(ImageField, self).clean(data) 390 if f is None: 391 return None 392 from PIL import Image 393 from cStringIO import StringIO 394 try: 395 Image.open(StringIO(f.content)) 396 except IOError: # Python Imaging Library doesn't recognize it as an image 397 raise ValidationError(ugettext(u"Upload a valid image. The file you uploaded was either not an image or a corrupted image.")) 398 return f 399 351 400 class URLField(RegexField): 352 401 def __init__(self, max_length=None, min_length=None, verify_exists=False, django/trunk/django/newforms/forms.py
r5782 r5819 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 … … 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() … … 180 181 self.cleaned_data = {} 181 182 for name, field in self.fields.items(): 182 # value_from_datadict() gets the data from the d ictionary.183 # value_from_datadict() gets the data from the data dictionaries. 183 184 # Each widget type knows how to retrieve its own data, because some 184 185 # 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)) 186 187 try: 187 188 value = field.clean(value) … … 284 285 Returns the data for this BoundField, or None if it wasn't given. 285 286 """ 286 return self.field.widget.value_from_datadict(self.form.data, self. html_name)287 return self.field.widget.value_from_datadict(self.form.data, self.form.files, self.html_name) 287 288 data = property(_data) 288 289 django/trunk/django/newforms/models.py
r5804 r5819 35 35 if fields and f.name not in fields: 36 36 continue 37 setattr(instance, f.name, cleaned_data[f.name])37 f.save_form_data(instance, cleaned_data[f.name]) 38 38 # Wrap up the saving of m2m data as a function 39 39 def save_m2m(): … … 44 44 continue 45 45 if f.name in cleaned_data: 46 setattr(instance, f.attname, cleaned_data[f.name])46 f.save_form_data(instance, cleaned_data[f.name]) 47 47 if commit: 48 48 # If we are committing, save the instance and the m2m data immediately django/trunk/django/newforms/widgets.py
r5782 r5819 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 … … 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) … … 121 121 class FileInput(Input): 122 122 input_type = 'file' 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) 123 130 124 131 class Textarea(Widget): … … 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) … … 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) … … 378 385 id_for_label = classmethod(id_for_label) 379 386 380 def value_from_datadict(self, data, name):381 return [widget.value_from_datadict(data, name + '_%s' % i) for i, widget in enumerate(self.widgets)]387 def value_from_datadict(self, data, files, name): 388 return [widget.value_from_datadict(data, files, name + '_%s' % i) for i, widget in enumerate(self.widgets)] 382 389 383 390 def format_output(self, rendered_widgets): django/trunk/docs/newforms.txt
r5813 r5819 711 711 </form> 712 712 713 Binding uploaded files to a form 714 -------------------------------- 715 716 Dealing with forms that have ``FileField`` and ``ImageField`` fields 717 is a little more complicated than a normal form. 718 719 Firstly, in order to upload files, you'll need to make sure that your 720 ``<form>`` element correctly defines the ``enctype`` as 721 ``"multipart/form-data"``:: 722 723 <form enctype="multipart/form-data" method="post" action="/foo/"> 724 725 Secondly, when you use the form, you need to bind the file data. File 726 data is handled separately to normal form data, so when your form 727 contains a ``FileField`` and ``ImageField``, you will need to specify 728 a second argument when you bind your form. So if we extend our 729 ContactForm to include an ``ImageField`` called ``mugshot``, we 730 need to bind the file data containing the mugshot image:: 731 732 # Bound form with an image field 733 >>> data = {'subject': 'hello', 734 ... 'message': 'Hi there', 735 ... 'sender': 'foo@example.com', 736 ... 'cc_myself': True} 737 >>> file_data = {'mugshot': {'filename':'face.jpg' 738 ... 'content': <file data>}} 739 >>> f = ContactFormWithMugshot(data, file_data) 740 741 In practice, you will usually specify ``request.FILES`` as the source 742 of file data (just like you use ``request.POST`` as the source of 743 form data):: 744 745 # Bound form with an image field, data from the request 746 >>> f = ContactFormWithMugshot(request.POST, request.FILES) 747 748 Constructing an unbound form is the same as always -- just omit both 749 form data *and* file data: 750 751 # Unbound form with a image field 752 >>> f = ContactFormWithMugshot() 753 713 754 Subclassing forms 714 755 ----------------- … … 1100 1141 given length. 1101 1142 1143 ``FileField`` 1144 ~~~~~~~~~~~~~ 1145 1146 * Default widget: ``FileInput`` 1147 * Empty value: ``None`` 1148 * Normalizes to: An ``UploadedFile`` object that wraps the file content 1149 and file name into a single object. 1150 * Validates that non-empty file data has been bound to the form. 1151 1152 An ``UploadedFile`` object has two attributes: 1153 1154 ====================== ===================================================== 1155 Argument Description 1156 ====================== ===================================================== 1157 ``filename`` The name of the file, provided by the uploading 1158 client. 1159 ``content`` The array of bytes comprising the file content. 1160 ====================== ===================================================== 1161 1162 The string representation of an ``UploadedFile`` is the same as the filename 1163 attribute. 1164 1165 When you use a ``FileField`` on a form, you must also remember to 1166 `bind the file data to the form`_. 1167 1168 .. _`bind the file data to the form`: `Binding uploaded files to a form`_ 1169 1170 ``ImageField`` 1171 ~~~~~~~~~~~~~~ 1172 1173 * Default widget: ``FileInput`` 1174 * Empty value: ``None`` 1175 * Normalizes to: An ``UploadedFile`` object that wraps the file content 1176 and file name into a single object. 1177 * Validates that file data has been bound to the form, and that the 1178 file is of an image format understood by PIL. 1179 1180 Using an ImageField requires that the `Python Imaging Library`_ is installed. 1181 1182 When you use a ``FileField`` on a form, you must also remember to 1183 `bind the file data to the form`_. 1184 1185 .. _Python Imaging Library: http://www.pythonware.com/products/pil/ 1186 1102 1187 ``IntegerField`` 1103 1188 ~~~~~~~~~~~~~~~~ … … 1379 1464 ``DecimalField`` ``DecimalField`` 1380 1465 ``EmailField`` ``EmailField`` 1381 ``FileField`` `` CharField``1466 ``FileField`` ``FileField`` 1382 1467 ``FilePathField`` ``CharField`` 1383 1468 ``FloatField`` ``FloatField`` 1384 1469 ``ForeignKey`` ``ModelChoiceField`` (see below) 1385 ``ImageField`` `` CharField``1470 ``ImageField`` ``ImageField`` 1386 1471 ``IntegerField`` ``IntegerField`` 1387 1472 ``IPAddressField`` ``CharField`` django/trunk/tests/regressiontests/forms/tests.py
r5782 r5819 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', '') … … 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: … … 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 ############################################################# … … 1532 1534 ... 1533 1535 ValidationError: [u'Ensure this value has at most 15 characters (it has 20).'] 1536 1537 # FileField ################################################################## 1538 1539 >>> f = FileField() 1540 >>> f.clean('') 1541 Traceback (most recent call last): 1542 ... 1543 ValidationError: [u'This field is required.'] 1544 1545 >>> f.clean(None) 1546 Traceback (most recent call last): 1547 ... 1548 ValidationError: [u'This field is required.'] 1549 1550 >>> f.clean({}) 1551 Traceback (most recent call last): 1552 ... 1553 ValidationError: [u'No file was submitted.'] 1554 1555 >>> f.clean('some content that is not a file') 1556 Traceback (most recent call last): 1557 ... 1558 ValidationError: [u'No file was submitted. Check the encoding type on the form.'] 1559 1560 >>> f.clean({'filename': 'name', 'content':None}) 1561 Traceback (most recent call last): 1562 ... 1563 ValidationError: [u'The submitted file is empty.'] 1564 1565 >>> f.clean({'filename': 'name', 'content':''}) 1566 Traceback (most recent call last): 1567 ... 1568 ValidationError: [u'The submitted file is empty.'] 1569 1570 >>> type(f.clean({'filename': 'name', 'content':'Some File Content'})) 1571 <class 'django.newforms.fields.UploadedFile'> 1534 1572 1535 1573 # URLField ################################################################## … … 2574 2612 >>> class MyForm(Form): 2575 2613 ... def __init__(self, data=None, auto_id=False, field_list=[]): 2576 ... Form.__init__(self, data, auto_id )2614 ... Form.__init__(self, data, auto_id=auto_id) 2577 2615 ... for field in field_list: 2578 2616 ... self.fields[field[0]] = field[1] … … 2592 2630 ... default_field_2 = CharField() 2593 2631 ... def __init__(self, data=None, auto_id=False, field_list=[]): 2594 ... Form.__init__(self, data, auto_id )2632 ... Form.__init__(self, data, auto_id=auto_id) 2595 2633 ... for field in field_list: 2596 2634 ... self.fields[field[0]] = field[1] … … 3247 3285 </select> 3248 3286 3287 # Forms with FileFields ################################################ 3288 3289 FileFields are a special case because they take their data from the request.FILES, 3290 not request.POST. 3291 3292 >>> class FileForm(Form): 3293 ... file1 = FileField() 3294 >>> f = FileForm(auto_id=False) 3295 >>> print f 3296 <tr><th>File1:</th><td><input type="file" name="file1" /></td></tr> 3297 3298 >>> f = FileForm(data={}, files={}, auto_id=False) 3299 >>> print f 3300 <tr><th>File1:</th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="file" name="file1" /></td></tr> 3301 3302 >>> f = FileForm(data={}, files={'file1': {'filename': 'name', 'content':''}}, auto_id=False) 3303 >>> print f 3304 <tr><th>File1:</th><td><ul class="errorlist"><li>The submitted file is empty.</li></ul><input type="file" name="file1" /></td></tr> 3305 3306 >>> f = FileForm(data={}, files={'file1': 'something that is not a file'}, auto_id=False) 3307 >>> print f 3308 <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> 3309 3310 >>> f = FileForm(data={}, files={'file1': {'filename': 'name', 'content':'some content'}}, auto_id=False) 3311 >>> print f 3312 <tr><th>File1:</th><td><input type="file" name="file1" /></td></tr> 3313 >>> f.is_valid() 3314 True 3315 3249 3316 # Basic form processing in a view ############################################# 3250 3317
