app/django/newforms/forms.py
changeset 54 03e267d67478
equal deleted inserted replaced
53:57b4279d8c4e 54:03e267d67478
       
     1 """
       
     2 Form classes
       
     3 """
       
     4 
       
     5 from copy import deepcopy
       
     6 
       
     7 from django.utils.datastructures import SortedDict
       
     8 from django.utils.html import escape
       
     9 from django.utils.encoding import StrAndUnicode, smart_unicode, force_unicode
       
    10 from django.utils.safestring import mark_safe
       
    11 
       
    12 from fields import Field, FileField
       
    13 from widgets import TextInput, Textarea
       
    14 from util import flatatt, ErrorDict, ErrorList, ValidationError
       
    15 
       
    16 __all__ = ('BaseForm', 'Form')
       
    17 
       
    18 NON_FIELD_ERRORS = '__all__'
       
    19 
       
    20 def pretty_name(name):
       
    21     "Converts 'first_name' to 'First name'"
       
    22     name = name[0].upper() + name[1:]
       
    23     return name.replace('_', ' ')
       
    24 
       
    25 def get_declared_fields(bases, attrs, with_base_fields=True):
       
    26     """
       
    27     Create a list of form field instances from the passed in 'attrs', plus any
       
    28     similar fields on the base classes (in 'bases'). This is used by both the
       
    29     Form and ModelForm metclasses.
       
    30 
       
    31     If 'with_base_fields' is True, all fields from the bases are used.
       
    32     Otherwise, only fields in the 'declared_fields' attribute on the bases are
       
    33     used. The distinction is useful in ModelForm subclassing.
       
    34     """
       
    35     fields = [(field_name, attrs.pop(field_name)) for field_name, obj in attrs.items() if isinstance(obj, Field)]
       
    36     fields.sort(lambda x, y: cmp(x[1].creation_counter, y[1].creation_counter))
       
    37 
       
    38     # If this class is subclassing another Form, add that Form's fields.
       
    39     # Note that we loop over the bases in *reverse*. This is necessary in
       
    40     # order to preserve the correct order of fields.
       
    41     if with_base_fields:
       
    42         for base in bases[::-1]:
       
    43             if hasattr(base, 'base_fields'):
       
    44                 fields = base.base_fields.items() + fields
       
    45     else:
       
    46         for base in bases[::-1]:
       
    47             if hasattr(base, 'declared_fields'):
       
    48                 fields = base.declared_fields.items() + fields
       
    49 
       
    50     return SortedDict(fields)
       
    51 
       
    52 class DeclarativeFieldsMetaclass(type):
       
    53     """
       
    54     Metaclass that converts Field attributes to a dictionary called
       
    55     'base_fields', taking into account parent class 'base_fields' as well.
       
    56     """
       
    57     def __new__(cls, name, bases, attrs):
       
    58         attrs['base_fields'] = get_declared_fields(bases, attrs)
       
    59         return type.__new__(cls, name, bases, attrs)
       
    60 
       
    61 class BaseForm(StrAndUnicode):
       
    62     # This is the main implementation of all the Form logic. Note that this
       
    63     # class is different than Form. See the comments by the Form class for more
       
    64     # information. Any improvements to the form API should be made to *this*
       
    65     # class, not to the Form class.
       
    66     def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
       
    67                  initial=None, error_class=ErrorList, label_suffix=':'):
       
    68         self.is_bound = data is not None or files is not None
       
    69         self.data = data or {}
       
    70         self.files = files or {}
       
    71         self.auto_id = auto_id
       
    72         self.prefix = prefix
       
    73         self.initial = initial or {}
       
    74         self.error_class = error_class
       
    75         self.label_suffix = label_suffix
       
    76         self._errors = None # Stores the errors after clean() has been called.
       
    77 
       
    78         # The base_fields class attribute is the *class-wide* definition of
       
    79         # fields. Because a particular *instance* of the class might want to
       
    80         # alter self.fields, we create self.fields here by copying base_fields.
       
    81         # Instances should always modify self.fields; they should not modify
       
    82         # self.base_fields.
       
    83         self.fields = deepcopy(self.base_fields)
       
    84 
       
    85     def __unicode__(self):
       
    86         return self.as_table()
       
    87 
       
    88     def __iter__(self):
       
    89         for name, field in self.fields.items():
       
    90             yield BoundField(self, field, name)
       
    91 
       
    92     def __getitem__(self, name):
       
    93         "Returns a BoundField with the given name."
       
    94         try:
       
    95             field = self.fields[name]
       
    96         except KeyError:
       
    97             raise KeyError('Key %r not found in Form' % name)
       
    98         return BoundField(self, field, name)
       
    99 
       
   100     def _get_errors(self):
       
   101         "Returns an ErrorDict for the data provided for the form"
       
   102         if self._errors is None:
       
   103             self.full_clean()
       
   104         return self._errors
       
   105     errors = property(_get_errors)
       
   106 
       
   107     def is_valid(self):
       
   108         """
       
   109         Returns True if the form has no errors. Otherwise, False. If errors are
       
   110         being ignored, returns False.
       
   111         """
       
   112         return self.is_bound and not bool(self.errors)
       
   113 
       
   114     def add_prefix(self, field_name):
       
   115         """
       
   116         Returns the field name with a prefix appended, if this Form has a
       
   117         prefix set.
       
   118 
       
   119         Subclasses may wish to override.
       
   120         """
       
   121         return self.prefix and ('%s-%s' % (self.prefix, field_name)) or field_name
       
   122 
       
   123     def _html_output(self, normal_row, error_row, row_ender, help_text_html, errors_on_separate_row):
       
   124         "Helper function for outputting HTML. Used by as_table(), as_ul(), as_p()."
       
   125         top_errors = self.non_field_errors() # Errors that should be displayed above all fields.
       
   126         output, hidden_fields = [], []
       
   127         for name, field in self.fields.items():
       
   128             bf = BoundField(self, field, name)
       
   129             bf_errors = self.error_class([escape(error) for error in bf.errors]) # Escape and cache in local variable.
       
   130             if bf.is_hidden:
       
   131                 if bf_errors:
       
   132                     top_errors.extend([u'(Hidden field %s) %s' % (name, force_unicode(e)) for e in bf_errors])
       
   133                 hidden_fields.append(unicode(bf))
       
   134             else:
       
   135                 if errors_on_separate_row and bf_errors:
       
   136                     output.append(error_row % force_unicode(bf_errors))
       
   137                 if bf.label:
       
   138                     label = escape(force_unicode(bf.label))
       
   139                     # Only add the suffix if the label does not end in
       
   140                     # punctuation.
       
   141                     if self.label_suffix:
       
   142                         if label[-1] not in ':?.!':
       
   143                             label += self.label_suffix
       
   144                     label = bf.label_tag(label) or ''
       
   145                 else:
       
   146                     label = ''
       
   147                 if field.help_text:
       
   148                     help_text = help_text_html % force_unicode(field.help_text)
       
   149                 else:
       
   150                     help_text = u''
       
   151                 output.append(normal_row % {'errors': force_unicode(bf_errors), 'label': force_unicode(label), 'field': unicode(bf), 'help_text': help_text})
       
   152         if top_errors:
       
   153             output.insert(0, error_row % force_unicode(top_errors))
       
   154         if hidden_fields: # Insert any hidden fields in the last row.
       
   155             str_hidden = u''.join(hidden_fields)
       
   156             if output:
       
   157                 last_row = output[-1]
       
   158                 # Chop off the trailing row_ender (e.g. '</td></tr>') and
       
   159                 # insert the hidden fields.
       
   160                 output[-1] = last_row[:-len(row_ender)] + str_hidden + row_ender
       
   161             else:
       
   162                 # If there aren't any rows in the output, just append the
       
   163                 # hidden fields.
       
   164                 output.append(str_hidden)
       
   165         return mark_safe(u'\n'.join(output))
       
   166 
       
   167     def as_table(self):
       
   168         "Returns this form rendered as HTML <tr>s -- excluding the <table></table>."
       
   169         return self._html_output(u'<tr><th>%(label)s</th><td>%(errors)s%(field)s%(help_text)s</td></tr>', u'<tr><td colspan="2">%s</td></tr>', '</td></tr>', u'<br />%s', False)
       
   170 
       
   171     def as_ul(self):
       
   172         "Returns this form rendered as HTML <li>s -- excluding the <ul></ul>."
       
   173         return self._html_output(u'<li>%(errors)s%(label)s %(field)s%(help_text)s</li>', u'<li>%s</li>', '</li>', u' %s', False)
       
   174 
       
   175     def as_p(self):
       
   176         "Returns this form rendered as HTML <p>s."
       
   177         return self._html_output(u'<p>%(label)s %(field)s%(help_text)s</p>', u'%s', '</p>', u' %s', True)
       
   178 
       
   179     def non_field_errors(self):
       
   180         """
       
   181         Returns an ErrorList of errors that aren't associated with a particular
       
   182         field -- i.e., from Form.clean(). Returns an empty ErrorList if there
       
   183         are none.
       
   184         """
       
   185         return self.errors.get(NON_FIELD_ERRORS, self.error_class())
       
   186 
       
   187     def full_clean(self):
       
   188         """
       
   189         Cleans all of self.data and populates self._errors and
       
   190         self.cleaned_data.
       
   191         """
       
   192         self._errors = ErrorDict()
       
   193         if not self.is_bound: # Stop further processing.
       
   194             return
       
   195         self.cleaned_data = {}
       
   196         for name, field in self.fields.items():
       
   197             # value_from_datadict() gets the data from the data dictionaries.
       
   198             # Each widget type knows how to retrieve its own data, because some
       
   199             # widgets split data over several HTML fields.
       
   200             value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
       
   201             try:
       
   202                 if isinstance(field, FileField):
       
   203                     initial = self.initial.get(name, field.initial)
       
   204                     value = field.clean(value, initial)
       
   205                 else:
       
   206                     value = field.clean(value)
       
   207                 self.cleaned_data[name] = value
       
   208                 if hasattr(self, 'clean_%s' % name):
       
   209                     value = getattr(self, 'clean_%s' % name)()
       
   210                     self.cleaned_data[name] = value
       
   211             except ValidationError, e:
       
   212                 self._errors[name] = e.messages
       
   213                 if name in self.cleaned_data:
       
   214                     del self.cleaned_data[name]
       
   215         try:
       
   216             self.cleaned_data = self.clean()
       
   217         except ValidationError, e:
       
   218             self._errors[NON_FIELD_ERRORS] = e.messages
       
   219         if self._errors:
       
   220             delattr(self, 'cleaned_data')
       
   221 
       
   222     def clean(self):
       
   223         """
       
   224         Hook for doing any extra form-wide cleaning after Field.clean() been
       
   225         called on every field. Any ValidationError raised by this method will
       
   226         not be associated with a particular field; it will have a special-case
       
   227         association with the field named '__all__'.
       
   228         """
       
   229         return self.cleaned_data
       
   230 
       
   231     def is_multipart(self):
       
   232         """
       
   233         Returns True if the form needs to be multipart-encrypted, i.e. it has
       
   234         FileInput. Otherwise, False.
       
   235         """
       
   236         for field in self.fields.values():
       
   237             if field.widget.needs_multipart_form:
       
   238                 return True
       
   239         return False
       
   240 
       
   241 class Form(BaseForm):
       
   242     "A collection of Fields, plus their associated data."
       
   243     # This is a separate class from BaseForm in order to abstract the way
       
   244     # self.fields is specified. This class (Form) is the one that does the
       
   245     # fancy metaclass stuff purely for the semantic sugar -- it allows one
       
   246     # to define a form using declarative syntax.
       
   247     # BaseForm itself has no way of designating self.fields.
       
   248     __metaclass__ = DeclarativeFieldsMetaclass
       
   249 
       
   250 class BoundField(StrAndUnicode):
       
   251     "A Field plus data"
       
   252     def __init__(self, form, field, name):
       
   253         self.form = form
       
   254         self.field = field
       
   255         self.name = name
       
   256         self.html_name = form.add_prefix(name)
       
   257         if self.field.label is None:
       
   258             self.label = pretty_name(name)
       
   259         else:
       
   260             self.label = self.field.label
       
   261         self.help_text = field.help_text or ''
       
   262 
       
   263     def __unicode__(self):
       
   264         """Renders this field as an HTML widget."""
       
   265         return self.as_widget()
       
   266 
       
   267     def _errors(self):
       
   268         """
       
   269         Returns an ErrorList for this field. Returns an empty ErrorList
       
   270         if there are none.
       
   271         """
       
   272         return self.form.errors.get(self.name, self.form.error_class())
       
   273     errors = property(_errors)
       
   274 
       
   275     def as_widget(self, widget=None, attrs=None):
       
   276         """
       
   277         Renders the field by rendering the passed widget, adding any HTML
       
   278         attributes passed as attrs.  If no widget is specified, then the
       
   279         field's default widget will be used.
       
   280         """
       
   281         if not widget:
       
   282             widget = self.field.widget
       
   283         attrs = attrs or {}
       
   284         auto_id = self.auto_id
       
   285         if auto_id and 'id' not in attrs and 'id' not in widget.attrs:
       
   286             attrs['id'] = auto_id
       
   287         if not self.form.is_bound:
       
   288             data = self.form.initial.get(self.name, self.field.initial)
       
   289             if callable(data):
       
   290                 data = data()
       
   291         else:
       
   292             data = self.data
       
   293         return widget.render(self.html_name, data, attrs=attrs)
       
   294 
       
   295     def as_text(self, attrs=None):
       
   296         """
       
   297         Returns a string of HTML for representing this as an <input type="text">.
       
   298         """
       
   299         return self.as_widget(TextInput(), attrs)
       
   300 
       
   301     def as_textarea(self, attrs=None):
       
   302         "Returns a string of HTML for representing this as a <textarea>."
       
   303         return self.as_widget(Textarea(), attrs)
       
   304 
       
   305     def as_hidden(self, attrs=None):
       
   306         """
       
   307         Returns a string of HTML for representing this as an <input type="hidden">.
       
   308         """
       
   309         return self.as_widget(self.field.hidden_widget(), attrs)
       
   310 
       
   311     def _data(self):
       
   312         """
       
   313         Returns the data for this BoundField, or None if it wasn't given.
       
   314         """
       
   315         return self.field.widget.value_from_datadict(self.form.data, self.form.files, self.html_name)
       
   316     data = property(_data)
       
   317 
       
   318     def label_tag(self, contents=None, attrs=None):
       
   319         """
       
   320         Wraps the given contents in a <label>, if the field has an ID attribute.
       
   321         Does not HTML-escape the contents. If contents aren't given, uses the
       
   322         field's HTML-escaped label.
       
   323 
       
   324         If attrs are given, they're used as HTML attributes on the <label> tag.
       
   325         """
       
   326         contents = contents or escape(self.label)
       
   327         widget = self.field.widget
       
   328         id_ = widget.attrs.get('id') or self.auto_id
       
   329         if id_:
       
   330             attrs = attrs and flatatt(attrs) or ''
       
   331             contents = '<label for="%s"%s>%s</label>' % (widget.id_for_label(id_), attrs, contents)
       
   332         return mark_safe(contents)
       
   333 
       
   334     def _is_hidden(self):
       
   335         "Returns True if this BoundField's widget is hidden."
       
   336         return self.field.widget.is_hidden
       
   337     is_hidden = property(_is_hidden)
       
   338 
       
   339     def _auto_id(self):
       
   340         """
       
   341         Calculates and returns the ID attribute for this BoundField, if the
       
   342         associated Form has specified auto_id. Returns an empty string otherwise.
       
   343         """
       
   344         auto_id = self.form.auto_id
       
   345         if auto_id and '%s' in smart_unicode(auto_id):
       
   346             return smart_unicode(auto_id) % self.html_name
       
   347         elif auto_id:
       
   348             return self.html_name
       
   349         return ''
       
   350     auto_id = property(_auto_id)