mirror of
https://github.com/django/django.git
synced 2025-06-03 10:39:12 +00:00
Fixed #34077 -- Added form field rendering.
This commit is contained in:
parent
d33368b4ab
commit
cad376f844
@ -1,7 +1,7 @@
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.forms.utils import pretty_name
|
from django.forms.utils import RenderableFieldMixin, pretty_name
|
||||||
from django.forms.widgets import MultiWidget, Textarea, TextInput
|
from django.forms.widgets import MultiWidget, Textarea, TextInput
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
from django.utils.html import format_html, html_safe
|
from django.utils.html import format_html, html_safe
|
||||||
@ -10,8 +10,7 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
__all__ = ("BoundField",)
|
__all__ = ("BoundField",)
|
||||||
|
|
||||||
|
|
||||||
@html_safe
|
class BoundField(RenderableFieldMixin):
|
||||||
class BoundField:
|
|
||||||
"A Field plus data"
|
"A Field plus data"
|
||||||
|
|
||||||
def __init__(self, form, field, name):
|
def __init__(self, form, field, name):
|
||||||
@ -26,12 +25,7 @@ class BoundField:
|
|||||||
else:
|
else:
|
||||||
self.label = self.field.label
|
self.label = self.field.label
|
||||||
self.help_text = field.help_text or ""
|
self.help_text = field.help_text or ""
|
||||||
|
self.renderer = form.renderer
|
||||||
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
|
@cached_property
|
||||||
def subwidgets(self):
|
def subwidgets(self):
|
||||||
@ -81,6 +75,13 @@ class BoundField:
|
|||||||
self.name, self.form.error_class(renderer=self.form.renderer)
|
self.name, self.form.error_class(renderer=self.form.renderer)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def template_name(self):
|
||||||
|
return self.field.template_name or self.form.renderer.field_template_name
|
||||||
|
|
||||||
|
def get_context(self):
|
||||||
|
return {"field": self}
|
||||||
|
|
||||||
def as_widget(self, widget=None, attrs=None, only_initial=False):
|
def as_widget(self, widget=None, attrs=None, only_initial=False):
|
||||||
"""
|
"""
|
||||||
Render the field by rendering the passed widget, adding any HTML
|
Render the field by rendering the passed widget, adding any HTML
|
||||||
|
@ -107,6 +107,7 @@ class Field:
|
|||||||
localize=False,
|
localize=False,
|
||||||
disabled=False,
|
disabled=False,
|
||||||
label_suffix=None,
|
label_suffix=None,
|
||||||
|
template_name=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.
|
||||||
@ -164,6 +165,7 @@ class Field:
|
|||||||
self.error_messages = messages
|
self.error_messages = messages
|
||||||
|
|
||||||
self.validators = [*self.default_validators, *validators]
|
self.validators = [*self.default_validators, *validators]
|
||||||
|
self.template_name = template_name
|
||||||
|
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
|
@ -4,16 +4,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% for field, errors in fields %}
|
{% for field, errors in fields %}
|
||||||
<div{% set classes = field.css_classes() %}{% if classes %} class="{{ classes }}"{% endif %}>
|
<div{% set classes = field.css_classes() %}{% if classes %} class="{{ classes }}"{% endif %}>
|
||||||
{% if field.use_fieldset %}
|
{{ field.as_field_group() }}
|
||||||
<fieldset>
|
|
||||||
{% if field.label %}{{ field.legend_tag() }}{% endif %}
|
|
||||||
{% else %}
|
|
||||||
{% if field.label %}{{ field.label_tag() }}{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
{% if field.help_text %}<div class="helptext">{{ field.help_text|safe }}</div>{% endif %}
|
|
||||||
{{ errors }}
|
|
||||||
{{ field }}
|
|
||||||
{% if field.use_fieldset %}</fieldset>{% endif %}
|
|
||||||
{% if loop.last %}
|
{% if loop.last %}
|
||||||
{% for field in hidden_fields %}{{ field }}{% endfor %}
|
{% for field in hidden_fields %}{{ field }}{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
10
django/forms/jinja2/django/forms/field.html
Normal file
10
django/forms/jinja2/django/forms/field.html
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{% if field.use_fieldset %}
|
||||||
|
<fieldset>
|
||||||
|
{% if field.label %}{{ field.legend_tag() }}{% endif %}
|
||||||
|
{% else %}
|
||||||
|
{% if field.label %}{{ field.label_tag() }}{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% if field.help_text %}<div class="helptext">{{ field.help_text|safe }}</div>{% endif %}
|
||||||
|
{{ field.errors }}
|
||||||
|
{{ field }}
|
||||||
|
{% if field.use_fieldset %}</fieldset>{% endif %}
|
@ -19,6 +19,7 @@ def get_default_renderer():
|
|||||||
class BaseRenderer:
|
class BaseRenderer:
|
||||||
form_template_name = "django/forms/div.html"
|
form_template_name = "django/forms/div.html"
|
||||||
formset_template_name = "django/forms/formsets/div.html"
|
formset_template_name = "django/forms/formsets/div.html"
|
||||||
|
field_template_name = "django/forms/field.html"
|
||||||
|
|
||||||
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()")
|
||||||
|
@ -4,16 +4,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% for field, errors in fields %}
|
{% for field, errors in fields %}
|
||||||
<div{% with classes=field.css_classes %}{% if classes %} class="{{ classes }}"{% endif %}{% endwith %}>
|
<div{% with classes=field.css_classes %}{% if classes %} class="{{ classes }}"{% endif %}{% endwith %}>
|
||||||
{% if field.use_fieldset %}
|
{{ field.as_field_group }}
|
||||||
<fieldset>
|
|
||||||
{% if field.label %}{{ field.legend_tag }}{% endif %}
|
|
||||||
{% else %}
|
|
||||||
{% if field.label %}{{ field.label_tag }}{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
{% if field.help_text %}<div class="helptext">{{ field.help_text|safe }}</div>{% endif %}
|
|
||||||
{{ errors }}
|
|
||||||
{{ field }}
|
|
||||||
{% if field.use_fieldset %}</fieldset>{% endif %}
|
|
||||||
{% if forloop.last %}
|
{% if forloop.last %}
|
||||||
{% for field in hidden_fields %}{{ field }}{% endfor %}
|
{% for field in hidden_fields %}{{ field }}{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
10
django/forms/templates/django/forms/field.html
Normal file
10
django/forms/templates/django/forms/field.html
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{% if field.use_fieldset %}
|
||||||
|
<fieldset>
|
||||||
|
{% if field.label %}{{ field.legend_tag }}{% endif %}
|
||||||
|
{% else %}
|
||||||
|
{% if field.label %}{{ field.label_tag }}{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% if field.help_text %}<div class="helptext">{{ field.help_text|safe }}</div>{% endif %}
|
||||||
|
{{ field.errors }}
|
||||||
|
{{ field }}
|
||||||
|
{% if field.use_fieldset %}</fieldset>{% endif %}
|
@ -58,6 +58,29 @@ class RenderableMixin:
|
|||||||
__html__ = render
|
__html__ = render
|
||||||
|
|
||||||
|
|
||||||
|
class RenderableFieldMixin(RenderableMixin):
|
||||||
|
def as_field_group(self):
|
||||||
|
return self.render()
|
||||||
|
|
||||||
|
def as_hidden(self):
|
||||||
|
raise NotImplementedError(
|
||||||
|
"Subclasses of RenderableFieldMixin must provide an as_hidden() method."
|
||||||
|
)
|
||||||
|
|
||||||
|
def as_widget(self):
|
||||||
|
raise NotImplementedError(
|
||||||
|
"Subclasses of RenderableFieldMixin must provide an as_widget() method."
|
||||||
|
)
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
__html__ = __str__
|
||||||
|
|
||||||
|
|
||||||
class RenderableFormMixin(RenderableMixin):
|
class RenderableFormMixin(RenderableMixin):
|
||||||
def as_p(self):
|
def as_p(self):
|
||||||
"""Render as <p> elements."""
|
"""Render as <p> elements."""
|
||||||
|
@ -1257,6 +1257,16 @@ Attributes of ``BoundField``
|
|||||||
>>> print(f["message"].name)
|
>>> print(f["message"].name)
|
||||||
message
|
message
|
||||||
|
|
||||||
|
.. attribute:: BoundField.template_name
|
||||||
|
|
||||||
|
.. versionadded:: 5.0
|
||||||
|
|
||||||
|
The name of the template rendered with :meth:`.BoundField.as_field_group`.
|
||||||
|
|
||||||
|
A property returning the value of the
|
||||||
|
:attr:`~django.forms.Field.template_name` if set otherwise
|
||||||
|
:attr:`~django.forms.renderers.BaseRenderer.field_template_name`.
|
||||||
|
|
||||||
.. attribute:: BoundField.use_fieldset
|
.. attribute:: BoundField.use_fieldset
|
||||||
|
|
||||||
Returns the value of this BoundField widget's ``use_fieldset`` attribute.
|
Returns the value of this BoundField widget's ``use_fieldset`` attribute.
|
||||||
@ -1281,6 +1291,15 @@ Attributes of ``BoundField``
|
|||||||
Methods of ``BoundField``
|
Methods of ``BoundField``
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
|
.. method:: BoundField.as_field_group()
|
||||||
|
|
||||||
|
.. versionadded:: 5.0
|
||||||
|
|
||||||
|
Renders the field using :meth:`.BoundField.render` with default values
|
||||||
|
which renders the ``BoundField``, including its label, help text and errors
|
||||||
|
using the template's :attr:`~django.forms.Field.template_name` if set
|
||||||
|
otherwise :attr:`~django.forms.renderers.BaseRenderer.field_template_name`
|
||||||
|
|
||||||
.. method:: BoundField.as_hidden(attrs=None, **kwargs)
|
.. method:: BoundField.as_hidden(attrs=None, **kwargs)
|
||||||
|
|
||||||
Returns a string of HTML for representing this as an ``<input type="hidden">``.
|
Returns a string of HTML for representing this as an ``<input type="hidden">``.
|
||||||
@ -1321,6 +1340,13 @@ Methods of ``BoundField``
|
|||||||
>>> f["message"].css_classes("foo bar")
|
>>> f["message"].css_classes("foo bar")
|
||||||
'foo bar required'
|
'foo bar required'
|
||||||
|
|
||||||
|
.. method:: BoundField.get_context()
|
||||||
|
|
||||||
|
.. versionadded:: 5.0
|
||||||
|
|
||||||
|
Return the template context for rendering the field. The available context
|
||||||
|
is ``field`` being the instance of the bound field.
|
||||||
|
|
||||||
.. method:: BoundField.label_tag(contents=None, attrs=None, label_suffix=None, tag=None)
|
.. method:: BoundField.label_tag(contents=None, attrs=None, label_suffix=None, tag=None)
|
||||||
|
|
||||||
Renders a label tag for the form field using the template specified by
|
Renders a label tag for the form field using the template specified by
|
||||||
@ -1368,6 +1394,20 @@ Methods of ``BoundField``
|
|||||||
checkbox widgets where ``<legend>`` may be more appropriate than a
|
checkbox widgets where ``<legend>`` may be more appropriate than a
|
||||||
``<label>``.
|
``<label>``.
|
||||||
|
|
||||||
|
.. method:: BoundField.render(template_name=None, context=None, renderer=None)
|
||||||
|
|
||||||
|
.. versionadded:: 5.0
|
||||||
|
|
||||||
|
The render method is called by ``as_field_group``. All arguments are
|
||||||
|
optional and default to:
|
||||||
|
|
||||||
|
* ``template_name``: :attr:`.BoundField.template_name`
|
||||||
|
* ``context``: Value returned by :meth:`.BoundField.get_context`
|
||||||
|
* ``renderer``: Value returned by :attr:`.Form.default_renderer`
|
||||||
|
|
||||||
|
By passing ``template_name`` you can customize the template used for just a
|
||||||
|
single call.
|
||||||
|
|
||||||
.. method:: BoundField.value()
|
.. method:: BoundField.value()
|
||||||
|
|
||||||
Use this method to render the raw value of this field as it would be rendered
|
Use this method to render the raw value of this field as it would be rendered
|
||||||
|
@ -337,6 +337,19 @@ using the ``disabled`` HTML attribute so that it won't be editable by users.
|
|||||||
Even if a user tampers with the field's value submitted to the server, it will
|
Even if a user tampers with the field's value submitted to the server, it will
|
||||||
be ignored in favor of the value from the form's initial data.
|
be ignored in favor of the value from the form's initial data.
|
||||||
|
|
||||||
|
``template_name``
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
.. attribute:: Field.template_name
|
||||||
|
|
||||||
|
.. versionadded:: 5.0
|
||||||
|
|
||||||
|
The ``template_name`` argument allows a custom template to be used when the
|
||||||
|
field is rendered with :meth:`~django.forms.BoundField.as_field_group`. By
|
||||||
|
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
|
||||||
|
template, see also :ref:`overriding-built-in-field-templates`.
|
||||||
|
|
||||||
Checking if the field data has changed
|
Checking if the field data has changed
|
||||||
======================================
|
======================================
|
||||||
|
|
||||||
|
@ -59,6 +59,14 @@ should return a rendered templates (as a string) or raise
|
|||||||
|
|
||||||
Defaults to ``"django/forms/formsets/div.html"`` template.
|
Defaults to ``"django/forms/formsets/div.html"`` template.
|
||||||
|
|
||||||
|
.. attribute:: field_template_name
|
||||||
|
|
||||||
|
.. versionadded:: 5.0
|
||||||
|
|
||||||
|
The default name of the template used to render a ``BoundField``.
|
||||||
|
|
||||||
|
Defaults to ``"django/forms/field.html"``
|
||||||
|
|
||||||
.. 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
|
||||||
@ -162,6 +170,16 @@ forms receive a dictionary with the following values:
|
|||||||
* ``hidden_fields``: All hidden bound fields.
|
* ``hidden_fields``: All hidden bound fields.
|
||||||
* ``errors``: All non field related or hidden field related form errors.
|
* ``errors``: All non field related or hidden field related form errors.
|
||||||
|
|
||||||
|
Context available in field templates
|
||||||
|
====================================
|
||||||
|
|
||||||
|
.. versionadded:: 5.0
|
||||||
|
|
||||||
|
Field templates receive a context from :meth:`.BoundField.get_context`. By
|
||||||
|
default, fields receive a dictionary with the following values:
|
||||||
|
|
||||||
|
* ``field``: The :class:`~django.forms.BoundField`.
|
||||||
|
|
||||||
Context available in widget templates
|
Context available in widget templates
|
||||||
=====================================
|
=====================================
|
||||||
|
|
||||||
@ -201,6 +219,19 @@ To override form templates, you must use the :class:`TemplatesSetting`
|
|||||||
renderer. Then overriding widget templates works :doc:`the same as
|
renderer. Then overriding widget templates works :doc:`the same as
|
||||||
</howto/overriding-templates>` overriding any other template in your project.
|
</howto/overriding-templates>` overriding any other template in your project.
|
||||||
|
|
||||||
|
.. _overriding-built-in-field-templates:
|
||||||
|
|
||||||
|
Overriding built-in field templates
|
||||||
|
===================================
|
||||||
|
|
||||||
|
.. versionadded:: 5.0
|
||||||
|
|
||||||
|
:attr:`.Field.template_name`
|
||||||
|
|
||||||
|
To override field templates, you must use the :class:`TemplatesSetting`
|
||||||
|
renderer. Then overriding field templates works :doc:`the same as
|
||||||
|
</howto/overriding-templates>` overriding any other template in your project.
|
||||||
|
|
||||||
.. _overriding-built-in-widget-templates:
|
.. _overriding-built-in-widget-templates:
|
||||||
|
|
||||||
Overriding built-in widget templates
|
Overriding built-in widget templates
|
||||||
|
@ -45,6 +45,69 @@ toggled on via the UI. This behavior can be changed via the new
|
|||||||
:attr:`.ModelAdmin.show_facets` attribute. For more information see
|
:attr:`.ModelAdmin.show_facets` attribute. For more information see
|
||||||
:ref:`facet-filters`.
|
:ref:`facet-filters`.
|
||||||
|
|
||||||
|
Simplified templates for form field rendering
|
||||||
|
---------------------------------------------
|
||||||
|
|
||||||
|
Django 5.0 introduces the concept of a field group, and field group templates.
|
||||||
|
This simplifies rendering of the related elements of a Django form field such
|
||||||
|
as its label, widget, help text, and errors.
|
||||||
|
|
||||||
|
For example, the template below:
|
||||||
|
|
||||||
|
.. code-block:: html+django
|
||||||
|
|
||||||
|
<form>
|
||||||
|
...
|
||||||
|
<div>
|
||||||
|
{{ form.name.label }}
|
||||||
|
{% if form.name.help_text %}
|
||||||
|
<div class="helptext">{{ form.name.help_text|safe }}</div>
|
||||||
|
{% endif %}
|
||||||
|
{{ form.name.errors }}
|
||||||
|
{{ form.name }}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
{{ form.email.label }}
|
||||||
|
{% if form.email.help_text %}
|
||||||
|
<div class="helptext">{{ form.email.help_text|safe }}</div>
|
||||||
|
{% endif %}
|
||||||
|
{{ form.email.errors }}
|
||||||
|
{{ form.email }}
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
{{ form.password.label }}
|
||||||
|
{% if form.password.help_text %}
|
||||||
|
<div class="helptext">{{ form.password.help_text|safe }}</div>
|
||||||
|
{% endif %}
|
||||||
|
{{ form.password.errors }}
|
||||||
|
{{ form.password }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
...
|
||||||
|
</form>
|
||||||
|
|
||||||
|
Can now be simplified to:
|
||||||
|
|
||||||
|
.. code-block:: html+django
|
||||||
|
|
||||||
|
<form>
|
||||||
|
...
|
||||||
|
<div>
|
||||||
|
{{ form.name.as_field_group }}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">{{ form.email.as_field_group }}</div>
|
||||||
|
<div class="col">{{ form.password.as_field_group }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
...
|
||||||
|
</form>
|
||||||
|
|
||||||
|
:meth:`~django.forms.BoundField.as_field_group` renders fields with the
|
||||||
|
``"django/forms/field.html"`` template by default and can be customized on a
|
||||||
|
per-project, per-field, or per-request basis. See
|
||||||
|
:ref:`reusable-field-group-templates`.
|
||||||
|
|
||||||
Minor features
|
Minor features
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
|
@ -559,13 +559,73 @@ the :meth:`.Form.render`. Here's an example of this being used in a view::
|
|||||||
|
|
||||||
See :ref:`ref-forms-api-outputting-html` for more details.
|
See :ref:`ref-forms-api-outputting-html` for more details.
|
||||||
|
|
||||||
|
.. _reusable-field-group-templates:
|
||||||
|
|
||||||
|
Reusable field group templates
|
||||||
|
------------------------------
|
||||||
|
|
||||||
|
.. versionadded:: 5.0
|
||||||
|
|
||||||
|
Each field is available as an attribute of the form, using
|
||||||
|
``{{form.name_of_field }}`` in a template. A field has a
|
||||||
|
:meth:`~django.forms.BoundField.as_field_group` method which renders the
|
||||||
|
related elements of the field as a group, its label, widget, errors, and help
|
||||||
|
text.
|
||||||
|
|
||||||
|
This allows generic templates to be written that arrange fields elements in the
|
||||||
|
required layout. For example:
|
||||||
|
|
||||||
|
.. code-block:: html+django
|
||||||
|
|
||||||
|
{{ form.non_field_errors }}
|
||||||
|
<div class="fieldWrapper">
|
||||||
|
{{ form.subject.as_field_group }}
|
||||||
|
</div>
|
||||||
|
<div class="fieldWrapper">
|
||||||
|
{{ form.message.as_field_group }}
|
||||||
|
</div>
|
||||||
|
<div class="fieldWrapper">
|
||||||
|
{{ form.sender.as_field_group }}
|
||||||
|
</div>
|
||||||
|
<div class="fieldWrapper">
|
||||||
|
{{ form.cc_myself.as_field_group }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
By default Django uses the ``"django/forms/field.html"`` template which is
|
||||||
|
designed for use with the default ``"django/forms/div.html"`` form style.
|
||||||
|
|
||||||
|
The default template can be customized by by setting
|
||||||
|
:attr:`~django.forms.renderers.BaseRenderer.field_template_name` in your
|
||||||
|
project-level :setting:`FORM_RENDERER`::
|
||||||
|
|
||||||
|
from django.forms.renderers import TemplatesSetting
|
||||||
|
|
||||||
|
|
||||||
|
class CustomFormRenderer(TemplatesSetting):
|
||||||
|
field_template_name = "field_snippet.html"
|
||||||
|
|
||||||
|
… or on a single field::
|
||||||
|
|
||||||
|
class MyForm(forms.Form):
|
||||||
|
subject = forms.CharField(template_name="my_custom_template.html")
|
||||||
|
...
|
||||||
|
|
||||||
|
… or on a per-request basis by calling
|
||||||
|
:meth:`.BoundField.render` and supplying a template name::
|
||||||
|
|
||||||
|
def index(request):
|
||||||
|
form = ContactForm()
|
||||||
|
subject = form["subject"]
|
||||||
|
context = {"subject": subject.render("my_custom_template.html")}
|
||||||
|
return render(request, "index.html", context)
|
||||||
|
|
||||||
Rendering fields manually
|
Rendering fields manually
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
We don't have to let Django unpack the form's fields; we can do it manually if
|
More fine grained control over field rendering is also possible. Likely this
|
||||||
we like (allowing us to reorder the fields, for example). Each field is
|
will be in a custom field template, to allow the template to be written once
|
||||||
available as an attribute of the form using ``{{ form.name_of_field }}``, and
|
and reused for each field. However, it can also be directly accessed from the
|
||||||
in a Django template, will be rendered appropriately. For example:
|
field attribute on the form. For example:
|
||||||
|
|
||||||
.. code-block:: html+django
|
.. code-block:: html+django
|
||||||
|
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
{{ field.label_tag }}
|
||||||
|
<p>Custom Field<p>
|
||||||
|
{{ field }}
|
@ -4602,6 +4602,7 @@ class Jinja2FormsTestCase(FormsTestCase):
|
|||||||
|
|
||||||
class CustomRenderer(DjangoTemplates):
|
class CustomRenderer(DjangoTemplates):
|
||||||
form_template_name = "forms_tests/form_snippet.html"
|
form_template_name = "forms_tests/form_snippet.html"
|
||||||
|
field_template_name = "forms_tests/custom_field.html"
|
||||||
|
|
||||||
|
|
||||||
class RendererTests(SimpleTestCase):
|
class RendererTests(SimpleTestCase):
|
||||||
@ -5009,6 +5010,28 @@ class TemplateTests(SimpleTestCase):
|
|||||||
"('username', 'adrian')]",
|
"('username', 'adrian')]",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_custom_field_template(self):
|
||||||
|
class MyForm(Form):
|
||||||
|
first_name = CharField(template_name="forms_tests/custom_field.html")
|
||||||
|
|
||||||
|
f = MyForm()
|
||||||
|
self.assertHTMLEqual(
|
||||||
|
f.render(),
|
||||||
|
'<div><label for="id_first_name">First name:</label><p>Custom Field<p>'
|
||||||
|
'<input type="text" name="first_name" required id="id_first_name"></div>',
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_custom_field_render_template(self):
|
||||||
|
class MyForm(Form):
|
||||||
|
first_name = CharField()
|
||||||
|
|
||||||
|
f = MyForm()
|
||||||
|
self.assertHTMLEqual(
|
||||||
|
f["first_name"].render(template_name="forms_tests/custom_field.html"),
|
||||||
|
'<label for="id_first_name">First name:</label><p>Custom Field<p>'
|
||||||
|
'<input type="text" name="first_name" required id="id_first_name">',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class OverrideTests(SimpleTestCase):
|
class OverrideTests(SimpleTestCase):
|
||||||
@override_settings(FORM_RENDERER="forms_tests.tests.test_forms.CustomRenderer")
|
@override_settings(FORM_RENDERER="forms_tests.tests.test_forms.CustomRenderer")
|
||||||
@ -5026,6 +5049,22 @@ class OverrideTests(SimpleTestCase):
|
|||||||
self.assertHTMLEqual(html, expected)
|
self.assertHTMLEqual(html, expected)
|
||||||
get_default_renderer.cache_clear()
|
get_default_renderer.cache_clear()
|
||||||
|
|
||||||
|
@override_settings(FORM_RENDERER="forms_tests.tests.test_forms.CustomRenderer")
|
||||||
|
def test_custom_renderer_field_template_name(self):
|
||||||
|
class Person(Form):
|
||||||
|
first_name = CharField()
|
||||||
|
|
||||||
|
get_default_renderer.cache_clear()
|
||||||
|
t = Template("{{ form.first_name.as_field_group }}")
|
||||||
|
html = t.render(Context({"form": Person()}))
|
||||||
|
expected = """
|
||||||
|
<label for="id_first_name">First name:</label>
|
||||||
|
<p>Custom Field<p>
|
||||||
|
<input type="text" name="first_name" required id="id_first_name">
|
||||||
|
"""
|
||||||
|
self.assertHTMLEqual(html, expected)
|
||||||
|
get_default_renderer.cache_clear()
|
||||||
|
|
||||||
def test_per_form_template_name(self):
|
def test_per_form_template_name(self):
|
||||||
class Person(Form):
|
class Person(Form):
|
||||||
first_name = CharField()
|
first_name = CharField()
|
||||||
|
@ -5,6 +5,7 @@ from django.core.exceptions import ValidationError
|
|||||||
from django.forms.utils import (
|
from django.forms.utils import (
|
||||||
ErrorDict,
|
ErrorDict,
|
||||||
ErrorList,
|
ErrorList,
|
||||||
|
RenderableFieldMixin,
|
||||||
RenderableMixin,
|
RenderableMixin,
|
||||||
flatatt,
|
flatatt,
|
||||||
pretty_name,
|
pretty_name,
|
||||||
@ -258,6 +259,18 @@ class FormsUtilsTestCase(SimpleTestCase):
|
|||||||
with self.assertRaisesMessage(NotImplementedError, msg):
|
with self.assertRaisesMessage(NotImplementedError, msg):
|
||||||
mixin.get_context()
|
mixin.get_context()
|
||||||
|
|
||||||
|
def test_field_mixin_as_hidden_must_be_implemented(self):
|
||||||
|
mixin = RenderableFieldMixin()
|
||||||
|
msg = "Subclasses of RenderableFieldMixin must provide an as_hidden() method."
|
||||||
|
with self.assertRaisesMessage(NotImplementedError, msg):
|
||||||
|
mixin.as_hidden()
|
||||||
|
|
||||||
|
def test_field_mixin_as_widget_must_be_implemented(self):
|
||||||
|
mixin = RenderableFieldMixin()
|
||||||
|
msg = "Subclasses of RenderableFieldMixin must provide an as_widget() method."
|
||||||
|
with self.assertRaisesMessage(NotImplementedError, msg):
|
||||||
|
mixin.as_widget()
|
||||||
|
|
||||||
def test_pretty_name(self):
|
def test_pretty_name(self):
|
||||||
self.assertEqual(pretty_name("john_doe"), "John doe")
|
self.assertEqual(pretty_name("john_doe"), "John doe")
|
||||||
self.assertEqual(pretty_name(None), "")
|
self.assertEqual(pretty_name(None), "")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user