Ticket #23: form-warnings.2.diff
File form-warnings.2.diff, 12.2 KB (added by , 16 years ago) |
---|
-
django/forms/forms.py
diff --git a/django/forms/forms.py b/django/forms/forms.py index f5bea10..1de8c03 100644
a b Form classes 3 3 """ 4 4 5 5 from copy import deepcopy 6 from random import randrange 6 7 7 8 from django.utils.datastructures import SortedDict 8 9 from django.utils.html import conditional_escape … … from django.utils.safestring import mark_safe 11 12 12 13 from fields import Field, FileField 13 14 from widgets import Media, media_property, TextInput, Textarea 14 from util import flatatt, ErrorDict, ErrorList, ValidationError 15 from util import flatatt, ErrorDict, ErrorList, ValidationError, WarningDict, WarningList 15 16 16 17 __all__ = ('BaseForm', 'Form') 17 18 … … class BaseForm(StrAndUnicode): 69 70 # information. Any improvements to the form API should be made to *this* 70 71 # class, not to the Form class. 71 72 def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, 72 initial=None, error_class=ErrorList, label_suffix=':',73 initial=None, error_class=ErrorList, warning_class=WarningList, label_suffix=':', 73 74 empty_permitted=False): 74 75 self.is_bound = data is not None or files is not None 75 76 self.data = data or {} … … class BaseForm(StrAndUnicode): 78 79 self.prefix = prefix 79 80 self.initial = initial or {} 80 81 self.error_class = error_class 82 self.warning_class = warning_class 81 83 self.label_suffix = label_suffix 82 84 self.empty_permitted = empty_permitted 83 85 self._errors = None # Stores the errors after clean() has been called. 86 self._warnings = None 84 87 self._changed_data = None 85 88 86 89 # The base_fields class attribute is the *class-wide* definition of … … class BaseForm(StrAndUnicode): 111 114 self.full_clean() 112 115 return self._errors 113 116 errors = property(_get_errors) 117 118 def _get_warnings(self): 119 if self._warnings is None: 120 self.full_clean() 121 return self._warnings 122 warnings = property(_get_warnings) 114 123 115 def is_valid(self ):124 def is_valid(self, require_no_warnings=False): 116 125 """ 117 126 Returns True if the form has no errors. Otherwise, False. If errors are 118 127 being ignored, returns False. 119 128 """ 129 if require_no_warnings: 130 return self.is_bound and not bool(self.errors) and not bool(self.warnings) 120 131 return self.is_bound and not bool(self.errors) 121 132 122 133 def add_prefix(self, field_name): … … class BaseForm(StrAndUnicode): 133 144 Add a 'initial' prefix for checking dynamic initial values 134 145 """ 135 146 return u'initial-%s' % self.add_prefix(field_name) 147 148 def add_warning(self, field_name, message): 149 if field_name in self._warnings: 150 self._warnings[field_name].append(message) 151 else: 152 self._warnings[field_name] = WarningList([message]) 136 153 137 154 def _html_output(self, normal_row, error_row, row_ender, help_text_html, errors_on_separate_row): 138 155 "Helper function for outputting HTML. Used by as_table(), as_ul(), as_p()." 139 156 top_errors = self.non_field_errors() # Errors that should be displayed above all fields. 157 top_warnings = self.non_field_warnings() 140 158 output, hidden_fields = [], [] 141 159 for name, field in self.fields.items(): 142 160 bf = BoundField(self, field, name) 143 161 bf_errors = self.error_class([conditional_escape(error) for error in bf.errors]) # Escape and cache in local variable. 162 bf_warnings = self.warning_class([conditional_escape(warning) for warning in bf.warnings]) 144 163 if bf.is_hidden: 145 164 if bf_errors: 146 165 top_errors.extend([u'(Hidden field %s) %s' % (name, force_unicode(e)) for e in bf_errors]) 166 if bf_warnings: 167 top_warnings.extend([u'Hidden field %s) %s' % (name, force_unicode(e)) for e in bf_warnings]) 147 168 hidden_fields.append(unicode(bf)) 148 169 else: 149 170 if errors_on_separate_row and bf_errors: 150 171 output.append(error_row % force_unicode(bf_errors)) 172 if errors_on_separate_row and bf_warnings: 173 output.append(error_row % force_unicode(bf_warnings)) 151 174 if bf.label: 152 175 label = conditional_escape(force_unicode(bf.label)) 153 176 # Only add the suffix if the label does not end in … … class BaseForm(StrAndUnicode): 163 186 else: 164 187 help_text = u'' 165 188 output.append(normal_row % {'errors': force_unicode(bf_errors), 'label': force_unicode(label), 'field': unicode(bf), 'help_text': help_text}) 189 if top_warnings: 190 output.insert(0, error_row % force_unicode(top_warnings)) 166 191 if top_errors: 167 192 output.insert(0, error_row % force_unicode(top_errors)) 168 193 if hidden_fields: # Insert any hidden fields in the last row. … … class BaseForm(StrAndUnicode): 204 229 are none. 205 230 """ 206 231 return self.errors.get(NON_FIELD_ERRORS, self.error_class()) 232 233 def non_field_warnings(self): 234 return self.warnings.get(NON_FIELD_ERRORS, self.warning_class()) 207 235 208 236 def full_clean(self): 209 237 """ … … class BaseForm(StrAndUnicode): 211 239 self.cleaned_data. 212 240 """ 213 241 self._errors = ErrorDict() 242 self._warnings = WarningDict() 214 243 if not self.is_bound: # Stop further processing. 215 244 return 216 245 self.cleaned_data = {} … … class BaseForm(StrAndUnicode): 226 255 try: 227 256 if isinstance(field, FileField): 228 257 initial = self.initial.get(name, field.initial) 229 value = field.clean(value, initial) 258 try: 259 value = field.clean(value, initial, lambda m: self.add_warning(name, m)) 260 except TypeError: 261 value = field.clean(value, initial) 230 262 else: 231 value = field.clean(value) 263 try: 264 value = field.clean(value) 265 except TypeError: 266 value = field.clean(value, lambda m: self.add_warning(name, m)) 232 267 self.cleaned_data[name] = value 233 268 if hasattr(self, 'clean_%s' % name): 234 value = getattr(self, 'clean_%s' % name)() 269 try: 270 value = getattr(self, 'clean_%s' % name)( lambda m: self.add_warning(name, m)) 271 except TypeError: 272 value = getattr(self, 'clean_%s' % name)() 235 273 self.cleaned_data[name] = value 236 274 except ValidationError, e: 237 275 self._errors[name] = e.messages 238 276 if name in self.cleaned_data: 239 277 del self.cleaned_data[name] 240 278 try: 241 self.cleaned_data = self.clean() 279 try: 280 self.cleaned_data = self.clean(lambda m: self.add_warning(name, m)) 281 except TypeError: 282 self.cleaned_data = self.clean() 242 283 except ValidationError, e: 243 284 self._errors[NON_FIELD_ERRORS] = e.messages 244 285 if self._errors: … … class BoundField(StrAndUnicode): 353 394 """ 354 395 return self.form.errors.get(self.name, self.form.error_class()) 355 396 errors = property(_errors) 397 398 def _warnings(self): 399 return self.form.warnings.get(self.name, self.form.warning_class()) 400 warnings = property(_warnings) 356 401 357 402 def as_widget(self, widget=None, attrs=None, only_initial=False): 358 403 """ -
django/forms/util.py
diff --git a/django/forms/util.py b/django/forms/util.py index b9b88a6..e51d810 100644
a b class ValidationError(Exception): 66 66 # AttributeError: ValidationError instance has no attribute 'args' 67 67 # See http://www.python.org/doc/current/tut/node10.html#handling 68 68 return repr(self.messages) 69 70 class WarningDict(ErrorDict): 71 """ 72 A collection of warnings that knows how to display itself in various formats. 73 74 The dictionary keys are the field names, and the values are the warnings. 75 """ 76 def as_ul(self): 77 if not self: 78 return u'' 79 return mark_safe(u'<ul class="warninglist">%s</ul>' % 80 ''.join([u'<li>%s%s</li>' % (k, force_unicode(v)) 81 for k, v in self.items()])) 82 83 84 class WarningList(ErrorList): 85 """ 86 A collection of warnings that knows how to display itself in various formats. 87 """ 88 def as_ul(self): 89 if not self: 90 return u'' 91 return mark_safe(u'<ul class="warninglist">%s</ul>' % 92 ''.join([u'<li>%s</li>' % conditional_escape(force_unicode(e)) 93 for e in self])) -
docs/ref/forms/validation.txt
diff --git a/docs/ref/forms/validation.txt b/docs/ref/forms/validation.txt index 3ad9ce9..992554b 100644
a b dictionary if there are any errors in the form. However, this behaviour may 303 303 change in the future, so it's not a bad idea to clean up after yourself in the 304 304 first place. 305 305 306 307 Validation Warnings 308 =================== 309 310 In addition to raising ValidationErrors you can also use Validation Warnings. 311 These are things you want to bring to the user's attention, but they don't 312 actually indicate that the input is invalid. 313 314 In order to send a validation warning, you need to add an extra argument to your 315 clean function, this argument is a callable that sends a warning. 316 317 For example, if we have a form with an ``IntegerField``, and we want to remind 318 the user that the value may be too small, but still allow it we could right a 319 validation method that looks like:: 320 321 from django.core.warnings import warn 322 def clean_value(self, warn): 323 p = self.cleaned_data['value'] 324 if p < 10: 325 warn("That's a pretty small value.") 326 return p 327 328 If a user enters a value under 10 they will be warned, but by default the 329 form will still be valid. Since there are cases where you might want a warning 330 to invalidate a form a form's ``is_valid()`` method takes a single boolean 331 argument that determines whether or not warnings should impact whether or not a 332 form is valid, by default it is False. This allows you to have behavior such as 333 displaying a form with warnings once, but when it's resubmitted allowing any 334 warnings. -
tests/regressiontests/forms/tests.py
diff --git a/tests/regressiontests/forms/tests.py b/tests/regressiontests/forms/tests.py index 6a8b017..dd70b0f 100644
a b from util import tests as util_tests 30 30 from widgets import tests as widgets_tests 31 31 from formsets import tests as formset_tests 32 32 from media import media_tests 33 from warnings import tests as warning_tests 33 34 34 35 __test__ = { 35 36 'extra_tests': extra_tests, … … __test__ = { 63 64 'media_tests': media_tests, 64 65 'util_tests': util_tests, 65 66 'widgets_tests': widgets_tests, 67 'warning_tests': warning_tests, 66 68 } 67 69 68 70 if __name__ == "__main__": -
new file tests/regressiontests/forms/warnings.py
diff --git a/tests/regressiontests/forms/warnings.py b/tests/regressiontests/forms/warnings.py new file mode 100644 index 0000000..4210faa
- + 1 tests = """ 2 >>> from django import forms 3 >>> class PriceForm(forms.Form): 4 ... price = forms.IntegerField() 5 ... 6 ... def clean_price(self, warn): 7 ... p = self.cleaned_data['price'] 8 ... if p < 10: 9 ... warn(u"That's an awfully low price") 10 ... if p == 7: 11 ... warn(u"7 is a pretty strange price") 12 ... return p 13 14 >>> form = PriceForm({'price': 5}) 15 >>> form.is_valid() 16 True 17 >>> form.is_valid(True) 18 False 19 >>> form.warnings 20 {'price': [u"That's an awfully low price"]} 21 >>> form = PriceForm({'price': 7}) 22 >>> form.is_valid() 23 True 24 >>> form.warnings 25 {'price': [u"That's an awfully low price", u'7 is a pretty strange price']} 26 >>> print form.as_p() 27 <ul class="warninglist"><li>That's an awfully low price</li><li>7 is a pretty strange price</li></ul> 28 <p><label for="id_price">Price:</label> <input type="text" name="price" value="7" id="id_price" /></p> 29 """