Permalink
Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.
Sign up| """ | |
| Form classes | |
| """ | |
| import copy | |
| from collections import OrderedDict | |
| from django.core.exceptions import NON_FIELD_ERRORS, ValidationError | |
| # BoundField is imported for backwards compatibility in Django 1.9 | |
| from django.forms.boundfield import BoundField # NOQA | |
| from django.forms.fields import Field, FileField | |
| # pretty_name is imported for backwards compatibility in Django 1.9 | |
| from django.forms.utils import ErrorDict, ErrorList, pretty_name # NOQA | |
| from django.forms.widgets import Media, MediaDefiningClass | |
| from django.utils.functional import cached_property | |
| from django.utils.html import conditional_escape, html_safe | |
| from django.utils.safestring import mark_safe | |
| from django.utils.translation import gettext as _ | |
| from .renderers import get_default_renderer | |
| __all__ = ('BaseForm', 'Form') | |
| class DeclarativeFieldsMetaclass(MediaDefiningClass): | |
| """Collect Fields declared on the base classes.""" | |
| def __new__(mcs, name, bases, attrs): | |
| # Collect fields from current class. | |
| current_fields = [] | |
| for key, value in list(attrs.items()): | |
| if isinstance(value, Field): | |
| current_fields.append((key, value)) | |
| attrs.pop(key) | |
| attrs['declared_fields'] = OrderedDict(current_fields) | |
| new_class = super(DeclarativeFieldsMetaclass, mcs).__new__(mcs, name, bases, attrs) | |
| # Walk through the MRO. | |
| declared_fields = OrderedDict() | |
| for base in reversed(new_class.__mro__): | |
| # Collect fields from base class. | |
| if hasattr(base, 'declared_fields'): | |
| declared_fields.update(base.declared_fields) | |
| # Field shadowing. | |
| for attr, value in base.__dict__.items(): | |
| if value is None and attr in declared_fields: | |
| declared_fields.pop(attr) | |
| new_class.base_fields = declared_fields | |
| new_class.declared_fields = declared_fields | |
| return new_class | |
| @classmethod | |
| def __prepare__(metacls, name, bases, **kwds): | |
| # Remember the order in which form fields are defined. | |
| return OrderedDict() | |
| @html_safe | |
| class BaseForm: | |
| """ | |
| The main implementation of all the Form logic. Note that this class is | |
| different than Form. See the comments by the Form class for more info. Any | |
| improvements to the form API should be made to this class, not to the Form | |
| class. | |
| """ | |
| default_renderer = None | |
| field_order = None | |
| prefix = None | |
| use_required_attribute = True | |
| def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, | |
| initial=None, error_class=ErrorList, label_suffix=None, | |
| empty_permitted=False, field_order=None, use_required_attribute=None, renderer=None): | |
| self.is_bound = data is not None or files is not None | |
| self.data = {} if data is None else data | |
| self.files = {} if files is None else files | |
| self.auto_id = auto_id | |
| if prefix is not None: | |
| self.prefix = prefix | |
| self.initial = initial or {} | |
| self.error_class = error_class | |
| # Translators: This is the default suffix added to form field labels | |
| self.label_suffix = label_suffix if label_suffix is not None else _(':') | |
| self.empty_permitted = empty_permitted | |
| self._errors = None # Stores the errors after clean() has been called. | |
| # The base_fields class attribute is the *class-wide* definition of | |
| # fields. Because a particular *instance* of the class might want to | |
| # alter self.fields, we create self.fields here by copying base_fields. | |
| # Instances should always modify self.fields; they should not modify | |
| # self.base_fields. | |
| self.fields = copy.deepcopy(self.base_fields) | |
| self._bound_fields_cache = {} | |
| self.order_fields(self.field_order if field_order is None else field_order) | |
| if use_required_attribute is not None: | |
| self.use_required_attribute = use_required_attribute | |
| if self.empty_permitted and self.use_required_attribute: | |
| raise ValueError( | |
| 'The empty_permitted and use_required_attribute arguments may ' | |
| 'not both be True.' | |
| ) | |
| # Initialize form renderer. Use a global default if not specified | |
| # either as an argument or as self.default_renderer. | |
| if renderer is None: | |
| if self.default_renderer is None: | |
| renderer = get_default_renderer() | |
| else: | |
| renderer = self.default_renderer | |
| if isinstance(self.default_renderer, type): | |
| renderer = renderer() | |
| self.renderer = renderer | |
| def order_fields(self, field_order): | |
| """ | |
| Rearrange the fields according to field_order. | |
| field_order is a list of field names specifying the order. Append fields | |
| not included in the list in the default order for backward compatibility | |
| with subclasses not overriding field_order. If field_order is None, | |
| keep all fields in the order defined in the class. Ignore unknown | |
| fields in field_order to allow disabling fields in form subclasses | |
| without redefining ordering. | |
| """ | |
| if field_order is None: | |
| return | |
| fields = OrderedDict() | |
| for key in field_order: | |
| try: | |
| fields[key] = self.fields.pop(key) | |
| except KeyError: # ignore unknown fields | |
| pass | |
| fields.update(self.fields) # add remaining fields in original order | |
| self.fields = fields | |
| def __str__(self): | |
| return self.as_table() | |
| def __repr__(self): | |
| if self._errors is None: | |
| is_valid = "Unknown" | |
| else: | |
| is_valid = self.is_bound and not self._errors | |
| return '<%(cls)s bound=%(bound)s, valid=%(valid)s, fields=(%(fields)s)>' % { | |
| 'cls': self.__class__.__name__, | |
| 'bound': self.is_bound, | |
| 'valid': is_valid, | |
| 'fields': ';'.join(self.fields), | |
| } | |
| def __iter__(self): | |
| for name in self.fields: | |
| yield self[name] | |
| def __getitem__(self, name): | |
| """Return a BoundField with the given name.""" | |
| try: | |
| field = self.fields[name] | |
| except KeyError: | |
| raise KeyError( | |
| "Key '%s' not found in '%s'. Choices are: %s." % ( | |
| name, | |
| self.__class__.__name__, | |
| ', '.join(sorted(f for f in self.fields)), | |
| ) | |
| ) | |
| if name not in self._bound_fields_cache: | |
| self._bound_fields_cache[name] = field.get_bound_field(self, name) | |
| return self._bound_fields_cache[name] | |
| @property | |
| def errors(self): | |
| """Return an ErrorDict for the data provided for the form.""" | |
| if self._errors is None: | |
| self.full_clean() | |
| return self._errors | |
| def is_valid(self): | |
| """Return True if the form has no errors, or False otherwise.""" | |
| return self.is_bound and not self.errors | |
| def add_prefix(self, field_name): | |
| """ | |
| Return the field name with a prefix appended, if this Form has a | |
| prefix set. | |
| Subclasses may wish to override. | |
| """ | |
| return '%s-%s' % (self.prefix, field_name) if self.prefix else field_name | |
| def add_initial_prefix(self, field_name): | |
| """Add a 'initial' prefix for checking dynamic initial values.""" | |
| return 'initial-%s' % self.add_prefix(field_name) | |
| def _html_output(self, normal_row, error_row, row_ender, help_text_html, errors_on_separate_row): | |
| "Output HTML. Used by as_table(), as_ul(), as_p()." | |
| top_errors = self.non_field_errors() # Errors that should be displayed above all fields. | |
| output, hidden_fields = [], [] | |
| for name, field in self.fields.items(): | |
| html_class_attr = '' | |
| bf = self[name] | |
| bf_errors = self.error_class(bf.errors) | |
| if bf.is_hidden: | |
| if bf_errors: | |
| top_errors.extend( | |
| [_('(Hidden field %(name)s) %(error)s') % {'name': name, 'error': str(e)} | |
| for e in bf_errors]) | |
| hidden_fields.append(str(bf)) | |
| else: | |
| # Create a 'class="..."' attribute if the row should have any | |
| # CSS classes applied. | |
| css_classes = bf.css_classes() | |
| if css_classes: | |
| html_class_attr = ' class="%s"' % css_classes | |
| if errors_on_separate_row and bf_errors: | |
| output.append(error_row % str(bf_errors)) | |
| if bf.label: | |
| label = conditional_escape(bf.label) | |
| label = bf.label_tag(label) or '' | |
| else: | |
| label = '' | |
| if field.help_text: | |
| help_text = help_text_html % field.help_text | |
| else: | |
| help_text = '' | |
| output.append(normal_row % { | |
| 'errors': bf_errors, | |
| 'label': label, | |
| 'field': bf, | |
| 'help_text': help_text, | |
| 'html_class_attr': html_class_attr, | |
| 'css_classes': css_classes, | |
| 'field_name': bf.html_name, | |
| }) | |
| if top_errors: | |
| output.insert(0, error_row % top_errors) | |
| if hidden_fields: # Insert any hidden fields in the last row. | |
| str_hidden = ''.join(hidden_fields) | |
| if output: | |
| last_row = output[-1] | |
| # Chop off the trailing row_ender (e.g. '</td></tr>') and | |
| # insert the hidden fields. | |
| if not last_row.endswith(row_ender): | |
| # This can happen in the as_p() case (and possibly others | |
| # that users write): if there are only top errors, we may | |
| # not be able to conscript the last row for our purposes, | |
| # so insert a new, empty row. | |
| last_row = (normal_row % { | |
| 'errors': '', | |
| 'label': '', | |
| 'field': '', | |
| 'help_text': '', | |
| 'html_class_attr': html_class_attr, | |
| 'css_classes': '', | |
| 'field_name': '', | |
| }) | |
| output.append(last_row) | |
| output[-1] = last_row[:-len(row_ender)] + str_hidden + row_ender | |
| else: | |
| # If there aren't any rows in the output, just append the | |
| # hidden fields. | |
| output.append(str_hidden) | |
| return mark_safe('\n'.join(output)) | |
| def as_table(self): | |
| "Return this form rendered as HTML <tr>s -- excluding the <table></table>." | |
| return self._html_output( | |
| normal_row='<tr%(html_class_attr)s><th>%(label)s</th><td>%(errors)s%(field)s%(help_text)s</td></tr>', | |
| error_row='<tr><td colspan="2">%s</td></tr>', | |
| row_ender='</td></tr>', | |
| help_text_html='<br><span class="helptext">%s</span>', | |
| errors_on_separate_row=False, | |
| ) | |
| def as_ul(self): | |
| "Return this form rendered as HTML <li>s -- excluding the <ul></ul>." | |
| return self._html_output( | |
| normal_row='<li%(html_class_attr)s>%(errors)s%(label)s %(field)s%(help_text)s</li>', | |
| error_row='<li>%s</li>', | |
| row_ender='</li>', | |
| help_text_html=' <span class="helptext">%s</span>', | |
| errors_on_separate_row=False, | |
| ) | |
| def as_p(self): | |
| "Return this form rendered as HTML <p>s." | |
| return self._html_output( | |
| normal_row='<p%(html_class_attr)s>%(label)s %(field)s%(help_text)s</p>', | |
| error_row='%s', | |
| row_ender='</p>', | |
| help_text_html=' <span class="helptext">%s</span>', | |
| errors_on_separate_row=True, | |
| ) | |
| def non_field_errors(self): | |
| """ | |
| Return an ErrorList of errors that aren't associated with a particular | |
| field -- i.e., from Form.clean(). Return an empty ErrorList if there | |
| are none. | |
| """ | |
| return self.errors.get(NON_FIELD_ERRORS, self.error_class(error_class='nonfield')) | |
| def add_error(self, field, error): | |
| """ | |
| Update the content of `self._errors`. | |
| The `field` argument is the name of the field to which the errors | |
| should be added. If it's None, treat the errors as NON_FIELD_ERRORS. | |
| The `error` argument can be a single error, a list of errors, or a | |
| dictionary that maps field names to lists of errors. An "error" can be | |
| either a simple string or an instance of ValidationError with its | |
| message attribute set and a "list or dictionary" can be an actual | |
| `list` or `dict` or an instance of ValidationError with its | |
| `error_list` or `error_dict` attribute set. | |
| If `error` is a dictionary, the `field` argument *must* be None and | |
| errors will be added to the fields that correspond to the keys of the | |
| dictionary. | |
| """ | |
| if not isinstance(error, ValidationError): | |
| # Normalize to ValidationError and let its constructor | |
| # do the hard work of making sense of the input. | |
| error = ValidationError(error) | |
| if hasattr(error, 'error_dict'): | |
| if field is not None: | |
| raise TypeError( | |
| "The argument `field` must be `None` when the `error` " | |
| "argument contains errors for multiple fields." | |
| ) | |
| else: | |
| error = error.error_dict | |
| else: | |
| error = {field or NON_FIELD_ERRORS: error.error_list} | |
| for field, error_list in error.items(): | |
| if field not in self.errors: | |
| if field != NON_FIELD_ERRORS and field not in self.fields: | |
| raise ValueError( | |
| "'%s' has no field named '%s'." % (self.__class__.__name__, field)) | |
| if field == NON_FIELD_ERRORS: | |
| self._errors[field] = self.error_class(error_class='nonfield') | |
| else: | |
| self._errors[field] = self.error_class() | |
| self._errors[field].extend(error_list) | |
| if field in self.cleaned_data: | |
| del self.cleaned_data[field] | |
| def has_error(self, field, code=None): | |
| return field in self.errors and ( | |
| code is None or | |
| any(error.code == code for error in self.errors.as_data()[field]) | |
| ) | |
| def full_clean(self): | |
| """ | |
| Clean all of self.data and populate self._errors and self.cleaned_data. | |
| """ | |
| self._errors = ErrorDict() | |
| if not self.is_bound: # Stop further processing. | |
| return | |
| self.cleaned_data = {} | |
| # If the form is permitted to be empty, and none of the form data has | |
| # changed from the initial data, short circuit any validation. | |
| if self.empty_permitted and not self.has_changed(): | |
| return | |
| self._clean_fields() | |
| self._clean_form() | |
| self._post_clean() | |
| def _clean_fields(self): | |
| for name, field in self.fields.items(): | |
| # value_from_datadict() gets the data from the data dictionaries. | |
| # Each widget type knows how to retrieve its own data, because some | |
| # widgets split data over several HTML fields. | |
| if field.disabled: | |
| value = self.get_initial_for_field(field, name) | |
| else: | |
| value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name)) | |
| try: | |
| if isinstance(field, FileField): | |
| initial = self.get_initial_for_field(field, name) | |
| value = field.clean(value, initial) | |
| else: | |
| value = field.clean(value) | |
| self.cleaned_data[name] = value | |
| if hasattr(self, 'clean_%s' % name): | |
| value = getattr(self, 'clean_%s' % name)() | |
| self.cleaned_data[name] = value | |
| except ValidationError as e: | |
| self.add_error(name, e) | |
| def _clean_form(self): | |
| try: | |
| cleaned_data = self.clean() | |
| except ValidationError as e: | |
| self.add_error(None, e) | |
| else: | |
| if cleaned_data is not None: | |
| self.cleaned_data = cleaned_data | |
| def _post_clean(self): | |
| """ | |
| An internal hook for performing additional cleaning after form cleaning | |
| is complete. Used for model validation in model forms. | |
| """ | |
| pass | |
| def clean(self): | |
| """ | |
| Hook for doing any extra form-wide cleaning after Field.clean() has been | |
| called on every field. Any ValidationError raised by this method will | |
| not be associated with a particular field; it will have a special-case | |
| association with the field named '__all__'. | |
| """ | |
| return self.cleaned_data | |
| def has_changed(self): | |
| """Return True if data differs from initial.""" | |
| return bool(self.changed_data) | |
| @cached_property | |
| def changed_data(self): | |
| data = [] | |
| for name, field in self.fields.items(): | |
| prefixed_name = self.add_prefix(name) | |
| data_value = field.widget.value_from_datadict(self.data, self.files, prefixed_name) | |
| if not field.show_hidden_initial: | |
| # Use the BoundField's initial as this is the value passed to | |
| # the widget. | |
| initial_value = self[name].initial | |
| else: | |
| initial_prefixed_name = self.add_initial_prefix(name) | |
| hidden_widget = field.hidden_widget() | |
| try: | |
| initial_value = field.to_python(hidden_widget.value_from_datadict( | |
| self.data, self.files, initial_prefixed_name)) | |
| except ValidationError: | |
| # Always assume data has changed if validation fails. | |
| data.append(name) | |
| continue | |
| if field.has_changed(initial_value, data_value): | |
| data.append(name) | |
| return data | |
| @property | |
| def media(self): | |
| """Return all media required to render the widgets on this form.""" | |
| media = Media() | |
| for field in self.fields.values(): | |
| media = media + field.widget.media | |
| return media | |
| def is_multipart(self): | |
| """ | |
| Return True if the form needs to be multipart-encoded, i.e. it has | |
| FileInput, or False otherwise. | |
| """ | |
| return any(field.widget.needs_multipart_form for field in self.fields.values()) | |
| def hidden_fields(self): | |
| """ | |
| Return a list of all the BoundField objects that are hidden fields. | |
| Useful for manual form layout in templates. | |
| """ | |
| return [field for field in self if field.is_hidden] | |
| def visible_fields(self): | |
| """ | |
| Return a list of BoundField objects that aren't hidden fields. | |
| The opposite of the hidden_fields() method. | |
| """ | |
| return [field for field in self if not field.is_hidden] | |
| def get_initial_for_field(self, field, field_name): | |
| """ | |
| Return initial data for field on form. Use initial data from the form | |
| or the field, in that order. Evaluate callable values. | |
| """ | |
| value = self.initial.get(field_name, field.initial) | |
| if callable(value): | |
| value = value() | |
| return value | |
| class Form(BaseForm, metaclass=DeclarativeFieldsMetaclass): | |
| "A collection of Fields, plus their associated data." | |
| # This is a separate class from BaseForm in order to abstract the way | |
| # self.fields is specified. This class (Form) is the one that does the | |
| # fancy metaclass stuff purely for the semantic sugar -- it allows one | |
| # to define a form using declarative syntax. | |
| # BaseForm itself has no way of designating self.fields. |