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