mirror of
				https://github.com/django/django.git
				synced 2025-10-31 01:25:32 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			304 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			304 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| from __future__ import unicode_literals
 | |
| 
 | |
| import datetime
 | |
| import warnings
 | |
| 
 | |
| from django.forms.utils import flatatt, pretty_name
 | |
| from django.forms.widgets import Textarea, TextInput
 | |
| from django.utils import six
 | |
| from django.utils.deprecation import RemovedInDjango21Warning
 | |
| from django.utils.encoding import force_text, python_2_unicode_compatible
 | |
| from django.utils.functional import cached_property
 | |
| from django.utils.html import conditional_escape, format_html, html_safe
 | |
| from django.utils.inspect import func_accepts_kwargs, func_supports_parameter
 | |
| from django.utils.safestring import mark_safe
 | |
| from django.utils.translation import ugettext_lazy as _
 | |
| 
 | |
| __all__ = ('BoundField',)
 | |
| 
 | |
| 
 | |
| @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 ''
 | |
| 
 | |
|     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()
 | |
| 
 | |
|     @cached_property
 | |
|     def subwidgets(self):
 | |
|         """
 | |
|         Most widgets yield a single subwidget, but others like RadioSelect and
 | |
|         CheckboxSelectMultiple produce one subwidget for each choice.
 | |
| 
 | |
|         This property is cached so that only one database query occurs when
 | |
|         rendering ModelChoiceFields.
 | |
|         """
 | |
|         id_ = self.field.widget.attrs.get('id') or self.auto_id
 | |
|         attrs = {'id': id_} if id_ else {}
 | |
|         attrs = self.build_widget_attrs(attrs)
 | |
|         return list(
 | |
|             BoundWidget(self.field.widget, widget, self.form.renderer)
 | |
|             for widget in self.field.widget.subwidgets(self.html_name, self.value(), attrs=attrs)
 | |
|         )
 | |
| 
 | |
|     def __bool__(self):
 | |
|         # BoundField evaluates to True even if it doesn't have subwidgets.
 | |
|         return True
 | |
| 
 | |
|     def __nonzero__(self):      # Python 2 compatibility
 | |
|         return type(self).__bool__(self)
 | |
| 
 | |
|     def __iter__(self):
 | |
|         return iter(self.subwidgets)
 | |
| 
 | |
|     def __len__(self):
 | |
|         return len(self.subwidgets)
 | |
| 
 | |
|     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 self.subwidgets[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 {}
 | |
|         attrs = self.build_widget_attrs(attrs, widget)
 | |
|         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
 | |
| 
 | |
|         kwargs = {}
 | |
|         if func_supports_parameter(widget.render, 'renderer') or func_accepts_kwargs(widget.render):
 | |
|             kwargs['renderer'] = self.form.renderer
 | |
|         else:
 | |
|             warnings.warn(
 | |
|                 'Add the `renderer` argument to the render() method of %s. '
 | |
|                 'It will be mandatory in Django 2.1.' % widget.__class__,
 | |
|                 RemovedInDjango21Warning, stacklevel=2,
 | |
|             )
 | |
|         html = widget.render(
 | |
|             name=name,
 | |
|             value=self.value(),
 | |
|             attrs=attrs,
 | |
|             **kwargs
 | |
|         )
 | |
|         return force_text(html)
 | |
| 
 | |
|     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.
 | |
|         """
 | |
|         data = self.initial
 | |
|         if self.form.is_bound:
 | |
|             data = self.field.bound_data(self.data, data)
 | |
|         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 force_text(auto_id):
 | |
|             return force_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_)
 | |
| 
 | |
|     @cached_property
 | |
|     def initial(self):
 | |
|         data = self.form.get_initial_for_field(self.field, self.name)
 | |
|         # 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)
 | |
|         return data
 | |
| 
 | |
|     def build_widget_attrs(self, attrs, widget=None):
 | |
|         if not widget:
 | |
|             widget = self.field.widget
 | |
|         attrs = dict(attrs)  # Copy attrs to avoid modifying the argument.
 | |
|         if widget.use_required_attribute(self.initial) and self.field.required and self.form.use_required_attribute:
 | |
|             attrs['required'] = True
 | |
|         if self.field.disabled:
 | |
|             attrs['disabled'] = True
 | |
|         return attrs
 | |
| 
 | |
| 
 | |
| @html_safe
 | |
| @python_2_unicode_compatible
 | |
| class BoundWidget(object):
 | |
|     """
 | |
|     A container class used for iterating over widgets. This is useful for
 | |
|     widgets that have choices. For example, the following can be used in a
 | |
|     template:
 | |
| 
 | |
|     {% for radio in myform.beatles %}
 | |
|       <label for="{{ radio.id_for_label }}">
 | |
|         {{ radio.choice_label }}
 | |
|         <span class="radio">{{ radio.tag }}</span>
 | |
|       </label>
 | |
|     {% endfor %}
 | |
|     """
 | |
|     def __init__(self, parent_widget, data, renderer):
 | |
|         self.parent_widget = parent_widget
 | |
|         self.data = data
 | |
|         self.renderer = renderer
 | |
| 
 | |
|     def __str__(self):
 | |
|         return self.tag(wrap_label=True)
 | |
| 
 | |
|     def tag(self, wrap_label=False):
 | |
|         context = {'widget': self.data, 'wrap_label': wrap_label}
 | |
|         return self.parent_widget._render(self.template_name, context, self.renderer)
 | |
| 
 | |
|     @property
 | |
|     def template_name(self):
 | |
|         if 'template_name' in self.data:
 | |
|             return self.data['template_name']
 | |
|         return self.parent_widget.template_name
 | |
| 
 | |
|     @property
 | |
|     def id_for_label(self):
 | |
|         return 'id_%s_%s' % (self.data['name'], self.data['index'])
 | |
| 
 | |
|     @property
 | |
|     def choice_label(self):
 | |
|         return self.data['label']
 |