Ticket #23: form-warnings.diff
File form-warnings.diff, 13.1 KB (added by , 16 years ago) |
---|
-
new file django/core/warnings.py
diff --git a/django/core/warnings.py b/django/core/warnings.py new file mode 100644 index 0000000..ba0575d
- + 1 """ 2 A system for implementing warnings. Based on the Django signal dispatcher. 3 """ 4 try: 5 from threading import currentThread 6 except ImportError: 7 from django.util.thread_support import currentThread 8 9 from django.dispatch import Signal 10 11 def create_reciever(callback=lambda *args, **kwargs: None, dispatch_uid=None): 12 def method(thread_id, message, **kwargs): 13 if thread_id != id(currentThread()): 14 return 15 callback(message, **kwargs) 16 17 warning.connect(method, weak=False, dispatch_uid=dispatch_uid) 18 19 def remove_reciever(dispatch_uid=None): 20 warning.disconnect(dispatch_uid) 21 22 def warn(message): 23 warning.send(None, **{'thread_id': id(currentThread()), 'message':message}) 24 25 warning = Signal(providing_args=['thread_id', 'message']) -
django/forms/forms.py
diff --git a/django/forms/forms.py b/django/forms/forms.py index f5bea10..ef50c1e 100644
a b Form classes 3 3 """ 4 4 5 5 from copy import deepcopy 6 from random import randrange 6 7 8 from django.core import warnings 7 9 from django.utils.datastructures import SortedDict 8 10 from django.utils.html import conditional_escape 9 11 from django.utils.encoding import StrAndUnicode, smart_unicode, force_unicode … … from django.utils.safestring import mark_safe 11 13 12 14 from fields import Field, FileField 13 15 from widgets import Media, media_property, TextInput, Textarea 14 from util import flatatt, ErrorDict, ErrorList, ValidationError 16 from util import flatatt, ErrorDict, ErrorList, ValidationError, WarningDict, WarningList 15 17 16 18 __all__ = ('BaseForm', 'Form') 17 19 … … class BaseForm(StrAndUnicode): 69 71 # information. Any improvements to the form API should be made to *this* 70 72 # class, not to the Form class. 71 73 def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, 72 initial=None, error_class=ErrorList, label_suffix=':',74 initial=None, error_class=ErrorList, warning_class=WarningList, label_suffix=':', 73 75 empty_permitted=False): 74 76 self.is_bound = data is not None or files is not None 75 77 self.data = data or {} … … class BaseForm(StrAndUnicode): 78 80 self.prefix = prefix 79 81 self.initial = initial or {} 80 82 self.error_class = error_class 83 self.warning_class = warning_class 81 84 self.label_suffix = label_suffix 82 85 self.empty_permitted = empty_permitted 83 86 self._errors = None # Stores the errors after clean() has been called. 87 self._warnings = None 84 88 self._changed_data = None 85 89 86 90 # The base_fields class attribute is the *class-wide* definition of … … class BaseForm(StrAndUnicode): 111 115 self.full_clean() 112 116 return self._errors 113 117 errors = property(_get_errors) 118 119 def _get_warnings(self): 120 if self._warnings is None: 121 self.full_clean() 122 return self._warnings 123 warnings = property(_get_warnings) 114 124 115 def is_valid(self ):125 def is_valid(self, require_no_warnings=False): 116 126 """ 117 127 Returns True if the form has no errors. Otherwise, False. If errors are 118 128 being ignored, returns False. 119 129 """ 130 if require_no_warnings: 131 return self.is_bound and not bool(self.errors) and not bool(self.warnings) 120 132 return self.is_bound and not bool(self.errors) 121 133 122 134 def add_prefix(self, field_name): … … class BaseForm(StrAndUnicode): 137 149 def _html_output(self, normal_row, error_row, row_ender, help_text_html, errors_on_separate_row): 138 150 "Helper function for outputting HTML. Used by as_table(), as_ul(), as_p()." 139 151 top_errors = self.non_field_errors() # Errors that should be displayed above all fields. 152 top_warnings = self.non_field_warnings() 140 153 output, hidden_fields = [], [] 141 154 for name, field in self.fields.items(): 142 155 bf = BoundField(self, field, name) 143 156 bf_errors = self.error_class([conditional_escape(error) for error in bf.errors]) # Escape and cache in local variable. 157 bf_warnings = self.warning_class([conditional_escape(warning) for warning in bf.warnings]) 144 158 if bf.is_hidden: 145 159 if bf_errors: 146 160 top_errors.extend([u'(Hidden field %s) %s' % (name, force_unicode(e)) for e in bf_errors]) 161 if bf_warnings: 162 top_warnings.extend([u'Hidden field %s) %s' % (name, force_unicode(e)) for e in bf_warnings]) 147 163 hidden_fields.append(unicode(bf)) 148 164 else: 149 165 if errors_on_separate_row and bf_errors: 150 166 output.append(error_row % force_unicode(bf_errors)) 167 if errors_on_separate_row and bf_warnings: 168 output.append(error_row % force_unicode(bf_warnings)) 151 169 if bf.label: 152 170 label = conditional_escape(force_unicode(bf.label)) 153 171 # Only add the suffix if the label does not end in … … class BaseForm(StrAndUnicode): 163 181 else: 164 182 help_text = u'' 165 183 output.append(normal_row % {'errors': force_unicode(bf_errors), 'label': force_unicode(label), 'field': unicode(bf), 'help_text': help_text}) 184 if top_warnings: 185 output.insert(0, error_row % force_unicode(top_warnings)) 166 186 if top_errors: 167 187 output.insert(0, error_row % force_unicode(top_errors)) 168 188 if hidden_fields: # Insert any hidden fields in the last row. … … class BaseForm(StrAndUnicode): 204 224 are none. 205 225 """ 206 226 return self.errors.get(NON_FIELD_ERRORS, self.error_class()) 227 228 def non_field_warnings(self): 229 return self.warnings.get(NON_FIELD_ERRORS, self.warning_class()) 207 230 208 231 def full_clean(self): 209 232 """ … … class BaseForm(StrAndUnicode): 211 234 self.cleaned_data. 212 235 """ 213 236 self._errors = ErrorDict() 237 self._warnings = WarningDict() 214 238 if not self.is_bound: # Stop further processing. 215 239 return 216 240 self.cleaned_data = {} … … class BaseForm(StrAndUnicode): 223 247 # Each widget type knows how to retrieve its own data, because some 224 248 # widgets split data over several HTML fields. 225 249 value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name)) 250 def add_warning(message, **kwargs): 251 if name in self._warnings: 252 self._warnings[name].append(message) 253 else: 254 self._warnings[name] = WarningList([message]) 255 dispatch_uid = 'warning-%s-%s' % (name ,''.join([str(randrange(0, 10)) for x in xrange(10)])) 256 warnings.create_reciever(add_warning, dispatch_uid) 226 257 try: 227 258 if isinstance(field, FileField): 228 259 initial = self.initial.get(name, field.initial) … … class BaseForm(StrAndUnicode): 237 268 self._errors[name] = e.messages 238 269 if name in self.cleaned_data: 239 270 del self.cleaned_data[name] 271 finally: 272 warnings.remove_reciever(dispatch_uid) 273 def add_warning(message, **kwargs): 274 if NON_FIELD_ERRORS in self._warnings: 275 self._warnings[NON_FIELD_ERRORS].append(message) 276 else: 277 self._warnings[NON_FIELD_ERRORS] = WarningList([message]) 278 dispatch_uid = 'warning-%s-%s' % (NON_FIELD_ERRORS ,''.join([str(randrange(0, 10)) for x in xrange(10)])) 279 warnings.create_reciever(add_warning, dispatch_uid) 240 280 try: 241 281 self.cleaned_data = self.clean() 242 282 except ValidationError, e: 243 283 self._errors[NON_FIELD_ERRORS] = e.messages 284 finally: 285 warnings.remove_reciever(dispatch_uid) 244 286 if self._errors: 245 287 delattr(self, 'cleaned_data') 246 288 … … class BoundField(StrAndUnicode): 353 395 """ 354 396 return self.form.errors.get(self.name, self.form.error_class()) 355 397 errors = property(_errors) 398 399 def _warnings(self): 400 return self.form.warnings.get(self.name, self.form.warning_class()) 401 warnings = property(_warnings) 356 402 357 403 def as_widget(self, widget=None, attrs=None, only_initial=False): 358 404 """ -
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..dee7b54 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 This functionality works with ``django.core.warnings``, in a form validation 315 method when you want to throw a warning you can call ``django.core.warnings.warn`` 316 with the warning message as the sole argument. 317 318 For example, if we have a form with an ``IntegerField``, and we want to remind 319 the user that the value may be too small, but still allow it we could right a 320 validation method that looks like:: 321 322 from django.core.warnings import warn 323 def clean_value(self): 324 p = self.cleaned_data['value'] 325 if p < 10: 326 warn("That's a pretty small value.") 327 return p 328 329 If a user enters a value under 10 they will be warned, but by default the 330 form will still be valid. Since there are cases where you might want a warning 331 to invalidate a form a form's ``is_valid()`` method takes a single boolean 332 argument that determines whether or not warnings should impact whether or not a 333 form is valid, by default it is False. This allows you to have behavior such as 334 displaying a form with warnings once, but when it's resubmitted allowing any 335 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..92b4f28
- + 1 tests = """ 2 >>> from django import forms 3 >>> from django.core.warnings import warn 4 >>> class PriceForm(forms.Form): 5 ... price = forms.IntegerField() 6 ... 7 ... def clean_price(self): 8 ... p = self.cleaned_data['price'] 9 ... if p < 10: 10 ... warn(u"That's an awfully low price") 11 ... if p == 7: 12 ... warn(u"7 is a pretty strange price") 13 ... return p 14 15 >>> form = PriceForm({'price': 5}) 16 >>> form.is_valid() 17 True 18 >>> form.is_valid(True) 19 False 20 >>> form.warnings 21 {'price': [u"That's an awfully low price"]} 22 >>> form = PriceForm({'price': 7}) 23 >>> form.is_valid() 24 True 25 >>> form.warnings 26 {'price': [u"That's an awfully low price", u'7 is a pretty strange price']} 27 >>> print form.as_p() 28 <ul class="warninglist"><li>That's an awfully low price</li><li>7 is a pretty strange price</li></ul> 29 <p><label for="id_price">Price:</label> <input type="text" name="price" value="7" id="id_price" /></p> 30 """