import re from django.core.exceptions import ValidationError from django.forms.utils import pretty_name from django.forms.widgets import MultiWidget, Textarea, TextInput from django.utils.functional import cached_property from django.utils.html import format_html, html_safe from django.utils.translation import gettext_lazy as _ __all__ = ("BoundField",) @html_safe class BoundField: "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): """Render 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 [ 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 __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, (int, slice)): raise TypeError( "BoundField indices must be integers or slices, not %s." % type(idx).__name__ ) return self.subwidgets[idx] @property def errors(self): """ Return an ErrorList (empty if there are no errors) for this field. """ return self.form.errors.get( self.name, self.form.error_class(renderer=self.form.renderer) ) def as_widget(self, widget=None, attrs=None, only_initial=False): """ Render the field by rendering the passed widget, adding any HTML attributes passed as attrs. If a widget isn't specified, use the field's default widget. """ widget = widget or self.field.widget if self.field.localize: widget.is_localized = True attrs = attrs or {} attrs = self.build_widget_attrs(attrs, widget) if self.auto_id and "id" not in widget.attrs: attrs.setdefault( "id", self.html_initial_id if only_initial else self.auto_id ) return widget.render( name=self.html_initial_name if only_initial else self.html_name, value=self.value(), attrs=attrs, renderer=self.form.renderer, ) def as_text(self, attrs=None, **kwargs): """ Return a string of HTML for representing this as an . """ return self.as_widget(TextInput(), attrs, **kwargs) def as_textarea(self, attrs=None, **kwargs): """Return a string of HTML for representing this as a