summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Angeletti <michael@angelettigroup.com>2015-02-22 22:06:41 -0500
committerTim Graham <timograham@gmail.com>2015-02-24 20:05:16 -0500
commit278b6987943d0a610aadc767200651ec5d7d738e (patch)
tree1b98f69afc39c1ffeab21763dafbdcd406739e27
parent81911f29b70710d8f72916c49a69aa5df5cd7df8 (diff)
[1.8.x] Fixed #24391 -- Made BoundField.value() cache callable values.
Backport of 65441bbdb02427655869c42791a0bc5a9c631292 from master
-rw-r--r--django/forms/forms.py24
-rw-r--r--tests/forms_tests/tests/test_forms.py15
2 files changed, 32 insertions, 7 deletions
diff --git a/django/forms/forms.py b/django/forms/forms.py
index 2d293b2ace..16fc5b00f2 100644
--- a/django/forms/forms.py
+++ b/django/forms/forms.py
@@ -31,6 +31,8 @@ def pretty_name(name):
return ''
return name.replace('_', ' ').capitalize()
+UNSET = object()
+
def get_declared_fields(bases, attrs, with_base_fields=True):
"""
@@ -134,6 +136,7 @@ class BaseForm(object):
# Instances should always modify self.fields; they should not modify
# self.base_fields.
self.fields = copy.deepcopy(self.base_fields)
+ self._bound_fields_cache = {}
def __str__(self):
return self.as_table()
@@ -161,7 +164,9 @@ class BaseForm(object):
except KeyError:
raise KeyError(
"Key %r not found in '%s'" % (name, self.__class__.__name__))
- return BoundField(self, field, name)
+ if name not in self._bound_fields_cache:
+ self._bound_fields_cache[name] = BoundField(self, field, name)
+ return self._bound_fields_cache[name]
@property
def errors(self):
@@ -527,6 +532,7 @@ class BoundField(object):
else:
self.label = self.field.label
self.help_text = field.help_text or ''
+ self._initial_value = UNSET
def __str__(self):
"""Renders this field as an HTML widget."""
@@ -621,12 +627,16 @@ class BoundField(object):
if not self.form.is_bound:
data = self.form.initial.get(self.name, self.field.initial)
if callable(data):
- data = data()
- # If this is an auto-generated default date, nix the
- # microseconds for standardized handling. See #22502.
- if (isinstance(data, (datetime.datetime, datetime.time)) and
- not getattr(self.field.widget, 'supports_microseconds', True)):
- data = data.replace(microsecond=0)
+ if self._initial_value is not UNSET:
+ data = self._initial_value
+ else:
+ data = data()
+ # If this is an auto-generated default date, nix the
+ # microseconds for standardized handling. See #22502.
+ if (isinstance(data, (datetime.datetime, datetime.time)) and
+ not getattr(self.field.widget, 'supports_microseconds', True)):
+ data = data.replace(microsecond=0)
+ self._initial_value = data
else:
data = self.field.bound_data(
self.data, self.form.initial.get(self.name, self.field.initial)
diff --git a/tests/forms_tests/tests/test_forms.py b/tests/forms_tests/tests/test_forms.py
index 33bccbb53b..82a9dd92e4 100644
--- a/tests/forms_tests/tests/test_forms.py
+++ b/tests/forms_tests/tests/test_forms.py
@@ -4,6 +4,7 @@ from __future__ import unicode_literals
import copy
import datetime
import json
+import uuid
from django.core.exceptions import NON_FIELD_ERRORS
from django.core.files.uploadedfile import SimpleUploadedFile
@@ -1376,6 +1377,20 @@ class FormsTestCase(TestCase):
self.assertEqual(bound['password'].value(), 'foo')
self.assertEqual(unbound['password'].value(), None)
+ def test_boundfield_initial_called_once(self):
+ """
+ Multiple calls to BoundField().value() in an unbound form should return
+ the same result each time (#24391).
+ """
+ class MyForm(Form):
+ name = CharField(max_length=10, initial=uuid.uuid4)
+
+ form = MyForm()
+ name = form['name']
+ self.assertEqual(name.value(), name.value())
+ # BoundField is also cached
+ self.assertIs(form['name'], name)
+
def test_boundfield_rendering(self):
"""
Python 2 issue: Test that rendering a BoundField with bytestring content