Commit 278b6987 authored by Michael Angeletti's avatar Michael Angeletti Committed by Tim Graham
Browse files

[1.8.x] Fixed #24391 -- Made BoundField.value() cache callable values.

Backport of 65441bbd from master
parent 81911f29
Loading
Loading
Loading
Loading
+17 −7
Original line number Diff line number Diff line
@@ -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):
                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)
+15 −0
Original line number Diff line number Diff line
@@ -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