diff --git a/django/forms/boundfield.py b/django/forms/boundfield.py
index a9b4c5e063..e68744dbdc 100644
--- a/django/forms/boundfield.py
+++ b/django/forms/boundfield.py
@@ -129,12 +129,9 @@ class BoundField(object):
Returns the value for this BoundField, using the initial value if
the form is not bound or the data otherwise.
"""
- if not self.form.is_bound:
- data = self.initial
- else:
- data = self.field.bound_data(
- self.data, self.form.initial.get(self.name, self.field.initial)
- )
+ data = self.initial
+ if self.form.is_bound:
+ data = self.field.bound_data(self.data, data)
return self.field.prepare_value(data)
def label_tag(self, contents=None, attrs=None, label_suffix=None):
@@ -218,14 +215,12 @@ class BoundField(object):
@cached_property
def initial(self):
- 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 self.field.widget.supports_microseconds):
- data = data.replace(microsecond=0)
+ data = self.form.get_initial_for_field(self.field, self.name)
+ # 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 self.field.widget.supports_microseconds):
+ data = data.replace(microsecond=0)
return data
def build_widget_attrs(self, attrs, widget=None):
diff --git a/django/forms/forms.py b/django/forms/forms.py
index 25e081e324..17d4598f4c 100644
--- a/django/forms/forms.py
+++ b/django/forms/forms.py
@@ -377,12 +377,12 @@ class BaseForm(object):
# Each widget type knows how to retrieve its own data, because some
# widgets split data over several HTML fields.
if field.disabled:
- value = self.initial.get(name, field.initial)
+ value = self.get_initial_for_field(field, name)
else:
value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
try:
if isinstance(field, FileField):
- initial = self.initial.get(name, field.initial)
+ initial = self.get_initial_for_field(field, name)
value = field.clean(value, initial)
else:
value = field.clean(value)
@@ -431,9 +431,9 @@ class BaseForm(object):
prefixed_name = self.add_prefix(name)
data_value = field.widget.value_from_datadict(self.data, self.files, prefixed_name)
if not field.show_hidden_initial:
- initial_value = self.initial.get(name, field.initial)
- if callable(initial_value):
- initial_value = initial_value()
+ # Use the BoundField's initial as this is the value passed to
+ # the widget.
+ initial_value = self[name].initial
else:
initial_prefixed_name = self.add_initial_prefix(name)
hidden_widget = field.hidden_widget()
@@ -482,6 +482,16 @@ class BaseForm(object):
"""
return [field for field in self if not field.is_hidden]
+ def get_initial_for_field(self, field, field_name):
+ """
+ Return initial data for field on form. Use initial data from the form
+ or the field, in that order. Evaluate callable values.
+ """
+ value = self.initial.get(field_name, field.initial)
+ if callable(value):
+ value = value()
+ return value
+
class Form(six.with_metaclass(DeclarativeFieldsMetaclass, BaseForm)):
"A collection of Fields, plus their associated data."
diff --git a/docs/ref/forms/api.txt b/docs/ref/forms/api.txt
index 094be3a565..8849ce284d 100644
--- a/docs/ref/forms/api.txt
+++ b/docs/ref/forms/api.txt
@@ -248,6 +248,14 @@ precedence::
Url:
Comment:
+.. method:: Form.get_initial_for_field(field, field_name)
+
+.. versionadded:: 1.11
+
+Use :meth:`~Form.get_initial_for_field()` to retrieve initial data for a form
+field. It retrieves data from :attr:`Form.initial` and :attr:`Field.initial`,
+in that order, and evaluates any callable initial values.
+
Checking which form data has changed
====================================
diff --git a/docs/releases/1.11.txt b/docs/releases/1.11.txt
index c8fc03c2b6..41baeb40dd 100644
--- a/docs/releases/1.11.txt
+++ b/docs/releases/1.11.txt
@@ -187,6 +187,10 @@ Forms
* The new :attr:`CharField.empty_value `
attribute allows specifying the Python value to use to represent "empty".
+* The new :meth:`Form.get_initial_for_field()
+ ` method returns initial data for a
+ form field.
+
Generic Views
~~~~~~~~~~~~~
diff --git a/tests/forms_tests/tests/test_forms.py b/tests/forms_tests/tests/test_forms.py
index 0e0ea9f1c2..183caa354c 100644
--- a/tests/forms_tests/tests/test_forms.py
+++ b/tests/forms_tests/tests/test_forms.py
@@ -1904,6 +1904,21 @@ Password:
"""
)
+ def test_get_initial_for_field(self):
+ class PersonForm(Form):
+ first_name = CharField(initial='John')
+ last_name = CharField(initial='Doe')
+ age = IntegerField()
+ occupation = CharField(initial=lambda: 'Unknown')
+
+ form = PersonForm(initial={'first_name': 'Jane'})
+ self.assertEqual(form.get_initial_for_field(form.fields['age'], 'age'), None)
+ self.assertEqual(form.get_initial_for_field(form.fields['last_name'], 'last_name'), 'Doe')
+ # Form.initial overrides Field.initial.
+ self.assertEqual(form.get_initial_for_field(form.fields['first_name'], 'first_name'), 'Jane')
+ # Callables are evaluated.
+ self.assertEqual(form.get_initial_for_field(form.fields['occupation'], 'occupation'), 'Unknown')
+
def test_changed_data(self):
class Person(Form):
first_name = CharField(initial='Hans')
@@ -1960,6 +1975,19 @@ Password:
# BoundField is also cached
self.assertIs(form['name'], name)
+ def test_boundfield_value_disabled_callable_initial(self):
+ class PersonForm(Form):
+ name = CharField(initial=lambda: 'John Doe', disabled=True)
+
+ # Without form data.
+ form = PersonForm()
+ self.assertEqual(form['name'].value(), 'John Doe')
+
+ # With form data. As the field is disabled, the value should not be
+ # affected by the form data.
+ form = PersonForm({})
+ self.assertEqual(form['name'].value(), 'John Doe')
+
def test_boundfield_rendering(self):
"""
Python 2 issue: Test that rendering a BoundField with bytestring content
@@ -2021,6 +2049,23 @@ Password:
self.assertEqual(unbound['hi_without_microsec'].value(), now_no_ms)
self.assertEqual(unbound['ti_without_microsec'].value(), now_no_ms)
+ def test_datetime_clean_initial_callable_disabled(self):
+ now = datetime.datetime(2006, 10, 25, 14, 30, 45, 123456)
+
+ class DateTimeForm(forms.Form):
+ dt = DateTimeField(initial=lambda: now, disabled=True)
+
+ form = DateTimeForm({})
+ self.assertEqual(form.errors, {})
+ self.assertEqual(form.cleaned_data, {'dt': now})
+
+ def test_datetime_changed_data_callable_with_microseconds(self):
+ class DateTimeForm(forms.Form):
+ dt = DateTimeField(initial=lambda: datetime.datetime(2006, 10, 25, 14, 30, 45, 123456), disabled=True)
+
+ form = DateTimeForm({'dt': '2006-10-25 14:30:45'})
+ self.assertEqual(form.changed_data, [])
+
def test_help_text(self):
# You can specify descriptive text for a field by using the 'help_text' argument)
class UserRegistration(Form):
@@ -2369,6 +2414,14 @@ Password:
'