diff --git a/django/forms/fields.py b/django/forms/fields.py index 202a6d72c8..4bd9c352f2 100644 --- a/django/forms/fields.py +++ b/django/forms/fields.py @@ -95,6 +95,7 @@ class Field: "required": _("This field is required."), } empty_values = list(validators.EMPTY_VALUES) + bound_field_class = None def __init__( self, @@ -111,6 +112,7 @@ class Field: disabled=False, label_suffix=None, template_name=None, + bound_field_class=None, ): # required -- Boolean that specifies whether the field is required. # True by default. @@ -135,11 +137,13 @@ class Field: # is its widget is shown in the form but not editable. # label_suffix -- Suffix to be added to the label. Overrides # form's label_suffix. + # bound_field_class -- BoundField class to use in Field.get_bound_field. self.required, self.label, self.initial = required, label, initial self.show_hidden_initial = show_hidden_initial self.help_text = help_text self.disabled = disabled self.label_suffix = label_suffix + self.bound_field_class = bound_field_class or self.bound_field_class widget = widget or self.widget if isinstance(widget, type): widget = widget() @@ -251,7 +255,10 @@ class Field: Return a BoundField instance that will be used when accessing the form field in a template. """ - return BoundField(form, self, field_name) + bound_field_class = ( + self.bound_field_class or form.bound_field_class or BoundField + ) + return bound_field_class(form, self, field_name) def __deepcopy__(self, memo): result = copy.copy(self) diff --git a/django/forms/forms.py b/django/forms/forms.py index 614f990395..844f15f9f2 100644 --- a/django/forms/forms.py +++ b/django/forms/forms.py @@ -68,6 +68,8 @@ class BaseForm(RenderableFormMixin): template_name_ul = "django/forms/ul.html" template_name_label = "django/forms/label.html" + bound_field_class = None + def __init__( self, data=None, @@ -81,6 +83,7 @@ class BaseForm(RenderableFormMixin): field_order=None, use_required_attribute=None, renderer=None, + bound_field_class=None, ): self.is_bound = data is not None or files is not None self.data = MultiValueDict() if data is None else data @@ -124,6 +127,12 @@ class BaseForm(RenderableFormMixin): renderer = renderer() self.renderer = renderer + self.bound_field_class = ( + bound_field_class + or self.bound_field_class + or getattr(self.renderer, "bound_field_class", None) + ) + def order_fields(self, field_order): """ Rearrange the fields according to field_order. diff --git a/django/forms/renderers.py b/django/forms/renderers.py index baf8f74507..20eaf265df 100644 --- a/django/forms/renderers.py +++ b/django/forms/renderers.py @@ -21,6 +21,8 @@ class BaseRenderer: formset_template_name = "django/forms/formsets/div.html" field_template_name = "django/forms/field.html" + bound_field_class = None + def get_template(self, template_name): raise NotImplementedError("subclasses must implement get_template()") diff --git a/docs/ref/forms/api.txt b/docs/ref/forms/api.txt index 1b1ecbec6c..9b827ca69a 100644 --- a/docs/ref/forms/api.txt +++ b/docs/ref/forms/api.txt @@ -822,6 +822,9 @@ classes, as needed. The HTML will look something like: >>> f["subject"].legend_tag(attrs={"class": "foo"}) Subject: +You may further modify the rendering of form rows by using a +:ref:`custom BoundField `. + .. _ref-forms-api-configuring-label: Configuring form elements' HTML ``id`` attributes and ``