Fixed #24391 -- Made BoundField.value() cache callable values.

This commit is contained in:
Michael Angeletti 2015-02-22 22:06:41 -05:00 committed by Tim Graham
parent d298b1ba50
commit 65441bbdb0
2 changed files with 32 additions and 7 deletions

View File

@ -29,6 +29,8 @@ def pretty_name(name):
return '' return ''
return name.replace('_', ' ').capitalize() return name.replace('_', ' ').capitalize()
UNSET = object()
class DeclarativeFieldsMetaclass(MediaDefiningClass): class DeclarativeFieldsMetaclass(MediaDefiningClass):
""" """
@ -93,6 +95,7 @@ class BaseForm(object):
# Instances should always modify self.fields; they should not modify # Instances should always modify self.fields; they should not modify
# self.base_fields. # self.base_fields.
self.fields = copy.deepcopy(self.base_fields) self.fields = copy.deepcopy(self.base_fields)
self._bound_fields_cache = {}
def __str__(self): def __str__(self):
return self.as_table() return self.as_table()
@ -120,7 +123,9 @@ class BaseForm(object):
except KeyError: except KeyError:
raise KeyError( raise KeyError(
"Key %r not found in '%s'" % (name, self.__class__.__name__)) "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 @property
def errors(self): def errors(self):
@ -486,6 +491,7 @@ class BoundField(object):
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._initial_value = UNSET
def __str__(self): def __str__(self):
"""Renders this field as an HTML widget.""" """Renders this field as an HTML widget."""
@ -580,12 +586,16 @@ class BoundField(object):
if not self.form.is_bound: if not self.form.is_bound:
data = self.form.initial.get(self.name, self.field.initial) data = self.form.initial.get(self.name, self.field.initial)
if callable(data): if callable(data):
data = data() if self._initial_value is not UNSET:
# If this is an auto-generated default date, nix the data = self._initial_value
# microseconds for standardized handling. See #22502. else:
if (isinstance(data, (datetime.datetime, datetime.time)) and data = data()
not getattr(self.field.widget, 'supports_microseconds', True)): # If this is an auto-generated default date, nix the
data = data.replace(microsecond=0) # 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: else:
data = self.field.bound_data( data = self.field.bound_data(
self.data, self.form.initial.get(self.name, self.field.initial) self.data, self.form.initial.get(self.name, self.field.initial)

View File

@ -4,6 +4,7 @@ from __future__ import unicode_literals
import copy import copy
import datetime import datetime
import json import json
import uuid
from django.core.exceptions import NON_FIELD_ERRORS from django.core.exceptions import NON_FIELD_ERRORS
from django.core.files.uploadedfile import SimpleUploadedFile from django.core.files.uploadedfile import SimpleUploadedFile
@ -1369,6 +1370,20 @@ class FormsTestCase(TestCase):
self.assertEqual(bound['password'].value(), 'foo') self.assertEqual(bound['password'].value(), 'foo')
self.assertEqual(unbound['password'].value(), None) 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): def test_boundfield_rendering(self):
""" """
Python 2 issue: Test that rendering a BoundField with bytestring content Python 2 issue: Test that rendering a BoundField with bytestring content