Ticket #23: form-warnings.2.diff

File form-warnings.2.diff, 12.2 KB (added by Alex Gaynor, 15 years ago)

removed the nasty warning system, instead pass a callable to the clean functions

  • django/forms/forms.py

    diff --git a/django/forms/forms.py b/django/forms/forms.py
    index f5bea10..1de8c03 100644
    a b Form classes  
    33"""
    44
    55from copy import deepcopy
     6from random import randrange
    67
    78from django.utils.datastructures import SortedDict
    89from django.utils.html import conditional_escape
    from django.utils.safestring import mark_safe  
    1112
    1213from fields import Field, FileField
    1314from widgets import Media, media_property, TextInput, Textarea
    14 from util import flatatt, ErrorDict, ErrorList, ValidationError
     15from util import flatatt, ErrorDict, ErrorList, ValidationError, WarningDict, WarningList
    1516
    1617__all__ = ('BaseForm', 'Form')
    1718
    class BaseForm(StrAndUnicode):  
    6970    # information. Any improvements to the form API should be made to *this*
    7071    # class, not to the Form class.
    7172    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=':',
    7374                 empty_permitted=False):
    7475        self.is_bound = data is not None or files is not None
    7576        self.data = data or {}
    class BaseForm(StrAndUnicode):  
    7879        self.prefix = prefix
    7980        self.initial = initial or {}
    8081        self.error_class = error_class
     82        self.warning_class = warning_class
    8183        self.label_suffix = label_suffix
    8284        self.empty_permitted = empty_permitted
    8385        self._errors = None # Stores the errors after clean() has been called.
     86        self._warnings = None
    8487        self._changed_data = None
    8588
    8689        # The base_fields class attribute is the *class-wide* definition of
    class BaseForm(StrAndUnicode):  
    111114            self.full_clean()
    112115        return self._errors
    113116    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)
    114123
    115     def is_valid(self):
     124    def is_valid(self, require_no_warnings=False):
    116125        """
    117126        Returns True if the form has no errors. Otherwise, False. If errors are
    118127        being ignored, returns False.
    119128        """
     129        if require_no_warnings:
     130            return self.is_bound and not bool(self.errors) and not bool(self.warnings)
    120131        return self.is_bound and not bool(self.errors)
    121132
    122133    def add_prefix(self, field_name):
    class BaseForm(StrAndUnicode):  
    133144        Add a 'initial' prefix for checking dynamic initial values
    134145        """
    135146        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])
    136153
    137154    def _html_output(self, normal_row, error_row, row_ender, help_text_html, errors_on_separate_row):
    138155        "Helper function for outputting HTML. Used by as_table(), as_ul(), as_p()."
    139156        top_errors = self.non_field_errors() # Errors that should be displayed above all fields.
     157        top_warnings = self.non_field_warnings()
    140158        output, hidden_fields = [], []
    141159        for name, field in self.fields.items():
    142160            bf = BoundField(self, field, name)
    143161            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])
    144163            if bf.is_hidden:
    145164                if bf_errors:
    146165                    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])
    147168                hidden_fields.append(unicode(bf))
    148169            else:
    149170                if errors_on_separate_row and bf_errors:
    150171                    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))
    151174                if bf.label:
    152175                    label = conditional_escape(force_unicode(bf.label))
    153176                    # Only add the suffix if the label does not end in
    class BaseForm(StrAndUnicode):  
    163186                else:
    164187                    help_text = u''
    165188                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))
    166191        if top_errors:
    167192            output.insert(0, error_row % force_unicode(top_errors))
    168193        if hidden_fields: # Insert any hidden fields in the last row.
    class BaseForm(StrAndUnicode):  
    204229        are none.
    205230        """
    206231        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())
    207235
    208236    def full_clean(self):
    209237        """
    class BaseForm(StrAndUnicode):  
    211239        self.cleaned_data.
    212240        """
    213241        self._errors = ErrorDict()
     242        self._warnings = WarningDict()
    214243        if not self.is_bound: # Stop further processing.
    215244            return
    216245        self.cleaned_data = {}
    class BaseForm(StrAndUnicode):  
    226255            try:
    227256                if isinstance(field, FileField):
    228257                    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)                       
    230262                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))
    232267                self.cleaned_data[name] = value
    233268                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)()
    235273                    self.cleaned_data[name] = value
    236274            except ValidationError, e:
    237275                self._errors[name] = e.messages
    238276                if name in self.cleaned_data:
    239277                    del self.cleaned_data[name]
    240278        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()
    242283        except ValidationError, e:
    243284            self._errors[NON_FIELD_ERRORS] = e.messages
    244285        if self._errors:
    class BoundField(StrAndUnicode):  
    353394        """
    354395        return self.form.errors.get(self.name, self.form.error_class())
    355396    errors = property(_errors)
     397   
     398    def _warnings(self):
     399        return self.form.warnings.get(self.name, self.form.warning_class())
     400    warnings = property(_warnings)
    356401
    357402    def as_widget(self, widget=None, attrs=None, only_initial=False):
    358403        """
  • 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):  
    6666        # AttributeError: ValidationError instance has no attribute 'args'
    6767        # See http://www.python.org/doc/current/tut/node10.html#handling
    6868        return repr(self.messages)
     69
     70class 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
     84class 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  
    303303change in the future, so it's not a bad idea to clean up after yourself in the
    304304first place.
    305305
     306
     307Validation Warnings
     308===================
     309
     310In addition to raising ValidationErrors you can also use Validation Warnings. 
     311These are things you want to bring to the user's attention, but they don't
     312actually indicate that the input is invalid.
     313
     314In order to send a validation warning, you need to add an extra argument to your
     315clean function, this argument is a callable that sends a warning.
     316
     317For example, if we have a form with an ``IntegerField``, and we want to remind
     318the user that the value may be too small, but still allow it we could right a
     319validation 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
     328If a user enters a value under 10 they will be warned, but by default the
     329form will still be valid.  Since there are cases where you might want a warning
     330to invalidate a form a form's ``is_valid()`` method takes a single boolean
     331argument that determines whether or not warnings should impact whether or not a
     332form is valid, by default it is False.  This allows you to have behavior such as
     333displaying a form with warnings once, but when it's resubmitted allowing any
     334warnings.
  • 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  
    3030from widgets import tests as widgets_tests
    3131from formsets import tests as formset_tests
    3232from media import media_tests
     33from warnings import tests as warning_tests
    3334
    3435__test__ = {
    3536    'extra_tests': extra_tests,
    __test__ = {  
    6364    'media_tests': media_tests,
    6465    'util_tests': util_tests,
    6566    'widgets_tests': widgets_tests,
     67    'warning_tests': warning_tests,
    6668}
    6769
    6870if __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
    - +  
     1tests = """
     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()
     16True
     17>>> form.is_valid(True)
     18False
     19>>> form.warnings
     20{'price': [u"That's an awfully low price"]}
     21>>> form = PriceForm({'price': 7})
     22>>> form.is_valid()
     23True
     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&#39;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"""
Back to Top