summaryrefslogtreecommitdiff
path: root/django/forms/forms.py
diff options
context:
space:
mode:
authorDavid Smith <smithdc@gmail.com>2021-09-10 08:06:01 +0100
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2021-09-20 15:50:18 +0200
commit456466d932830b096d39806e291fe23ec5ed38d5 (patch)
tree9320cc645ef43eb920630cff02c1387b34f21906 /django/forms/forms.py
parent5353e7c2505c0d0ab8232ad9c131b3c99c833988 (diff)
Fixed #31026 -- Switched form rendering to template engine.
Thanks Carlton Gibson, Keryn Knight, Mariusz Felisiak, and Nick Pope for reviews. Co-authored-by: Johannes Hoppe <info@johanneshoppe.com>
Diffstat (limited to 'django/forms/forms.py')
-rw-r--r--django/forms/forms.py95
1 files changed, 55 insertions, 40 deletions
diff --git a/django/forms/forms.py b/django/forms/forms.py
index 2bf268ae76..589b4693fd 100644
--- a/django/forms/forms.py
+++ b/django/forms/forms.py
@@ -4,15 +4,17 @@ Form classes
import copy
import datetime
+import warnings
from django.core.exceptions import NON_FIELD_ERRORS, ValidationError
from django.forms.fields import Field, FileField
-from django.forms.utils import ErrorDict, ErrorList
+from django.forms.utils import ErrorDict, ErrorList, RenderableFormMixin
from django.forms.widgets import Media, MediaDefiningClass
from django.utils.datastructures import MultiValueDict
+from django.utils.deprecation import RemovedInDjango50Warning
from django.utils.functional import cached_property
-from django.utils.html import conditional_escape, html_safe
-from django.utils.safestring import mark_safe
+from django.utils.html import conditional_escape
+from django.utils.safestring import SafeString, mark_safe
from django.utils.translation import gettext as _
from .renderers import get_default_renderer
@@ -49,8 +51,7 @@ class DeclarativeFieldsMetaclass(MediaDefiningClass):
return new_class
-@html_safe
-class BaseForm:
+class BaseForm(RenderableFormMixin):
"""
The main implementation of all the Form logic. Note that this class is
different than Form. See the comments by the Form class for more info. Any
@@ -62,6 +63,12 @@ class BaseForm:
prefix = None
use_required_attribute = True
+ template_name = 'django/forms/default.html'
+ template_name_p = 'django/forms/p.html'
+ template_name_table = 'django/forms/table.html'
+ template_name_ul = 'django/forms/ul.html'
+ template_name_label = 'django/forms/label.html'
+
def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
initial=None, error_class=ErrorList, label_suffix=None,
empty_permitted=False, field_order=None, use_required_attribute=None, renderer=None):
@@ -129,9 +136,6 @@ class BaseForm:
fields.update(self.fields) # add remaining fields in original order
self.fields = fields
- def __str__(self):
- return self.as_table()
-
def __repr__(self):
if self._errors is None:
is_valid = "Unknown"
@@ -206,6 +210,12 @@ class BaseForm:
def _html_output(self, normal_row, error_row, row_ender, help_text_html, errors_on_separate_row):
"Output HTML. Used by as_table(), as_ul(), as_p()."
+ warnings.warn(
+ 'django.forms.BaseForm._html_output() is deprecated. '
+ 'Please use .render() and .get_context() instead.',
+ RemovedInDjango50Warning,
+ stacklevel=2,
+ )
# Errors that should be displayed above all fields.
top_errors = self.non_field_errors().copy()
output, hidden_fields = [], []
@@ -282,35 +292,37 @@ class BaseForm:
output.append(str_hidden)
return mark_safe('\n'.join(output))
- def as_table(self):
- "Return this form rendered as HTML <tr>s -- excluding the <table></table>."
- return self._html_output(
- normal_row='<tr%(html_class_attr)s><th>%(label)s</th><td>%(errors)s%(field)s%(help_text)s</td></tr>',
- error_row='<tr><td colspan="2">%s</td></tr>',
- row_ender='</td></tr>',
- help_text_html='<br><span class="helptext">%s</span>',
- errors_on_separate_row=False,
- )
-
- def as_ul(self):
- "Return this form rendered as HTML <li>s -- excluding the <ul></ul>."
- return self._html_output(
- normal_row='<li%(html_class_attr)s>%(errors)s%(label)s %(field)s%(help_text)s</li>',
- error_row='<li>%s</li>',
- row_ender='</li>',
- help_text_html=' <span class="helptext">%s</span>',
- errors_on_separate_row=False,
- )
-
- def as_p(self):
- "Return this form rendered as HTML <p>s."
- return self._html_output(
- normal_row='<p%(html_class_attr)s>%(label)s %(field)s%(help_text)s</p>',
- error_row='%s',
- row_ender='</p>',
- help_text_html=' <span class="helptext">%s</span>',
- errors_on_separate_row=True,
- )
+ def get_context(self):
+ fields = []
+ hidden_fields = []
+ top_errors = self.non_field_errors().copy()
+ for name, bf in self._bound_items():
+ bf_errors = self.error_class(bf.errors, renderer=self.renderer)
+ if bf.is_hidden:
+ if bf_errors:
+ top_errors += [
+ _('(Hidden field %(name)s) %(error)s') % {'name': name, 'error': str(e)}
+ for e in bf_errors
+ ]
+ hidden_fields.append(bf)
+ else:
+ errors_str = str(bf_errors)
+ # RemovedInDjango50Warning.
+ if not isinstance(errors_str, SafeString):
+ warnings.warn(
+ f'Returning a plain string from '
+ f'{self.error_class.__name__} is deprecated. Please '
+ f'customize via the template system instead.',
+ RemovedInDjango50Warning,
+ )
+ errors_str = mark_safe(errors_str)
+ fields.append((bf, errors_str))
+ return {
+ 'form': self,
+ 'fields': fields,
+ 'hidden_fields': hidden_fields,
+ 'errors': top_errors,
+ }
def non_field_errors(self):
"""
@@ -318,7 +330,10 @@ class BaseForm:
field -- i.e., from Form.clean(). Return an empty ErrorList if there
are none.
"""
- return self.errors.get(NON_FIELD_ERRORS, self.error_class(error_class='nonfield'))
+ return self.errors.get(
+ NON_FIELD_ERRORS,
+ self.error_class(error_class='nonfield', renderer=self.renderer),
+ )
def add_error(self, field, error):
"""
@@ -360,9 +375,9 @@ class BaseForm:
raise ValueError(
"'%s' has no field named '%s'." % (self.__class__.__name__, field))
if field == NON_FIELD_ERRORS:
- self._errors[field] = self.error_class(error_class='nonfield')
+ self._errors[field] = self.error_class(error_class='nonfield', renderer=self.renderer)
else:
- self._errors[field] = self.error_class()
+ self._errors[field] = self.error_class(renderer=self.renderer)
self._errors[field].extend(error_list)
if field in self.cleaned_data:
del self.cleaned_data[field]