|
- from __future__ import unicode_literals
-
- import datetime
-
- from django.forms.utils import flatatt, pretty_name
- from django.forms.widgets import Textarea, TextInput
- from django.utils import six
- from django.utils.encoding import (
- force_text, python_2_unicode_compatible, smart_text,
- )
- from django.utils.html import conditional_escape, format_html, html_safe
- from django.utils.safestring import mark_safe
- from django.utils.translation import ugettext_lazy as _
-
- __all__ = ('BoundField',)
-
-
- UNSET = object()
-
-
- @html_safe
- @python_2_unicode_compatible
- class BoundField(object):
- "A Field plus data"
- def __init__(self, form, field, name):
- self.form = form
- self.field = field
- self.name = name
- self.html_name = form.add_prefix(name)
- self.html_initial_name = form.add_initial_prefix(name)
- self.html_initial_id = form.add_initial_prefix(self.auto_id)
- if self.field.label is None:
- self.label = pretty_name(name)
- else:
- self.label = self.field.label
- self.help_text = field.help_text or ''
- self._initial_value = UNSET
-
- def __str__(self):
- """Renders this field as an HTML widget."""
- if self.field.show_hidden_initial:
- return self.as_widget() + self.as_hidden(only_initial=True)
- return self.as_widget()
-
- def __iter__(self):
- """
- Yields rendered strings that comprise all widgets in this BoundField.
-
- This really is only useful for RadioSelect widgets, so that you can
- iterate over individual radio buttons in a template.
- """
- id_ = self.field.widget.attrs.get('id') or self.auto_id
- attrs = {'id': id_} if id_ else {}
- for subwidget in self.field.widget.subwidgets(self.html_name, self.value(), attrs):
- yield subwidget
-
- def __len__(self):
- return len(list(self.__iter__()))
-
- def __getitem__(self, idx):
- # Prevent unnecessary reevaluation when accessing BoundField's attrs
- # from templates.
- if not isinstance(idx, six.integer_types + (slice,)):
- raise TypeError
- return list(self.__iter__())[idx]
-
- @property
- def errors(self):
- """
- Returns an ErrorList for this field. Returns an empty ErrorList
- if there are none.
- """
- return self.form.errors.get(self.name, self.form.error_class())
-
- def as_widget(self, widget=None, attrs=None, only_initial=False):
- """
- Renders the field by rendering the passed widget, adding any HTML
- attributes passed as attrs. If no widget is specified, then the
- field's default widget will be used.
- """
- if not widget:
- widget = self.field.widget
-
- if self.field.localize:
- widget.is_localized = True
-
- attrs = attrs or {}
- if self.field.disabled:
- attrs['disabled'] = True
- auto_id = self.auto_id
- if auto_id and 'id' not in attrs and 'id' not in widget.attrs:
- if not only_initial:
- attrs['id'] = auto_id
- else:
- attrs['id'] = self.html_initial_id
-
- if not only_initial:
- name = self.html_name
- else:
- name = self.html_initial_name
- return force_text(widget.render(name, self.value(), attrs=attrs))
-
- def as_text(self, attrs=None, **kwargs):
- """
- Returns a string of HTML for representing this as an <input type="text">.
- """
- return self.as_widget(TextInput(), attrs, **kwargs)
-
- def as_textarea(self, attrs=None, **kwargs):
- "Returns a string of HTML for representing this as a <textarea>."
- return self.as_widget(Textarea(), attrs, **kwargs)
-
- def as_hidden(self, attrs=None, **kwargs):
- """
- Returns a string of HTML for representing this as an <input type="hidden">.
- """
- return self.as_widget(self.field.hidden_widget(), attrs, **kwargs)
-
- @property
- def data(self):
- """
- Returns the data for this BoundField, or None if it wasn't given.
- """
- return self.field.widget.value_from_datadict(self.form.data, self.form.files, self.html_name)
-
- def value(self):
- """
- Returns the value for this BoundField, using the initial value if
- the form is not bound or the data otherwise.
- """
- if not self.form.is_bound:
- data = self.form.initial.get(self.name, self.field.initial)
- if callable(data):
- if self._initial_value is not UNSET:
- data = self._initial_value
- else:
- data = data()
- # If this is an auto-generated default date, nix the
- # microseconds for standardized handling. See #22502.
- if (isinstance(data, (datetime.datetime, datetime.time)) and
- not self.field.widget.supports_microseconds):
- data = data.replace(microsecond=0)
- self._initial_value = data
- else:
- data = self.field.bound_data(
- self.data, self.form.initial.get(self.name, self.field.initial)
- )
- return self.field.prepare_value(data)
-
- def label_tag(self, contents=None, attrs=None, label_suffix=None):
- """
- Wraps the given contents in a <label>, if the field has an ID attribute.
- contents should be 'mark_safe'd to avoid HTML escaping. If contents
- aren't given, uses the field's HTML-escaped label.
-
- If attrs are given, they're used as HTML attributes on the <label> tag.
-
- label_suffix allows overriding the form's label_suffix.
- """
- contents = contents or self.label
- if label_suffix is None:
- label_suffix = (self.field.label_suffix if self.field.label_suffix is not None
- else self.form.label_suffix)
- # Only add the suffix if the label does not end in punctuation.
- # Translators: If found as last label character, these punctuation
- # characters will prevent the default label_suffix to be appended to the label
- if label_suffix and contents and contents[-1] not in _(':?.!'):
- contents = format_html('{}{}', contents, label_suffix)
- widget = self.field.widget
- id_ = widget.attrs.get('id') or self.auto_id
- if id_:
- id_for_label = widget.id_for_label(id_)
- if id_for_label:
- attrs = dict(attrs or {}, **{'for': id_for_label})
- if self.field.required and hasattr(self.form, 'required_css_class'):
- attrs = attrs or {}
- if 'class' in attrs:
- attrs['class'] += ' ' + self.form.required_css_class
- else:
- attrs['class'] = self.form.required_css_class
- attrs = flatatt(attrs) if attrs else ''
- contents = format_html('<label{}>{}</label>', attrs, contents)
- else:
- contents = conditional_escape(contents)
- return mark_safe(contents)
-
- def css_classes(self, extra_classes=None):
- """
- Returns a string of space-separated CSS classes for this field.
- """
- if hasattr(extra_classes, 'split'):
- extra_classes = extra_classes.split()
- extra_classes = set(extra_classes or [])
- if self.errors and hasattr(self.form, 'error_css_class'):
- extra_classes.add(self.form.error_css_class)
- if self.field.required and hasattr(self.form, 'required_css_class'):
- extra_classes.add(self.form.required_css_class)
- return ' '.join(extra_classes)
-
- @property
- def is_hidden(self):
- "Returns True if this BoundField's widget is hidden."
- return self.field.widget.is_hidden
-
- @property
- def auto_id(self):
- """
- Calculates and returns the ID attribute for this BoundField, if the
- associated Form has specified auto_id. Returns an empty string otherwise.
- """
- auto_id = self.form.auto_id
- if auto_id and '%s' in smart_text(auto_id):
- return smart_text(auto_id) % self.html_name
- elif auto_id:
- return self.html_name
- return ''
-
- @property
- def id_for_label(self):
- """
- Wrapper around the field widget's `id_for_label` method.
- Useful, for example, for focusing on this field regardless of whether
- it has a single widget or a MultiWidget.
- """
- widget = self.field.widget
- id_ = widget.attrs.get('id') or self.auto_id
- return widget.id_for_label(id_)
|