web/lib/django/forms/forms.py
changeset 0 0d40e90630ef
child 29 cc9b7e14412b
equal deleted inserted replaced
-1:000000000000 0:0d40e90630ef
       
     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 conditional_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([conditional_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 = conditional_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 _raw_value(self, fieldname):
       
   209         """
       
   210         Returns the raw_value for a particular field name. This is just a
       
   211         convenient wrapper around widget.value_from_datadict.
       
   212         """
       
   213         field = self.fields[fieldname]
       
   214         prefix = self.add_prefix(fieldname)
       
   215         return field.widget.value_from_datadict(self.data, self.files, prefix)
       
   216 
       
   217     def full_clean(self):
       
   218         """
       
   219         Cleans all of self.data and populates self._errors and
       
   220         self.cleaned_data.
       
   221         """
       
   222         self._errors = ErrorDict()
       
   223         if not self.is_bound: # Stop further processing.
       
   224             return
       
   225         self.cleaned_data = {}
       
   226         # If the form is permitted to be empty, and none of the form data has
       
   227         # changed from the initial data, short circuit any validation.
       
   228         if self.empty_permitted and not self.has_changed():
       
   229             return
       
   230         for name, field in self.fields.items():
       
   231             # value_from_datadict() gets the data from the data dictionaries.
       
   232             # Each widget type knows how to retrieve its own data, because some
       
   233             # widgets split data over several HTML fields.
       
   234             value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
       
   235             try:
       
   236                 if isinstance(field, FileField):
       
   237                     initial = self.initial.get(name, field.initial)
       
   238                     value = field.clean(value, initial)
       
   239                 else:
       
   240                     value = field.clean(value)
       
   241                 self.cleaned_data[name] = value
       
   242                 if hasattr(self, 'clean_%s' % name):
       
   243                     value = getattr(self, 'clean_%s' % name)()
       
   244                     self.cleaned_data[name] = value
       
   245             except ValidationError, e:
       
   246                 self._errors[name] = self.error_class(e.messages)
       
   247                 if name in self.cleaned_data:
       
   248                     del self.cleaned_data[name]
       
   249         try:
       
   250             self.cleaned_data = self.clean()
       
   251         except ValidationError, e:
       
   252             self._errors[NON_FIELD_ERRORS] = self.error_class(e.messages)
       
   253         if self._errors:
       
   254             delattr(self, 'cleaned_data')
       
   255 
       
   256     def clean(self):
       
   257         """
       
   258         Hook for doing any extra form-wide cleaning after Field.clean() been
       
   259         called on every field. Any ValidationError raised by this method will
       
   260         not be associated with a particular field; it will have a special-case
       
   261         association with the field named '__all__'.
       
   262         """
       
   263         return self.cleaned_data
       
   264 
       
   265     def has_changed(self):
       
   266         """
       
   267         Returns True if data differs from initial.
       
   268         """
       
   269         return bool(self.changed_data)
       
   270 
       
   271     def _get_changed_data(self):
       
   272         if self._changed_data is None:
       
   273             self._changed_data = []
       
   274             # XXX: For now we're asking the individual widgets whether or not the
       
   275             # data has changed. It would probably be more efficient to hash the
       
   276             # initial data, store it in a hidden field, and compare a hash of the
       
   277             # submitted data, but we'd need a way to easily get the string value
       
   278             # for a given field. Right now, that logic is embedded in the render
       
   279             # method of each widget.
       
   280             for name, field in self.fields.items():
       
   281                 prefixed_name = self.add_prefix(name)
       
   282                 data_value = field.widget.value_from_datadict(self.data, self.files, prefixed_name)
       
   283                 if not field.show_hidden_initial:
       
   284                     initial_value = self.initial.get(name, field.initial)
       
   285                 else:
       
   286                     initial_prefixed_name = self.add_initial_prefix(name)
       
   287                     hidden_widget = field.hidden_widget()
       
   288                     initial_value = hidden_widget.value_from_datadict(
       
   289                         self.data, self.files, initial_prefixed_name)
       
   290                 if field.widget._has_changed(initial_value, data_value):
       
   291                     self._changed_data.append(name)
       
   292         return self._changed_data
       
   293     changed_data = property(_get_changed_data)
       
   294 
       
   295     def _get_media(self):
       
   296         """
       
   297         Provide a description of all media required to render the widgets on this form
       
   298         """
       
   299         media = Media()
       
   300         for field in self.fields.values():
       
   301             media = media + field.widget.media
       
   302         return media
       
   303     media = property(_get_media)
       
   304 
       
   305     def is_multipart(self):
       
   306         """
       
   307         Returns True if the form needs to be multipart-encrypted, i.e. it has
       
   308         FileInput. Otherwise, False.
       
   309         """
       
   310         for field in self.fields.values():
       
   311             if field.widget.needs_multipart_form:
       
   312                 return True
       
   313         return False
       
   314 
       
   315     def hidden_fields(self):
       
   316         """
       
   317         Returns a list of all the BoundField objects that are hidden fields.
       
   318         Useful for manual form layout in templates.
       
   319         """
       
   320         return [field for field in self if field.is_hidden]
       
   321 
       
   322     def visible_fields(self):
       
   323         """
       
   324         Returns a list of BoundField objects that aren't hidden fields.
       
   325         The opposite of the hidden_fields() method.
       
   326         """
       
   327         return [field for field in self if not field.is_hidden]
       
   328 
       
   329 class Form(BaseForm):
       
   330     "A collection of Fields, plus their associated data."
       
   331     # This is a separate class from BaseForm in order to abstract the way
       
   332     # self.fields is specified. This class (Form) is the one that does the
       
   333     # fancy metaclass stuff purely for the semantic sugar -- it allows one
       
   334     # to define a form using declarative syntax.
       
   335     # BaseForm itself has no way of designating self.fields.
       
   336     __metaclass__ = DeclarativeFieldsMetaclass
       
   337 
       
   338 class BoundField(StrAndUnicode):
       
   339     "A Field plus data"
       
   340     def __init__(self, form, field, name):
       
   341         self.form = form
       
   342         self.field = field
       
   343         self.name = name
       
   344         self.html_name = form.add_prefix(name)
       
   345         self.html_initial_name = form.add_initial_prefix(name)
       
   346         if self.field.label is None:
       
   347             self.label = pretty_name(name)
       
   348         else:
       
   349             self.label = self.field.label
       
   350         self.help_text = field.help_text or ''
       
   351 
       
   352     def __unicode__(self):
       
   353         """Renders this field as an HTML widget."""
       
   354         if self.field.show_hidden_initial:
       
   355             return self.as_widget() + self.as_hidden(only_initial=True)
       
   356         return self.as_widget()
       
   357 
       
   358     def _errors(self):
       
   359         """
       
   360         Returns an ErrorList for this field. Returns an empty ErrorList
       
   361         if there are none.
       
   362         """
       
   363         return self.form.errors.get(self.name, self.form.error_class())
       
   364     errors = property(_errors)
       
   365 
       
   366     def as_widget(self, widget=None, attrs=None, only_initial=False):
       
   367         """
       
   368         Renders the field by rendering the passed widget, adding any HTML
       
   369         attributes passed as attrs.  If no widget is specified, then the
       
   370         field's default widget will be used.
       
   371         """
       
   372         if not widget:
       
   373             widget = self.field.widget
       
   374         attrs = attrs or {}
       
   375         auto_id = self.auto_id
       
   376         if auto_id and 'id' not in attrs and 'id' not in widget.attrs:
       
   377             attrs['id'] = auto_id
       
   378         if not self.form.is_bound:
       
   379             data = self.form.initial.get(self.name, self.field.initial)
       
   380             if callable(data):
       
   381                 data = data()
       
   382         else:
       
   383             if isinstance(self.field, FileField) and self.data is None:
       
   384                 data = self.form.initial.get(self.name, self.field.initial)
       
   385             else:
       
   386                 data = self.data
       
   387         if not only_initial:
       
   388             name = self.html_name
       
   389         else:
       
   390             name = self.html_initial_name
       
   391         return widget.render(name, data, attrs=attrs)
       
   392 
       
   393     def as_text(self, attrs=None, **kwargs):
       
   394         """
       
   395         Returns a string of HTML for representing this as an <input type="text">.
       
   396         """
       
   397         return self.as_widget(TextInput(), attrs, **kwargs)
       
   398 
       
   399     def as_textarea(self, attrs=None, **kwargs):
       
   400         "Returns a string of HTML for representing this as a <textarea>."
       
   401         return self.as_widget(Textarea(), attrs, **kwargs)
       
   402 
       
   403     def as_hidden(self, attrs=None, **kwargs):
       
   404         """
       
   405         Returns a string of HTML for representing this as an <input type="hidden">.
       
   406         """
       
   407         return self.as_widget(self.field.hidden_widget(), attrs, **kwargs)
       
   408 
       
   409     def _data(self):
       
   410         """
       
   411         Returns the data for this BoundField, or None if it wasn't given.
       
   412         """
       
   413         return self.field.widget.value_from_datadict(self.form.data, self.form.files, self.html_name)
       
   414     data = property(_data)
       
   415 
       
   416     def label_tag(self, contents=None, attrs=None):
       
   417         """
       
   418         Wraps the given contents in a <label>, if the field has an ID attribute.
       
   419         Does not HTML-escape the contents. If contents aren't given, uses the
       
   420         field's HTML-escaped label.
       
   421 
       
   422         If attrs are given, they're used as HTML attributes on the <label> tag.
       
   423         """
       
   424         contents = contents or conditional_escape(self.label)
       
   425         widget = self.field.widget
       
   426         id_ = widget.attrs.get('id') or self.auto_id
       
   427         if id_:
       
   428             attrs = attrs and flatatt(attrs) or ''
       
   429             contents = u'<label for="%s"%s>%s</label>' % (widget.id_for_label(id_), attrs, unicode(contents))
       
   430         return mark_safe(contents)
       
   431 
       
   432     def _is_hidden(self):
       
   433         "Returns True if this BoundField's widget is hidden."
       
   434         return self.field.widget.is_hidden
       
   435     is_hidden = property(_is_hidden)
       
   436 
       
   437     def _auto_id(self):
       
   438         """
       
   439         Calculates and returns the ID attribute for this BoundField, if the
       
   440         associated Form has specified auto_id. Returns an empty string otherwise.
       
   441         """
       
   442         auto_id = self.form.auto_id
       
   443         if auto_id and '%s' in smart_unicode(auto_id):
       
   444             return smart_unicode(auto_id) % self.html_name
       
   445         elif auto_id:
       
   446             return self.html_name
       
   447         return ''
       
   448     auto_id = property(_auto_id)