mirror of
https://github.com/django/django.git
synced 2024-12-22 17:16:24 +00:00
Fixed #35192 -- Added possibility to override BoundField at Form level or Field level.
This commit is contained in:
parent
7e41a7a47d
commit
9122e8542b
@ -18,7 +18,6 @@ from urllib.parse import urlsplit, urlunsplit
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core import validators
|
from django.core import validators
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.forms.boundfield import BoundField
|
|
||||||
from django.forms.utils import from_current_timezone, to_current_timezone
|
from django.forms.utils import from_current_timezone, to_current_timezone
|
||||||
from django.forms.widgets import (
|
from django.forms.widgets import (
|
||||||
FILE_INPUT_CONTRADICTION,
|
FILE_INPUT_CONTRADICTION,
|
||||||
@ -95,6 +94,7 @@ class Field:
|
|||||||
"required": _("This field is required."),
|
"required": _("This field is required."),
|
||||||
}
|
}
|
||||||
empty_values = list(validators.EMPTY_VALUES)
|
empty_values = list(validators.EMPTY_VALUES)
|
||||||
|
bound_field_class = None
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@ -111,6 +111,7 @@ class Field:
|
|||||||
disabled=False,
|
disabled=False,
|
||||||
label_suffix=None,
|
label_suffix=None,
|
||||||
template_name=None,
|
template_name=None,
|
||||||
|
bound_field_class=None,
|
||||||
):
|
):
|
||||||
# required -- Boolean that specifies whether the field is required.
|
# required -- Boolean that specifies whether the field is required.
|
||||||
# True by default.
|
# True by default.
|
||||||
@ -135,11 +136,13 @@ class Field:
|
|||||||
# is its widget is shown in the form but not editable.
|
# is its widget is shown in the form but not editable.
|
||||||
# label_suffix -- Suffix to be added to the label. Overrides
|
# label_suffix -- Suffix to be added to the label. Overrides
|
||||||
# form's label_suffix.
|
# 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.required, self.label, self.initial = required, label, initial
|
||||||
self.show_hidden_initial = show_hidden_initial
|
self.show_hidden_initial = show_hidden_initial
|
||||||
self.help_text = help_text
|
self.help_text = help_text
|
||||||
self.disabled = disabled
|
self.disabled = disabled
|
||||||
self.label_suffix = label_suffix
|
self.label_suffix = label_suffix
|
||||||
|
self.bound_field_class = bound_field_class or self.bound_field_class
|
||||||
widget = widget or self.widget
|
widget = widget or self.widget
|
||||||
if isinstance(widget, type):
|
if isinstance(widget, type):
|
||||||
widget = widget()
|
widget = widget()
|
||||||
@ -251,7 +254,8 @@ class Field:
|
|||||||
Return a BoundField instance that will be used when accessing the form
|
Return a BoundField instance that will be used when accessing the form
|
||||||
field in a template.
|
field in a template.
|
||||||
"""
|
"""
|
||||||
return BoundField(form, self, field_name)
|
BoundFieldClass = self.bound_field_class or form.bound_field_class
|
||||||
|
return BoundFieldClass(form, self, field_name)
|
||||||
|
|
||||||
def __deepcopy__(self, memo):
|
def __deepcopy__(self, memo):
|
||||||
result = copy.copy(self)
|
result = copy.copy(self)
|
||||||
|
@ -68,6 +68,8 @@ class BaseForm(RenderableFormMixin):
|
|||||||
template_name_ul = "django/forms/ul.html"
|
template_name_ul = "django/forms/ul.html"
|
||||||
template_name_label = "django/forms/label.html"
|
template_name_label = "django/forms/label.html"
|
||||||
|
|
||||||
|
bound_field_class = None
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
data=None,
|
data=None,
|
||||||
@ -81,6 +83,7 @@ class BaseForm(RenderableFormMixin):
|
|||||||
field_order=None,
|
field_order=None,
|
||||||
use_required_attribute=None,
|
use_required_attribute=None,
|
||||||
renderer=None,
|
renderer=None,
|
||||||
|
bound_field_class=None,
|
||||||
):
|
):
|
||||||
self.is_bound = data is not None or files is not None
|
self.is_bound = data is not None or files is not None
|
||||||
self.data = MultiValueDict() if data is None else data
|
self.data = MultiValueDict() if data is None else data
|
||||||
@ -124,6 +127,12 @@ class BaseForm(RenderableFormMixin):
|
|||||||
renderer = renderer()
|
renderer = renderer()
|
||||||
self.renderer = renderer
|
self.renderer = renderer
|
||||||
|
|
||||||
|
self.bound_field_class = (
|
||||||
|
bound_field_class
|
||||||
|
or self.bound_field_class
|
||||||
|
or self.renderer.bound_field_class
|
||||||
|
)
|
||||||
|
|
||||||
def order_fields(self, field_order):
|
def order_fields(self, field_order):
|
||||||
"""
|
"""
|
||||||
Rearrange the fields according to field_order.
|
Rearrange the fields according to field_order.
|
||||||
|
@ -21,6 +21,12 @@ class BaseRenderer:
|
|||||||
formset_template_name = "django/forms/formsets/div.html"
|
formset_template_name = "django/forms/formsets/div.html"
|
||||||
field_template_name = "django/forms/field.html"
|
field_template_name = "django/forms/field.html"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def bound_field_class(self):
|
||||||
|
from django.forms.boundfield import BoundField
|
||||||
|
|
||||||
|
return BoundField
|
||||||
|
|
||||||
def get_template(self, template_name):
|
def get_template(self, template_name):
|
||||||
raise NotImplementedError("subclasses must implement get_template()")
|
raise NotImplementedError("subclasses must implement get_template()")
|
||||||
|
|
||||||
|
@ -934,6 +934,30 @@ When set to ``True`` (the default), required form fields will have the
|
|||||||
``use_required_attribute=False`` to avoid incorrect browser validation when
|
``use_required_attribute=False`` to avoid incorrect browser validation when
|
||||||
adding and deleting forms from a formset.
|
adding and deleting forms from a formset.
|
||||||
|
|
||||||
|
.. attribute:: Form.bound_field_class
|
||||||
|
|
||||||
|
.. versionadded:: 5.2
|
||||||
|
|
||||||
|
Specify a :class:`~django.forms.BoundField` class to be used on the form. For
|
||||||
|
example::
|
||||||
|
|
||||||
|
from django import forms
|
||||||
|
|
||||||
|
|
||||||
|
class CustomBoundField(forms.BoundField):
|
||||||
|
def label_tag(self, contents=None, attrs=None, label_suffix=None, tag=None):
|
||||||
|
attrs = attrs or {}
|
||||||
|
attrs["class"] = "wide"
|
||||||
|
return super().label_tag(contents, attrs, label_suffix, tag)
|
||||||
|
|
||||||
|
|
||||||
|
class MyForm(forms.Form):
|
||||||
|
bound_field_class = CustomBoundField
|
||||||
|
|
||||||
|
This takes precedence over :attr:`.BaseRenderer.bound_field_class`.
|
||||||
|
|
||||||
|
See :ref:`custom-boundfield` for examples of overriding a ``BoundField``.
|
||||||
|
|
||||||
Configuring the rendering of a form's widgets
|
Configuring the rendering of a form's widgets
|
||||||
---------------------------------------------
|
---------------------------------------------
|
||||||
|
|
||||||
@ -1125,6 +1149,12 @@ they're not the only way a form object can be displayed.
|
|||||||
|
|
||||||
The ``__str__()`` method of this object displays the HTML for this field.
|
The ``__str__()`` method of this object displays the HTML for this field.
|
||||||
|
|
||||||
|
You can use :attr:`.Form.bound_field_class` and
|
||||||
|
:attr:`.Field.bound_field_class` to use a different ``BoundField`` class
|
||||||
|
per-form or per-field.
|
||||||
|
|
||||||
|
See :ref:`custom-boundfield` for examples of overriding a ``BoundField``.
|
||||||
|
|
||||||
To retrieve a single ``BoundField``, use dictionary lookup syntax on your form
|
To retrieve a single ``BoundField``, use dictionary lookup syntax on your form
|
||||||
using the field's name as the key:
|
using the field's name as the key:
|
||||||
|
|
||||||
@ -1464,26 +1494,34 @@ Methods of ``BoundField``
|
|||||||
>>> print(bound_form["subject"].value())
|
>>> print(bound_form["subject"].value())
|
||||||
hi
|
hi
|
||||||
|
|
||||||
|
.. _custom-boundfield:
|
||||||
|
|
||||||
Customizing ``BoundField``
|
Customizing ``BoundField``
|
||||||
==========================
|
==========================
|
||||||
|
|
||||||
If you need to access some additional information about a form field in a
|
There are multiple ways to use a custom :class:`.BoundField`:
|
||||||
template and using a subclass of :class:`~django.forms.Field` isn't
|
|
||||||
sufficient, consider also customizing :class:`~django.forms.BoundField`.
|
|
||||||
|
|
||||||
A custom form field can override ``get_bound_field()``:
|
* A project can define a project-wide :class:`.BoundField` class using
|
||||||
|
:attr:`.BaseRenderer.bound_field_class` and :setting:`FORM_RENDERER`.
|
||||||
|
|
||||||
.. method:: Field.get_bound_field(form, field_name)
|
* A form can override :attr:`.Form.bound_field_class` or set the constructor
|
||||||
|
``bound_field_class`` argument in the constructor.
|
||||||
|
|
||||||
Takes an instance of :class:`~django.forms.Form` and the name of the field.
|
* A custom form field can override :attr:`.Field.bound_field_class` or set the
|
||||||
The return value will be used when accessing the field in a template. Most
|
``bound_field_class`` argument in the constructor.
|
||||||
likely it will be an instance of a subclass of
|
|
||||||
:class:`~django.forms.BoundField`.
|
|
||||||
|
|
||||||
If you have a ``GPSCoordinatesField``, for example, and want to be able to
|
* Lastly, a custom form field can override :meth:`.Field.get_bound_field()`
|
||||||
|
though any of the previous options is preferred.
|
||||||
|
|
||||||
|
You may want to use a custom :class:`.BoundField` if you need to access some
|
||||||
|
additional information about a form field in a template and using a subclass of
|
||||||
|
:class:`~django.forms.Field` isn't sufficient.
|
||||||
|
|
||||||
|
For example, if you have a ``GPSCoordinatesField``, and want to be able to
|
||||||
access additional information about the coordinates in a template, this could
|
access additional information about the coordinates in a template, this could
|
||||||
be implemented as follows::
|
be implemented as follows::
|
||||||
|
|
||||||
|
|
||||||
class GPSCoordinatesBoundField(BoundField):
|
class GPSCoordinatesBoundField(BoundField):
|
||||||
@property
|
@property
|
||||||
def country(self):
|
def country(self):
|
||||||
@ -1499,12 +1537,36 @@ be implemented as follows::
|
|||||||
|
|
||||||
|
|
||||||
class GPSCoordinatesField(Field):
|
class GPSCoordinatesField(Field):
|
||||||
def get_bound_field(self, form, field_name):
|
bound_field_class = GPSCoordinatesBoundField
|
||||||
return GPSCoordinatesBoundField(form, self, field_name)
|
|
||||||
|
|
||||||
Now you can access the country in a template with
|
Now you can access the country in a template with
|
||||||
``{{ form.coordinates.country }}``.
|
``{{ form.coordinates.country }}``.
|
||||||
|
|
||||||
|
You may also want to customize the default form field template rendering. For
|
||||||
|
example, you can override :meth:`.BoundField.label_tag` to add a custom class::
|
||||||
|
|
||||||
|
class StyledLabelBoundField(BoundField):
|
||||||
|
def label_tag(self, contents=None, attrs=None, label_suffix=None, tag=None):
|
||||||
|
attrs = attrs or {}
|
||||||
|
attrs["class"] = "wide"
|
||||||
|
return super().label_tag(contents, attrs, label_suffix, tag)
|
||||||
|
|
||||||
|
|
||||||
|
class UserForm(forms.Form):
|
||||||
|
bound_field_class = StyledLabelBoundField
|
||||||
|
name = CharField()
|
||||||
|
|
||||||
|
This would update the default form rendering:
|
||||||
|
|
||||||
|
.. code-block:: pycon
|
||||||
|
|
||||||
|
>>> f = UserForm()
|
||||||
|
>>> print(f["name"].label_tag)
|
||||||
|
<label for="id_name" class="wide">Name:</label>
|
||||||
|
|
||||||
|
A project can define a project-wise ``BoundField`` class using
|
||||||
|
:attr:`.BaseRenderer.bound_field_class` and a custom :setting:`FORM_RENDERER`.
|
||||||
|
|
||||||
.. _binding-uploaded-files:
|
.. _binding-uploaded-files:
|
||||||
|
|
||||||
Binding uploaded files to a form
|
Binding uploaded files to a form
|
||||||
|
@ -397,6 +397,15 @@ default this value is set to ``"django/forms/field.html"``. Can be changed per
|
|||||||
field by overriding this attribute or more generally by overriding the default
|
field by overriding this attribute or more generally by overriding the default
|
||||||
template, see also :ref:`overriding-built-in-field-templates`.
|
template, see also :ref:`overriding-built-in-field-templates`.
|
||||||
|
|
||||||
|
``bound_field_class``
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
.. versionadded:: 5.2
|
||||||
|
|
||||||
|
.. attribute:: Field.bound_field_class
|
||||||
|
|
||||||
|
Allows a per-field override of :attr:`.Form.bound_field_class`.
|
||||||
|
|
||||||
Checking if the field data has changed
|
Checking if the field data has changed
|
||||||
======================================
|
======================================
|
||||||
|
|
||||||
@ -1626,4 +1635,15 @@ only requirements are that it implement a ``clean()`` method and that its
|
|||||||
``label``, ``initial``, ``widget``, ``help_text``).
|
``label``, ``initial``, ``widget``, ``help_text``).
|
||||||
|
|
||||||
You can also customize how a field will be accessed by overriding
|
You can also customize how a field will be accessed by overriding
|
||||||
:meth:`~django.forms.Field.get_bound_field()`.
|
:attr:`~django.forms.Field.bound_field_class` or override
|
||||||
|
:meth:`.Field.get_bound_field()` if you need more flexibility when creating
|
||||||
|
the ``BoundField``:
|
||||||
|
|
||||||
|
.. method:: Field.get_bound_field(form, field_name)
|
||||||
|
|
||||||
|
Takes an instance of :class:`~django.forms.Form` and the name of the field.
|
||||||
|
The return value will be used when accessing the field in a template. Most
|
||||||
|
likely it will be an instance of a subclass of
|
||||||
|
:class:`.BoundField`.
|
||||||
|
|
||||||
|
See :ref:`custom-boundfield` for examples of overriding a ``BoundField``.
|
||||||
|
@ -65,6 +65,17 @@ should return a rendered templates (as a string) or raise
|
|||||||
|
|
||||||
Defaults to ``"django/forms/field.html"``
|
Defaults to ``"django/forms/field.html"``
|
||||||
|
|
||||||
|
.. attribute:: bound_field_class
|
||||||
|
|
||||||
|
.. versionadded:: 5.2
|
||||||
|
|
||||||
|
The default :class:`~django.forms.BoundField` to use when rendering
|
||||||
|
forms. If not overridden by :attr:`.Form.bound_field_class` or
|
||||||
|
:attr:`.Field.bound_field_class`, this is the ``BoundField`` class that
|
||||||
|
gets used during rendering.
|
||||||
|
|
||||||
|
Defaults to :class:`django.forms.BoundField`.
|
||||||
|
|
||||||
.. method:: get_template(template_name)
|
.. method:: get_template(template_name)
|
||||||
|
|
||||||
Subclasses must implement this method with the appropriate template
|
Subclasses must implement this method with the appropriate template
|
||||||
|
@ -50,6 +50,38 @@ be a ``CompositePrimaryKey``::
|
|||||||
|
|
||||||
See :doc:`/topics/composite-primary-key` for more details.
|
See :doc:`/topics/composite-primary-key` for more details.
|
||||||
|
|
||||||
|
Simplified usage of :class:`~django.forms.BoundField`
|
||||||
|
-----------------------------------------------------
|
||||||
|
|
||||||
|
Prior to 5.2, it was necessary to override :meth:`.Field.get_bound_field()` to
|
||||||
|
use custom :class:`~django.forms.BoundField`. Django now supports specifying a
|
||||||
|
:class:`~django.forms.BoundField` class to use at a project, form and
|
||||||
|
field level::
|
||||||
|
|
||||||
|
from django.forms import Form
|
||||||
|
from django.forms.boundfield import BoundField
|
||||||
|
from django.forms.renderers import DjangoTemplates
|
||||||
|
|
||||||
|
|
||||||
|
class BoundFieldWithCssClass(BoundField):
|
||||||
|
def css_classes(self, extra_classes=None):
|
||||||
|
return super().css_classes("user-form")
|
||||||
|
|
||||||
|
|
||||||
|
class CustomForm(Form):
|
||||||
|
# Override per-form.
|
||||||
|
bound_field_class = BoundFieldWithCssClass
|
||||||
|
|
||||||
|
# Or per-field.
|
||||||
|
name = CharField(bound_field_class=BoundFieldWithCssClass)
|
||||||
|
|
||||||
|
|
||||||
|
# Alternatively, set bound_field_class on your custom FORM_RENDERER.
|
||||||
|
class CustomRenderer(DjangoTemplates):
|
||||||
|
bound_field_class = BoundFieldWithCssClass
|
||||||
|
|
||||||
|
See :ref:`custom-boundfield` for more details.
|
||||||
|
|
||||||
Minor features
|
Minor features
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ from django.core.files.uploadedfile import SimpleUploadedFile
|
|||||||
from django.core.validators import MaxValueValidator, RegexValidator
|
from django.core.validators import MaxValueValidator, RegexValidator
|
||||||
from django.forms import (
|
from django.forms import (
|
||||||
BooleanField,
|
BooleanField,
|
||||||
|
BoundField,
|
||||||
CharField,
|
CharField,
|
||||||
CheckboxSelectMultiple,
|
CheckboxSelectMultiple,
|
||||||
ChoiceField,
|
ChoiceField,
|
||||||
@ -5385,3 +5386,117 @@ class OverrideTests(SimpleTestCase):
|
|||||||
'<label for="id_name" class="required">Name:</label>'
|
'<label for="id_name" class="required">Name:</label>'
|
||||||
'<legend class="required">Language:</legend>',
|
'<legend class="required">Language:</legend>',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class BoundFieldWithoutColon(BoundField):
|
||||||
|
def label_tag(self, contents=None, attrs=None, label_suffix=None, tag=None):
|
||||||
|
return super().label_tag(
|
||||||
|
contents=contents, attrs=attrs, label_suffix="", tag=None
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class BoundFieldWithTwoColons(BoundField):
|
||||||
|
def label_tag(self, contents=None, attrs=None, label_suffix=None, tag=None):
|
||||||
|
return super().label_tag(
|
||||||
|
contents=contents, attrs=attrs, label_suffix="::", tag=None
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class BoundFieldWithCustomClass(BoundField):
|
||||||
|
def label_tag(self, contents=None, attrs=None, label_suffix=None, tag=None):
|
||||||
|
attrs = attrs or {}
|
||||||
|
attrs["class"] = "custom-class"
|
||||||
|
return super().label_tag(contents, attrs, label_suffix, tag)
|
||||||
|
|
||||||
|
|
||||||
|
class BoundFieldOverrideRenderer(DjangoTemplates):
|
||||||
|
bound_field_class = BoundFieldWithoutColon
|
||||||
|
|
||||||
|
|
||||||
|
@override_settings(
|
||||||
|
FORM_RENDERER="forms_tests.tests.test_forms.BoundFieldOverrideRenderer"
|
||||||
|
)
|
||||||
|
class CustomBoundFieldTest(SimpleTestCase):
|
||||||
|
def test_renderer_custom_bound_field(self):
|
||||||
|
t = Template("{{ form }}")
|
||||||
|
html = t.render(Context({"form": Person()}))
|
||||||
|
expected = """
|
||||||
|
<div><label for="id_first_name">First name</label>
|
||||||
|
<input type="text" name="first_name" required
|
||||||
|
id="id_first_name"></div>
|
||||||
|
<div><label for="id_last_name">Last name</label>
|
||||||
|
<input type="text" name="last_name" required
|
||||||
|
id="id_last_name"></div><div>
|
||||||
|
<label for="id_birthday">Birthday</label>
|
||||||
|
<input type="text" name="birthday" required
|
||||||
|
id="id_birthday"></div>"""
|
||||||
|
self.assertHTMLEqual(html, expected)
|
||||||
|
|
||||||
|
def test_form_custom_boundfield(self):
|
||||||
|
class CustomBoundFieldPerson(Person):
|
||||||
|
bound_field_class = BoundFieldWithTwoColons
|
||||||
|
|
||||||
|
with self.subTest("form's BoundField takes over renderer's BoundField"):
|
||||||
|
t = Template("{{ form }}")
|
||||||
|
html = t.render(Context({"form": CustomBoundFieldPerson()}))
|
||||||
|
expected = """
|
||||||
|
<div><label for="id_first_name">First name::</label>
|
||||||
|
<input type="text" name="first_name" required
|
||||||
|
id="id_first_name"></div>
|
||||||
|
<div><label for="id_last_name">Last name::</label>
|
||||||
|
<input type="text" name="last_name" required
|
||||||
|
id="id_last_name"></div><div>
|
||||||
|
<label for="id_birthday">Birthday::</label>
|
||||||
|
<input type="text" name="birthday" required
|
||||||
|
id="id_birthday"></div>"""
|
||||||
|
self.assertHTMLEqual(html, expected)
|
||||||
|
|
||||||
|
with self.subTest("Constructor argument takes over class property"):
|
||||||
|
t = Template("{{ form }}")
|
||||||
|
html = t.render(
|
||||||
|
Context(
|
||||||
|
{
|
||||||
|
"form": CustomBoundFieldPerson(
|
||||||
|
bound_field_class=BoundFieldWithCustomClass
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
expected = """
|
||||||
|
<div><label class="custom-class" for="id_first_name">First name:</label>
|
||||||
|
<input type="text" name="first_name" required
|
||||||
|
id="id_first_name"></div>
|
||||||
|
<div><label class="custom-class" for="id_last_name">Last name:</label>
|
||||||
|
<input type="text" name="last_name" required
|
||||||
|
id="id_last_name"></div><div>
|
||||||
|
<label class="custom-class" for="id_birthday">Birthday:</label>
|
||||||
|
<input type="text" name="birthday" required
|
||||||
|
id="id_birthday"></div>"""
|
||||||
|
self.assertHTMLEqual(html, expected)
|
||||||
|
|
||||||
|
def test_field_custom_bound_field(self):
|
||||||
|
class BoundFieldWithTwoColonsCharField(CharField):
|
||||||
|
bound_field_class = BoundFieldWithTwoColons
|
||||||
|
|
||||||
|
class CustomFieldBoundFieldPerson(Person):
|
||||||
|
bound_field_class = BoundField
|
||||||
|
|
||||||
|
first_name = BoundFieldWithTwoColonsCharField()
|
||||||
|
last_name = BoundFieldWithTwoColonsCharField(
|
||||||
|
bound_field_class=BoundFieldWithCustomClass
|
||||||
|
)
|
||||||
|
|
||||||
|
html = Template("{{ form }}").render(
|
||||||
|
Context({"form": CustomFieldBoundFieldPerson()})
|
||||||
|
)
|
||||||
|
expected = """
|
||||||
|
<div><label for="id_first_name">First name::</label>
|
||||||
|
<input type="text" name="first_name" required
|
||||||
|
id="id_first_name"></div>
|
||||||
|
<div><label class="custom-class" for="id_last_name">Last name:</label>
|
||||||
|
<input type="text" name="last_name" required
|
||||||
|
id="id_last_name"></div><div>
|
||||||
|
<label for="id_birthday">Birthday:</label>
|
||||||
|
<input type="text" name="birthday" required
|
||||||
|
id="id_birthday"></div>"""
|
||||||
|
self.assertHTMLEqual(html, expected)
|
||||||
|
@ -847,8 +847,15 @@ class ModelFormBaseTest(TestCase):
|
|||||||
self.assertEqual(m1.mode, mode)
|
self.assertEqual(m1.mode, mode)
|
||||||
|
|
||||||
def test_renderer_kwarg(self):
|
def test_renderer_kwarg(self):
|
||||||
custom = object()
|
from django.forms.renderers import BaseRenderer
|
||||||
self.assertIs(ProductForm(renderer=custom).renderer, custom)
|
|
||||||
|
class CustomRenderer(BaseRenderer):
|
||||||
|
custom_attribute = "test"
|
||||||
|
|
||||||
|
custom = CustomRenderer()
|
||||||
|
form = ProductForm(renderer=custom)
|
||||||
|
self.assertIs(form.renderer, custom)
|
||||||
|
self.assertEqual(form.renderer.custom_attribute, "test")
|
||||||
|
|
||||||
def test_default_splitdatetime_field(self):
|
def test_default_splitdatetime_field(self):
|
||||||
class PubForm(forms.ModelForm):
|
class PubForm(forms.ModelForm):
|
||||||
|
Loading…
Reference in New Issue
Block a user