app/django/forms/forms.py
changeset 323 ff1a9aa48cfd
equal deleted inserted replaced
322:6641e941ef1e 323:ff1a9aa48cfd
       
     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 Media, media_property, 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     Also integrates any additional media definitions
       
    35     """
       
    36     fields = [(field_name, attrs.pop(field_name)) for field_name, obj in attrs.items() if isinstance(obj, Field)]
       
    37     fields.sort(lambda x, y: cmp(x[1].creation_counter, y[1].creation_counter))
       
    38 
       
    39     # If this class is subclassing another Form, add that Form's fields.
       
    40     # Note that we loop over the bases in *reverse*. This is necessary in
       
    41     # order to preserve the correct order of fields.
       
    42     if with_base_fields:
       
    43         for base in bases[::-1]:
       
    44             if hasattr(base, 'base_fields'):
       
    45                 fields = base.base_fields.items() + fields
       
    46     else:
       
    47         for base in bases[::-1]:
       
    48             if hasattr(base, 'declared_fields'):
       
    49                 fields = base.declared_fields.items() + fields
       
    50 
       
    51     return SortedDict(fields)
       
    52 
       
    53 class DeclarativeFieldsMetaclass(type):
       
    54     """
       
    55     Metaclass that converts Field attributes to a dictionary called
       
    56     'base_fields', taking into account parent class 'base_fields' as well.
       
    57     """
       
    58     def __new__(cls, name, bases, attrs):
       
    59         attrs['base_fields'] = get_declared_fields(bases, attrs)
       
    60         new_class = super(DeclarativeFieldsMetaclass,
       
    61                      cls).__new__(cls, name, bases, attrs)
       
    62         if 'media' not in attrs:
       
    63             new_class.media = media_property(new_class)
       
    64         return new_class
       
    65 
       
    66 class BaseForm(StrAndUnicode):
       
    67     # This is the main implementation of all the Form logic. Note that this
       
    68     # class is different than Form. See the comments by the Form class for more
       
    69     # information. Any improvements to the form API should be made to *this*
       
    70     # class, not to the Form class.
       
    71     def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
       
    72                  initial=None, error_class=ErrorList, label_suffix=':',
       
    73                  empty_permitted=False):
       
    74         self.is_bound = data is not None or files is not None
       
    75         self.data = data or {}
       
    76         self.files = files or {}
       
    77         self.auto_id = auto_id
       
    78         self.prefix = prefix
       
    79         self.initial = initial or {}
       
    80         self.error_class = error_class
       
    81         self.label_suffix = label_suffix
       
    82         self.empty_permitted = empty_permitted
       
    83         self._errors = None # Stores the errors after clean() has been called.
       
    84         self._changed_data = None
       
    85 
       
    86         # The base_fields class attribute is the *class-wide* definition of
       
    87         # fields. Because a particular *instance* of the class might want to
       
    88         # alter self.fields, we create self.fields here by copying base_fields.
       
    89         # Instances should always modify self.fields; they should not modify
       
    90         # self.base_fields.
       
    91         self.fields = deepcopy(self.base_fields)
       
    92 
       
    93     def __unicode__(self):
       
    94         return self.as_table()
       
    95 
       
    96     def __iter__(self):
       
    97         for name, field in self.fields.items():
       
    98             yield BoundField(self, field, name)
       
    99 
       
   100     def __getitem__(self, name):
       
   101         "Returns a BoundField with the given name."
       
   102         try:
       
   103             field = self.fields[name]
       
   104         except KeyError:
       
   105             raise KeyError('Key %r not found in Form' % name)
       
   106         return BoundField(self, field, name)
       
   107 
       
   108     def _get_errors(self):
       
   109         "Returns an ErrorDict for the data provided for the form"
       
   110         if self._errors is None:
       
   111             self.full_clean()
       
   112         return self._errors
       
   113     errors = property(_get_errors)
       
   114 
       
   115     def is_valid(self):
       
   116         """
       
   117         Returns True if the form has no errors. Otherwise, False. If errors are
       
   118         being ignored, returns False.
       
   119         """
       
   120         return self.is_bound and not bool(self.errors)
       
   121 
       
   122     def add_prefix(self, field_name):
       
   123         """
       
   124         Returns the field name with a prefix appended, if this Form has a
       
   125         prefix set.
       
   126 
       
   127         Subclasses may wish to override.
       
   128         """
       
   129         return self.prefix and ('%s-%s' % (self.prefix, field_name)) or field_name
       
   130 
       
   131     def add_initial_prefix(self, field_name):
       
   132         """
       
   133         Add a 'initial' prefix for checking dynamic initial values
       
   134         """
       
   135         return u'initial-%s' % self.add_prefix(field_name)
       
   136 
       
   137     def _html_output(self, normal_row, error_row, row_ender, help_text_html, errors_on_separate_row):
       
   138         "Helper function for outputting HTML. Used by as_table(), as_ul(), as_p()."
       
   139         top_errors = self.non_field_errors() # Errors that should be displayed above all fields.
       
   140         output, hidden_fields = [], []
       
   141         for name, field in self.fields.items():
       
   142             bf = BoundField(self, field, name)
       
   143             bf_errors = self.error_class([escape(error) for error in bf.errors]) # Escape and cache in local variable.
       
   144             if bf.is_hidden:
       
   145                 if bf_errors:
       
   146                     top_errors.extend([u'(Hidden field %s) %s' % (name, force_unicode(e)) for e in bf_errors])
       
   147                 hidden_fields.append(unicode(bf))
       
   148             else:
       
   149                 if errors_on_separate_row and bf_errors:
       
   150                     output.append(error_row % force_unicode(bf_errors))
       
   151                 if bf.label:
       
   152                     label = escape(force_unicode(bf.label))
       
   153                     # Only add the suffix if the label does not end in
       
   154                     # punctuation.
       
   155                     if self.label_suffix:
       
   156                         if label[-1] not in ':?.!':
       
   157                             label += self.label_suffix
       
   158                     label = bf.label_tag(label) or ''
       
   159                 else:
       
   160                     label = ''
       
   161                 if field.help_text:
       
   162                     help_text = help_text_html % force_unicode(field.help_text)
       
   163                 else:
       
   164                     help_text = u''
       
   165                 output.append(normal_row % {'errors': force_unicode(bf_errors), 'label': force_unicode(label), 'field': unicode(bf), 'help_text': help_text})
       
   166         if top_errors:
       
   167             output.insert(0, error_row % force_unicode(top_errors))
       
   168         if hidden_fields: # Insert any hidden fields in the last row.
       
   169             str_hidden = u''.join(hidden_fields)
       
   170             if output:
       
   171                 last_row = output[-1]
       
   172                 # Chop off the trailing row_ender (e.g. '</td></tr>') and
       
   173                 # insert the hidden fields.
       
   174                 if not last_row.endswith(row_ender):
       
   175                     # This can happen in the as_p() case (and possibly others
       
   176                     # that users write): if there are only top errors, we may
       
   177                     # not be able to conscript the last row for our purposes,
       
   178                     # so insert a new, empty row.
       
   179                     last_row = normal_row % {'errors': '', 'label': '', 'field': '', 'help_text': ''}
       
   180                     output.append(last_row)
       
   181                 output[-1] = last_row[:-len(row_ender)] + str_hidden + row_ender
       
   182             else:
       
   183                 # If there aren't any rows in the output, just append the
       
   184                 # hidden fields.
       
   185                 output.append(str_hidden)
       
   186         return mark_safe(u'\n'.join(output))
       
   187 
       
   188     def as_table(self):
       
   189         "Returns this form rendered as HTML <tr>s -- excluding the <table></table>."
       
   190         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)
       
   191 
       
   192     def as_ul(self):
       
   193         "Returns this form rendered as HTML <li>s -- excluding the <ul></ul>."
       
   194         return self._html_output(u'<li>%(errors)s%(label)s %(field)s%(help_text)s</li>', u'<li>%s</li>', '</li>', u' %s', False)
       
   195 
       
   196     def as_p(self):
       
   197         "Returns this form rendered as HTML <p>s."
       
   198         return self._html_output(u'<p>%(label)s %(field)s%(help_text)s</p>', u'%s', '</p>', u' %s', True)
       
   199 
       
   200     def non_field_errors(self):
       
   201         """
       
   202         Returns an ErrorList of errors that aren't associated with a particular
       
   203         field -- i.e., from Form.clean(). Returns an empty ErrorList if there
       
   204         are none.
       
   205         """
       
   206         return self.errors.get(NON_FIELD_ERRORS, self.error_class())
       
   207 
       
   208     def full_clean(self):
       
   209         """
       
   210         Cleans all of self.data and populates self._errors and
       
   211         self.cleaned_data.
       
   212         """
       
   213         self._errors = ErrorDict()
       
   214         if not self.is_bound: # Stop further processing.
       
   215             return
       
   216         self.cleaned_data = {}
       
   217         # If the form is permitted to be empty, and none of the form data has
       
   218         # changed from the initial data, short circuit any validation.
       
   219         if self.empty_permitted and not self.has_changed():
       
   220             return
       
   221         for name, field in self.fields.items():
       
   222             # value_from_datadict() gets the data from the data dictionaries.
       
   223             # Each widget type knows how to retrieve its own data, because some
       
   224             # widgets split data over several HTML fields.
       
   225             value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
       
   226             try:
       
   227                 if isinstance(field, FileField):
       
   228                     initial = self.initial.get(name, field.initial)
       
   229                     value = field.clean(value, initial)
       
   230                 else:
       
   231                     value = field.clean(value)
       
   232                 self.cleaned_data[name] = value
       
   233                 if hasattr(self, 'clean_%s' % name):
       
   234                     value = getattr(self, 'clean_%s' % name)()
       
   235                     self.cleaned_data[name] = value
       
   236             except ValidationError, e:
       
   237                 self._errors[name] = e.messages
       
   238                 if name in self.cleaned_data:
       
   239                     del self.cleaned_data[name]
       
   240         try:
       
   241             self.cleaned_data = self.clean()
       
   242         except ValidationError, e:
       
   243             self._errors[NON_FIELD_ERRORS] = e.messages
       
   244         if self._errors:
       
   245             delattr(self, 'cleaned_data')
       
   246 
       
   247     def clean(self):
       
   248         """
       
   249         Hook for doing any extra form-wide cleaning after Field.clean() been
       
   250         called on every field. Any ValidationError raised by this method will
       
   251         not be associated with a particular field; it will have a special-case
       
   252         association with the field named '__all__'.
       
   253         """
       
   254         return self.cleaned_data
       
   255 
       
   256     def has_changed(self):
       
   257         """
       
   258         Returns True if data differs from initial.
       
   259         """
       
   260         return bool(self.changed_data)
       
   261 
       
   262     def _get_changed_data(self):
       
   263         if self._changed_data is None:
       
   264             self._changed_data = []
       
   265             # XXX: For now we're asking the individual widgets whether or not the
       
   266             # data has changed. It would probably be more efficient to hash the
       
   267             # initial data, store it in a hidden field, and compare a hash of the
       
   268             # submitted data, but we'd need a way to easily get the string value
       
   269             # for a given field. Right now, that logic is embedded in the render
       
   270             # method of each widget.
       
   271             for name, field in self.fields.items():
       
   272                 prefixed_name = self.add_prefix(name)
       
   273                 data_value = field.widget.value_from_datadict(self.data, self.files, prefixed_name)
       
   274                 if not field.show_hidden_initial:
       
   275                     initial_value = self.initial.get(name, field.initial)
       
   276                 else:
       
   277                     initial_prefixed_name = self.add_initial_prefix(name)
       
   278                     hidden_widget = field.hidden_widget()
       
   279                     initial_value = hidden_widget.value_from_datadict(
       
   280                         self.data, self.files, initial_prefixed_name)
       
   281                 if field.widget._has_changed(initial_value, data_value):
       
   282                     self._changed_data.append(name)
       
   283         return self._changed_data
       
   284     changed_data = property(_get_changed_data)
       
   285 
       
   286     def _get_media(self):
       
   287         """
       
   288         Provide a description of all media required to render the widgets on this form
       
   289         """
       
   290         media = Media()
       
   291         for field in self.fields.values():
       
   292             media = media + field.widget.media
       
   293         return media
       
   294     media = property(_get_media)
       
   295 
       
   296     def is_multipart(self):
       
   297         """
       
   298         Returns True if the form needs to be multipart-encrypted, i.e. it has
       
   299         FileInput. Otherwise, False.
       
   300         """
       
   301         for field in self.fields.values():
       
   302             if field.widget.needs_multipart_form:
       
   303                 return True
       
   304         return False
       
   305 
       
   306 class Form(BaseForm):
       
   307     "A collection of Fields, plus their associated data."
       
   308     # This is a separate class from BaseForm in order to abstract the way
       
   309     # self.fields is specified. This class (Form) is the one that does the
       
   310     # fancy metaclass stuff purely for the semantic sugar -- it allows one
       
   311     # to define a form using declarative syntax.
       
   312     # BaseForm itself has no way of designating self.fields.
       
   313     __metaclass__ = DeclarativeFieldsMetaclass
       
   314 
       
   315 class BoundField(StrAndUnicode):
       
   316     "A Field plus data"
       
   317     def __init__(self, form, field, name):
       
   318         self.form = form
       
   319         self.field = field
       
   320         self.name = name
       
   321         self.html_name = form.add_prefix(name)
       
   322         self.html_initial_name = form.add_initial_prefix(name)
       
   323         if self.field.label is None:
       
   324             self.label = pretty_name(name)
       
   325         else:
       
   326             self.label = self.field.label
       
   327         self.help_text = field.help_text or ''
       
   328 
       
   329     def __unicode__(self):
       
   330         """Renders this field as an HTML widget."""
       
   331         if self.field.show_hidden_initial:
       
   332             return self.as_widget() + self.as_hidden(only_initial=True)
       
   333         return self.as_widget()
       
   334 
       
   335     def _errors(self):
       
   336         """
       
   337         Returns an ErrorList for this field. Returns an empty ErrorList
       
   338         if there are none.
       
   339         """
       
   340         return self.form.errors.get(self.name, self.form.error_class())
       
   341     errors = property(_errors)
       
   342 
       
   343     def as_widget(self, widget=None, attrs=None, only_initial=False):
       
   344         """
       
   345         Renders the field by rendering the passed widget, adding any HTML
       
   346         attributes passed as attrs.  If no widget is specified, then the
       
   347         field's default widget will be used.
       
   348         """
       
   349         if not widget:
       
   350             widget = self.field.widget
       
   351         attrs = attrs or {}
       
   352         auto_id = self.auto_id
       
   353         if auto_id and 'id' not in attrs and 'id' not in widget.attrs:
       
   354             attrs['id'] = auto_id
       
   355         if not self.form.is_bound:
       
   356             data = self.form.initial.get(self.name, self.field.initial)
       
   357             if callable(data):
       
   358                 data = data()
       
   359         else:
       
   360             data = self.data
       
   361         if not only_initial:
       
   362             name = self.html_name
       
   363         else:
       
   364             name = self.html_initial_name
       
   365         return widget.render(name, data, attrs=attrs)
       
   366         
       
   367     def as_text(self, attrs=None, **kwargs):
       
   368         """
       
   369         Returns a string of HTML for representing this as an <input type="text">.
       
   370         """
       
   371         return self.as_widget(TextInput(), attrs, **kwargs)
       
   372 
       
   373     def as_textarea(self, attrs=None, **kwargs):
       
   374         "Returns a string of HTML for representing this as a <textarea>."
       
   375         return self.as_widget(Textarea(), attrs, **kwargs)
       
   376 
       
   377     def as_hidden(self, attrs=None, **kwargs):
       
   378         """
       
   379         Returns a string of HTML for representing this as an <input type="hidden">.
       
   380         """
       
   381         return self.as_widget(self.field.hidden_widget(), attrs, **kwargs)
       
   382 
       
   383     def _data(self):
       
   384         """
       
   385         Returns the data for this BoundField, or None if it wasn't given.
       
   386         """
       
   387         return self.field.widget.value_from_datadict(self.form.data, self.form.files, self.html_name)
       
   388     data = property(_data)
       
   389 
       
   390     def label_tag(self, contents=None, attrs=None):
       
   391         """
       
   392         Wraps the given contents in a <label>, if the field has an ID attribute.
       
   393         Does not HTML-escape the contents. If contents aren't given, uses the
       
   394         field's HTML-escaped label.
       
   395 
       
   396         If attrs are given, they're used as HTML attributes on the <label> tag.
       
   397         """
       
   398         contents = contents or escape(self.label)
       
   399         widget = self.field.widget
       
   400         id_ = widget.attrs.get('id') or self.auto_id
       
   401         if id_:
       
   402             attrs = attrs and flatatt(attrs) or ''
       
   403             contents = u'<label for="%s"%s>%s</label>' % (widget.id_for_label(id_), attrs, unicode(contents))
       
   404         return mark_safe(contents)
       
   405 
       
   406     def _is_hidden(self):
       
   407         "Returns True if this BoundField's widget is hidden."
       
   408         return self.field.widget.is_hidden
       
   409     is_hidden = property(_is_hidden)
       
   410 
       
   411     def _auto_id(self):
       
   412         """
       
   413         Calculates and returns the ID attribute for this BoundField, if the
       
   414         associated Form has specified auto_id. Returns an empty string otherwise.
       
   415         """
       
   416         auto_id = self.form.auto_id
       
   417         if auto_id and '%s' in smart_unicode(auto_id):
       
   418             return smart_unicode(auto_id) % self.html_name
       
   419         elif auto_id:
       
   420             return self.html_name
       
   421         return ''
       
   422     auto_id = property(_auto_id)