Ticket #7048: 7048_r13926.diff
File 7048_r13926.diff, 26.8 KB (added by , 14 years ago) |
---|
-
django/contrib/admin/media/css/widgets.css
diff --git a/django/contrib/admin/media/css/widgets.css b/django/contrib/admin/media/css/widgets.css index 620e082..a761a7b 100644
a b p.file-upload { 198 198 margin-left: 5px; 199 199 } 200 200 201 span.clearable-file-input label { 202 color: #333; 203 font-size: 11px; 204 display: inline; 205 float: none; 206 } 207 201 208 /* CALENDARS & CLOCKS */ 202 209 203 210 .calendarbox, .clockbox { -
django/contrib/admin/widgets.py
diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py index 2c7ac5c..eb7f217 100644
a b class AdminRadioFieldRenderer(RadioFieldRenderer): 85 85 class AdminRadioSelect(forms.RadioSelect): 86 86 renderer = AdminRadioFieldRenderer 87 87 88 class AdminFileWidget(forms.FileInput): 89 """ 90 A FileField Widget that shows its current value if it has one. 91 """ 92 def __init__(self, attrs={}): 93 super(AdminFileWidget, self).__init__(attrs) 88 class AdminFileWidget(forms.ClearableFileInput): 89 template_with_initial = (u'<p class="file-upload">%s</p>' 90 % forms.ClearableFileInput.template_with_initial) 91 template_with_clear = (u'<span class="clearable-file-input">%s</span>' 92 % forms.ClearableFileInput.template_with_clear) 94 93 95 def render(self, name, value, attrs=None):96 output = []97 if value and hasattr(value, "url"):98 output.append('%s <a target="_blank" href="%s">%s</a> <br />%s ' % \99 (_('Currently:'), value.url, value, _('Change:')))100 output.append(super(AdminFileWidget, self).render(name, value, attrs))101 return mark_safe(u''.join(output))102 94 103 95 class ForeignKeyRawIdWidget(forms.TextInput): 104 96 """ -
django/db/models/fields/files.py
diff --git a/django/db/models/fields/files.py b/django/db/models/fields/files.py index 6dfeddb..9ee523b 100644
a b class FileField(Field): 282 282 return os.path.join(self.get_directory_name(), self.get_filename(filename)) 283 283 284 284 def save_form_data(self, instance, data): 285 if data: 285 # Important: None means "no change", other false value means "clear" 286 # This subtle distinction (rather than a more explicit marker) is 287 # needed because we need to consume values that are also sane for a 288 # regular (non Model-) Form to find in its cleaned_data dictionary. 289 if data is not None: 290 # This value will be converted to unicode and stored in the 291 # database, so leaving False as-is is not acceptable. 292 if not data: 293 data = '' 286 294 setattr(instance, self.name, data) 287 295 288 296 def formfield(self, **kwargs): -
django/forms/fields.py
diff --git a/django/forms/fields.py b/django/forms/fields.py index de14a5c..03455e0 100644
a b from django.core.validators import EMPTY_VALUES 27 27 28 28 from util import ErrorList 29 29 from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, \ 30 FileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, \ 31 DateInput, DateTimeInput, TimeInput, SplitDateTimeWidget, SplitHiddenDateTimeWidget 30 ClearableFileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, \ 31 DateInput, DateTimeInput, TimeInput, SplitDateTimeWidget, SplitHiddenDateTimeWidget, \ 32 FILE_INPUT_CONTRADICTION 32 33 33 34 __all__ = ( 34 35 'Field', 'CharField', 'IntegerField', … … class Field(object): 108 109 if self.localize: 109 110 widget.is_localized = True 110 111 112 # Let the widget know whether it should display as required. 113 widget.is_required = self.required 114 111 115 # Hook into self.widget_attrs() for any Field-specific HTML attributes. 112 116 extra_attrs = self.widget_attrs(widget) 113 117 if extra_attrs: … … class Field(object): 167 171 self.run_validators(value) 168 172 return value 169 173 174 def bound_data(self, data, initial): 175 """ 176 Return the value that should be shown for this field on render of a 177 bound form, given the submitted POST data for the field and the initial 178 data, if any. 179 180 For most fields, this will simply be data; FileFields need to handle it 181 a bit differently. 182 """ 183 return data 184 170 185 def widget_attrs(self, widget): 171 186 """ 172 187 Given a Widget instance (*not* a Widget class), returns a dictionary of … … class EmailField(CharField): 434 449 default_validators = [validators.validate_email] 435 450 436 451 class FileField(Field): 437 widget = FileInput452 widget = ClearableFileInput 438 453 default_error_messages = { 439 454 'invalid': _(u"No file was submitted. Check the encoding type on the form."), 440 455 'missing': _(u"No file was submitted."), 441 456 'empty': _(u"The submitted file is empty."), 442 457 'max_length': _(u'Ensure this filename has at most %(max)d characters (it has %(length)d).'), 458 'contradiction': _(u'Please either submit a file or check the clear checkbox, not both.') 443 459 } 444 460 445 461 def __init__(self, *args, **kwargs): … … class FileField(Field): 468 484 return data 469 485 470 486 def clean(self, data, initial=None): 487 # If the widget got contradictory inputs, we raise a validation error 488 if data is FILE_INPUT_CONTRADICTION: 489 raise ValidationError(self.error_messages['contradiction']) 490 # False means the field value should be cleared; further validation is 491 # not needed. 492 if data is False: 493 if not self.required: 494 return False 495 # If the field is required, clearing is not possible (the widget 496 # shouldn't return False data in that case anyway). False is not 497 # in validators.EMPTY_VALUES; if a False value makes it this far 498 # it should be validated from here on out as None (so it will be 499 # caught by the required check). 500 data = None 471 501 if not data and initial: 472 502 return initial 473 503 return super(FileField, self).clean(data) 474 504 505 def bound_data(self, data, initial): 506 if data in (None, FILE_INPUT_CONTRADICTION): 507 return initial 508 return data 509 475 510 class ImageField(FileField): 476 511 default_error_messages = { 477 512 'invalid_image': _(u"Upload a valid image. The file you uploaded was either not an image or a corrupted image."), -
django/forms/forms.py
diff --git a/django/forms/forms.py b/django/forms/forms.py index 13ef1a7..0377de4 100644
a b class BoundField(StrAndUnicode): 437 437 if callable(data): 438 438 data = data() 439 439 else: 440 if isinstance(self.field, FileField) and self.data is None: 441 data = self.form.initial.get(self.name, self.field.initial) 442 else: 443 data = self.data 440 data = self.field.bound_data( 441 self.data, self.form.initial.get(self.name, self.field.initial)) 444 442 data = self.field.prepare_value(data) 445 443 446 444 if not only_initial: -
django/forms/widgets.py
diff --git a/django/forms/widgets.py b/django/forms/widgets.py index 2e16c35..cb12586 100644
a b from itertools import chain 7 7 from django.conf import settings 8 8 from django.utils.datastructures import MultiValueDict, MergeDict 9 9 from django.utils.html import escape, conditional_escape 10 from django.utils.translation import ugettext 10 from django.utils.translation import ugettext, ugettext_lazy 11 11 from django.utils.encoding import StrAndUnicode, force_unicode 12 12 from django.utils.safestring import mark_safe 13 13 from django.utils import datetime_safe, formats … … from urlparse import urljoin 18 18 19 19 __all__ = ( 20 20 'Media', 'MediaDefiningClass', 'Widget', 'TextInput', 'PasswordInput', 21 'HiddenInput', 'MultipleHiddenInput', 21 'HiddenInput', 'MultipleHiddenInput', 'ClearableFileInput', 22 22 'FileInput', 'DateInput', 'DateTimeInput', 'TimeInput', 'Textarea', 'CheckboxInput', 23 23 'Select', 'NullBooleanSelect', 'SelectMultiple', 'RadioSelect', 24 24 'CheckboxSelectMultiple', 'MultiWidget', … … class Widget(object): 134 134 is_hidden = False # Determines whether this corresponds to an <input type="hidden">. 135 135 needs_multipart_form = False # Determines does this widget need multipart-encrypted form 136 136 is_localized = False 137 is_required = False 137 138 138 139 def __init__(self, attrs=None): 139 140 if attrs is not None: … … class FileInput(Input): 286 287 return False 287 288 return True 288 289 290 FILE_INPUT_CONTRADICTION = object() 291 292 class ClearableFileInput(FileInput): 293 initial_text = ugettext_lazy('Currently') 294 input_text = ugettext_lazy('Change') 295 clear_checkbox_label = ugettext_lazy('Clear') 296 297 template_with_initial = u'%(initial_text)s: %(initial)s %(clear_template)s<br />%(input_text)s: %(input)s' 298 299 template_with_clear = u'%(clear)s <label for="%(clear_checkbox_id)s">%(clear_checkbox_label)s</label>' 300 301 def clear_checkbox_name(self, name): 302 """ 303 Given the name of the file input, return the name of the clear checkbox 304 input. 305 """ 306 return name + '-clear' 307 308 def clear_checkbox_id(self, name): 309 """ 310 Given the name of the clear checkbox input, return the HTML id for it. 311 """ 312 return name + '_id' 313 314 def render(self, name, value, attrs=None): 315 substitutions = { 316 'initial_text': self.initial_text, 317 'input_text': self.input_text, 318 'clear_template': '', 319 'clear_checkbox_label': self.clear_checkbox_label, 320 } 321 template = u'%(input)s' 322 substitutions['input'] = super(ClearableFileInput, self).render(name, value, attrs) 323 324 if value and hasattr(value, "url"): 325 template = self.template_with_initial 326 substitutions['initial'] = (u'<a target="_blank" href="%s">%s</a>' 327 % (value.url, value)) 328 if not self.is_required: 329 checkbox_name = self.clear_checkbox_name(name) 330 checkbox_id = self.clear_checkbox_id(checkbox_name) 331 substitutions['clear_checkbox_name'] = checkbox_name 332 substitutions['clear_checkbox_id'] = checkbox_id 333 substitutions['clear'] = CheckboxInput().render(checkbox_name, False, attrs={'id': checkbox_id}) 334 substitutions['clear_template'] = self.template_with_clear % substitutions 335 336 return mark_safe(template % substitutions) 337 338 def value_from_datadict(self, data, files, name): 339 upload = super(ClearableFileInput, self).value_from_datadict(data, files, name) 340 if not self.is_required and CheckboxInput().value_from_datadict( 341 data, files, self.clear_checkbox_name(name)): 342 if upload: 343 # If the user contradicts themselves (uploads a new file AND 344 # checks the "clear" checkbox), we return a unique marker 345 # object that FileField will turn into a ValidationError. 346 return FILE_INPUT_CONTRADICTION 347 # False signals to clear any existing value, as opposed to just None 348 return False 349 return upload 350 289 351 class Textarea(Widget): 290 352 def __init__(self, attrs=None): 291 353 # The 'rows' and 'cols' attributes are required for HTML correctness. -
docs/ref/forms/fields.txt
diff --git a/docs/ref/forms/fields.txt b/docs/ref/forms/fields.txt index 0dd9095..d2b1c30 100644
a b given length. 507 507 508 508 .. class:: FileField(**kwargs) 509 509 510 * Default widget: `` FileInput``510 * Default widget: ``ClearableFileInput`` 511 511 * Empty value: ``None`` 512 512 * Normalizes to: An ``UploadedFile`` object that wraps the file content 513 513 and file name into a single object. … … These control the range of values permitted in the field. 573 573 574 574 .. class:: ImageField(**kwargs) 575 575 576 * Default widget: `` FileInput``576 * Default widget: ``ClearableFileInput`` 577 577 * Empty value: ``None`` 578 578 * Normalizes to: An ``UploadedFile`` object that wraps the file content 579 579 and file name into a single object. -
docs/ref/forms/widgets.txt
diff --git a/docs/ref/forms/widgets.txt b/docs/ref/forms/widgets.txt index 05215d4..c01a736 100644
a b commonly used groups of widgets: 46 46 47 47 File upload input: ``<input type='file' ...>`` 48 48 49 .. class:: ClearableFileInput 50 51 .. versionadded:: 1.3 52 53 File upload input: ``<input type='file' ...>``, with an additional checkbox 54 input to clear the field's value, if the field is not required and has 55 initial data. 56 49 57 .. class:: DateInput 50 58 51 59 .. versionadded:: 1.1 -
docs/releases/1.3.txt
diff --git a/docs/releases/1.3.txt b/docs/releases/1.3.txt index 51de6fd..aaee5d8 100644
a b custom widget to your form that sets the ``render_value`` argument:: 42 42 username = forms.CharField(max_length=100) 43 43 password = forms.PasswordField(widget=forms.PasswordInput(render_value=True)) 44 44 45 Clearable default widget for FileField 46 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 47 48 Django 1.3 now includes a ``ClearableFileInput`` form widget in addition to 49 ``FileInput``. ``ClearableFileInput`` renders with a checkbox to clear the 50 field's value (if the field has a value and is not required); ``FileInput`` 51 provided no means for clearing an existing file from a ``FileField``. 52 53 ``ClearableFileInput`` is now the default widget for a ``FileField``, so 54 existing forms including ``FileField`` without assigning a custom widget will 55 need to account for the possible extra checkbox in the rendered form output. 56 57 To return to the previous rendering (without the ability to clear the 58 ``FileField``), use the ``FileInput`` widget in place of 59 ``ClearableFileInput``. For instance, in a ``ModelForm`` for a hypothetical 60 ``Document`` model with a ``FileField`` named ``document``:: 61 62 from django import forms 63 from myapp.models import Document 64 65 class DocumentForm(forms.ModelForm): 66 class Meta: 67 model = Document 68 widgets = {'document': forms.FileInput} 69 45 70 .. _deprecated-features-1.3: 46 71 47 72 Features deprecated in 1.3 -
tests/regressiontests/admin_widgets/models.py
diff --git a/tests/regressiontests/admin_widgets/models.py b/tests/regressiontests/admin_widgets/models.py index 59d625b..20c0df6 100644
a b HTML escaped. 116 116 117 117 >>> w = AdminFileWidget() 118 118 >>> print conditional_escape(w.render('test', album.cover_art)) 119 Currently: <a target="_blank" href="%(STORAGE_URL)salbums/hybrid_theory.jpg">albums\hybrid_theory.jpg</a> <br />Change: <input type="file" name="test" />119 <p class="file-upload">Currently: <a target="_blank" href="%(STORAGE_URL)salbums/hybrid_theory.jpg">albums\hybrid_theory.jpg</a> <span class="clearable-file-input"><input type="checkbox" name="test-clear" id="test-clear_id" /> <label for="test-clear_id">Clear</label></span><br />Change: <input type="file" name="test" /></p> 120 120 >>> print conditional_escape(w.render('test', SimpleUploadedFile('test', 'content'))) 121 121 <input type="file" name="test" /> 122 122 -
tests/regressiontests/forms/fields.py
diff --git a/tests/regressiontests/forms/fields.py b/tests/regressiontests/forms/fields.py index e4f2c26..d29e2e8 100644
a b class FieldsTests(TestCase): 57 57 except error, e: 58 58 self.assertEqual(message, str(e)) 59 59 60 def test_field_sets_widget_is_required(self): 61 self.assertEqual(Field(required=True).widget.is_required, True) 62 self.assertEqual(Field(required=False).widget.is_required, False) 63 60 64 # CharField ################################################################### 61 65 62 66 def test_charfield_0(self): -
tests/regressiontests/forms/tests.py
diff --git a/tests/regressiontests/forms/tests.py b/tests/regressiontests/forms/tests.py index 7a91cb7..1871d4f 100644
a b from media import media_tests 39 39 40 40 from fields import FieldsTests 41 41 from validators import TestFieldWithValidators 42 from widgets import WidgetTests 42 from widgets import WidgetTests, ClearableFileInputTests 43 43 44 44 from input_formats import * 45 45 -
tests/regressiontests/forms/widgets.py
diff --git a/tests/regressiontests/forms/widgets.py b/tests/regressiontests/forms/widgets.py index 59b33d5..10483a7 100644
a b u'<input type="hidden" name="date_0" value="17.09.2007" /><input type="hidden" n 1269 1269 from django.utils import copycompat as copy 1270 1270 from unittest import TestCase 1271 1271 from django import forms 1272 from django.core.files.uploadedfile import SimpleUploadedFile 1272 1273 1273 1274 1274 1275 class SelectAndTextWidget(forms.MultiWidget): … … class WidgetTests(TestCase): 1323 1324 self.assertFalse(form.is_valid()) 1324 1325 form = SplitDateRequiredForm({'field': ['', '']}) 1325 1326 self.assertFalse(form.is_valid()) 1327 1328 1329 class FakeFieldFile(object): 1330 """ 1331 Quacks like a FieldFile (has a .url and unicode representation), but 1332 doesn't require us to care about storages etc. 1333 1334 """ 1335 url = 'something' 1336 1337 def __unicode__(self): 1338 return self.url 1339 1340 class ClearableFileInputTests(TestCase): 1341 def test_clear_input_renders(self): 1342 """ 1343 A ClearableFileInput with is_required False and rendered with 1344 an initial value that is a file renders a clear checkbox. 1345 1346 """ 1347 widget = forms.ClearableFileInput() 1348 widget.is_required = False 1349 self.assertEqual(widget.render('myfile', FakeFieldFile()), 1350 u'Currently: <a target="_blank" href="something">something</a> <input type="checkbox" name="myfile-clear" id="myfile-clear_id" /> <label for="myfile-clear_id">Clear</label><br />Change: <input type="file" name="myfile" />') 1351 1352 def test_clear_input_renders_only_if_not_required(self): 1353 """ 1354 A ClearableFileInput with is_required=False does not render a clear 1355 checkbox. 1356 1357 """ 1358 widget = forms.ClearableFileInput() 1359 widget.is_required = True 1360 self.assertEqual(widget.render('myfile', FakeFieldFile()), 1361 u'Currently: <a target="_blank" href="something">something</a> <br />Change: <input type="file" name="myfile" />') 1362 1363 def test_clear_input_renders_only_if_initial(self): 1364 """ 1365 A ClearableFileInput instantiated with no initial value does not render 1366 a clear checkbox. 1367 1368 """ 1369 widget = forms.ClearableFileInput() 1370 widget.is_required = False 1371 self.assertEqual(widget.render('myfile', None), 1372 u'<input type="file" name="myfile" />') 1373 1374 def test_clear_input_checked_returns_false(self): 1375 """ 1376 ClearableFileInput.value_from_datadict returns False if the clear 1377 checkbox is checked, if not required. 1378 1379 """ 1380 widget = forms.ClearableFileInput() 1381 widget.is_required = False 1382 self.assertEqual(widget.value_from_datadict( 1383 data={'myfile-clear': True}, 1384 files={}, 1385 name='myfile'), False) 1386 1387 def test_clear_input_checked_returns_false_only_if_not_required(self): 1388 """ 1389 ClearableFileInput.value_from_datadict never returns False if the field 1390 is required. 1391 1392 """ 1393 widget = forms.ClearableFileInput() 1394 widget.is_required = True 1395 f = SimpleUploadedFile('something.txt', 'content') 1396 self.assertEqual(widget.value_from_datadict( 1397 data={'myfile-clear': True}, 1398 files={'myfile': f}, 1399 name='myfile'), f) -
tests/regressiontests/model_fields/models.py
diff --git a/tests/regressiontests/model_fields/models.py b/tests/regressiontests/model_fields/models.py index 45cd223..1dc1649 100644
a b class BooleanModel(models.Model): 67 67 string = models.CharField(max_length=10, default='abc') 68 68 69 69 ############################################################################### 70 # FileField 71 72 class Document(models.Model): 73 myfile = models.FileField(upload_to='unused') 74 75 ############################################################################### 70 76 # ImageField 71 77 72 78 # If PIL available, do these tests. -
tests/regressiontests/model_fields/tests.py
diff --git a/tests/regressiontests/model_fields/tests.py b/tests/regressiontests/model_fields/tests.py index 72a7d4d..58c32b5 100644
a b import django.test 6 6 from django import forms 7 7 from django.db import models 8 8 from django.core.exceptions import ValidationError 9 from django.db.models.fields.files import FieldFile 9 10 10 from models import Foo, Bar, Whiz, BigD, BigS, Image, BigInt, Post, NullBooleanModel, BooleanModel 11 from models import Foo, Bar, Whiz, BigD, BigS, Image, BigInt, Post, NullBooleanModel, BooleanModel, Document 11 12 12 13 # If PIL available, do these tests. 13 14 if Image: … … class TypeCoercionTests(django.test.TestCase): 311 312 def test_lookup_integer_in_textfield(self): 312 313 self.assertEquals(Post.objects.filter(body=24).count(), 0) 313 314 315 class FileFieldTests(unittest.TestCase): 316 def test_clearable(self): 317 """ 318 Test that FileField.save_form_data will clear its instance attribute 319 value if passed False. 320 321 """ 322 d = Document(myfile='something.txt') 323 self.assertEqual(d.myfile, 'something.txt') 324 field = d._meta.get_field('myfile') 325 field.save_form_data(d, False) 326 self.assertEqual(d.myfile, '') 327 328 def test_unchanged(self): 329 """ 330 Test that FileField.save_form_data considers None to mean "no change" 331 rather than "clear". 332 333 """ 334 d = Document(myfile='something.txt') 335 self.assertEqual(d.myfile, 'something.txt') 336 field = d._meta.get_field('myfile') 337 field.save_form_data(d, None) 338 self.assertEqual(d.myfile, 'something.txt') 339 340 def test_changed(self): 341 """ 342 Test that FileField.save_form_data, if passed a truthy value, updates 343 its instance attribute. 344 345 """ 346 d = Document(myfile='something.txt') 347 self.assertEqual(d.myfile, 'something.txt') 348 field = d._meta.get_field('myfile') 349 field.save_form_data(d, 'else.txt') 350 self.assertEqual(d.myfile, 'else.txt') -
tests/regressiontests/model_forms_regress/models.py
diff --git a/tests/regressiontests/model_forms_regress/models.py b/tests/regressiontests/model_forms_regress/models.py index 4f9811a..75b8a40 100644
a b class Author1(models.Model): 57 57 58 58 class Homepage(models.Model): 59 59 url = models.URLField(verify_exists=False) 60 61 class Document(models.Model): 62 myfile = models.FileField(upload_to='unused', blank=True) -
tests/regressiontests/model_forms_regress/tests.py
diff --git a/tests/regressiontests/model_forms_regress/tests.py b/tests/regressiontests/model_forms_regress/tests.py index baf769c..397651a 100644
a b 1 import unittest 1 2 from datetime import date 2 3 3 4 from django import db … … from django import forms 5 6 from django.forms.models import modelform_factory, ModelChoiceField 6 7 from django.conf import settings 7 8 from django.test import TestCase 8 from django.core.exceptions import FieldError 9 from django.core.exceptions import FieldError, ValidationError 10 from django.core.files.uploadedfile import SimpleUploadedFile 9 11 10 12 from models import Person, RealPerson, Triple, FilePathModel, Article, \ 11 Publication, CustomFF, Author, Author1, Homepage 13 Publication, CustomFF, Author, Author1, Homepage, Document 12 14 13 15 14 16 class ModelMultipleChoiceFieldTests(TestCase): … … class InvalidFieldAndFactory(TestCase): 333 335 self.assertRaises(FieldError, modelform_factory, 334 336 Person, fields=['no-field', 'name']) 335 337 338 339 class DocumentForm(forms.ModelForm): 340 class Meta: 341 model = Document 342 343 class FileFieldTests(unittest.TestCase): 344 def test_clean_false(self): 345 """ 346 If the ``clean`` method on a non-required FileField receives False as 347 the data (meaning clear the field value), it returns False, regardless 348 of the value of ``initial``. 349 350 """ 351 f = forms.FileField(required=False) 352 self.assertEqual(f.clean(False), False) 353 self.assertEqual(f.clean(False, 'initial'), False) 354 355 def test_clean_false_required(self): 356 """ 357 If the ``clean`` method on a required FileField receives False as the 358 data, it has the same effect as None: initial is returned if non-empty, 359 otherwise the validation catches the lack of a required value. 360 361 """ 362 f = forms.FileField(required=True) 363 self.assertEqual(f.clean(False, 'initial'), 'initial') 364 self.assertRaises(ValidationError, f.clean, False) 365 366 def test_full_clear(self): 367 """ 368 Integration happy-path test that a model FileField can actually be set 369 and cleared via a ModelForm. 370 371 """ 372 form = DocumentForm() 373 self.assert_('name="myfile"' in unicode(form)) 374 self.assert_('myfile-clear' not in unicode(form)) 375 form = DocumentForm(files={'myfile': SimpleUploadedFile('something.txt', 'content')}) 376 self.assert_(form.is_valid()) 377 doc = form.save(commit=False) 378 self.assertEqual(doc.myfile.name, 'something.txt') 379 form = DocumentForm(instance=doc) 380 self.assert_('myfile-clear' in unicode(form)) 381 form = DocumentForm(instance=doc, data={'myfile-clear': 'true'}) 382 doc = form.save(commit=False) 383 self.assertEqual(bool(doc.myfile), False) 384 385 def test_clear_and_file_contradiction(self): 386 """ 387 If the user submits a new file upload AND checks the clear checkbox, 388 they get a validation error, and the bound redisplay of the form still 389 includes the current file and the clear checkbox. 390 391 """ 392 form = DocumentForm(files={'myfile': SimpleUploadedFile('something.txt', 'content')}) 393 self.assert_(form.is_valid()) 394 doc = form.save(commit=False) 395 form = DocumentForm(instance=doc, 396 files={'myfile': SimpleUploadedFile('something.txt', 'content')}, 397 data={'myfile-clear': 'true'}) 398 self.assert_(not form.is_valid()) 399 self.assertEqual(form.errors['myfile'], 400 [u'Please either submit a file or check the clear checkbox, not both.']) 401 rendered = unicode(form) 402 self.assert_('something.txt' in rendered) 403 self.assert_('myfile-clear' in rendered)