diff --git a/django/forms/fields.py b/django/forms/fields.py
index ea93c306c5..762d59c1b9 100644
--- a/django/forms/fields.py
+++ b/django/forms/fields.py
@@ -204,6 +204,14 @@ class Field(six.with_metaclass(RenameFieldMethods, object)):
data_value = data if data is not None else ''
return initial_value != data_value
+ def get_bound_field(self, form, field_name):
+ """
+ Return a BoundField instance that will be used when accessing the form
+ field in a template.
+ """
+ from django.forms.forms import BoundField
+ return BoundField(form, self, field_name)
+
def __deepcopy__(self, memo):
result = copy.copy(self)
memo[id(self)] = result
diff --git a/django/forms/forms.py b/django/forms/forms.py
index 7db2465fda..07415f8fe1 100644
--- a/django/forms/forms.py
+++ b/django/forms/forms.py
@@ -152,7 +152,7 @@ class BaseForm(object):
raise KeyError(
"Key %r not found in '%s'" % (name, self.__class__.__name__))
if name not in self._bound_fields_cache:
- self._bound_fields_cache[name] = BoundField(self, field, name)
+ self._bound_fields_cache[name] = field.get_bound_field(self, name)
return self._bound_fields_cache[name]
@property
diff --git a/docs/ref/forms/api.txt b/docs/ref/forms/api.txt
index 16e8443361..ca9e4d868f 100644
--- a/docs/ref/forms/api.txt
+++ b/docs/ref/forms/api.txt
@@ -932,6 +932,48 @@ and using the template above, would render something like:
+Customizing ``BoundField``
+--------------------------
+
+.. versionadded:: 1.9
+
+If you need to access some additional information about a form field in a
+template and using a subclass of :class:`~django.forms.Field` isn't
+sufficient, consider also customizing :class:`~django.forms.BoundField`.
+
+A custom form field can override ``get_bound_field()``:
+
+.. method:: Field.get_bound_field(form, field_name)
+
+ Takes an instance of :class:`~django.forms.Form` and the name of the field.
+ The return value will be used when accessing the field in a template. Most
+ likely it will be an instance of a subclass of
+ :class:`~django.forms.BoundField`.
+
+If you have a ``GPSCoordinatesField``, for example, and want to be able to
+access additional information about the coordinates in a template, this could
+be implemented as follows::
+
+ class GPSCoordinatesBoundField(BoundField):
+ @property
+ def country(self):
+ """
+ Return the country the coordinates lie in or None if it can't be
+ determined.
+ """
+ value = self.value()
+ if value:
+ return get_country_from_coordinates(value)
+ else:
+ return None
+
+ class GPSCoordinatesField(Field):
+ def get_bound_field(self, form, field_name):
+ return GPSCoordinatesBoundField(form, self, field_name)
+
+Now you can access the country in a template with
+``{{ form.coordinates.country }}``.
+
.. _binding-uploaded-files:
Binding uploaded files to a form
diff --git a/docs/ref/forms/fields.txt b/docs/ref/forms/fields.txt
index 88c35829c0..ac7c6400ae 100644
--- a/docs/ref/forms/fields.txt
+++ b/docs/ref/forms/fields.txt
@@ -1239,3 +1239,6 @@ custom ``Field`` classes. To do this, just create a subclass of
``clean()`` method and that its ``__init__()`` method accept the core arguments
mentioned above (``required``, ``label``, ``initial``, ``widget``,
``help_text``).
+
+You can also customize how a field will be accessed by overriding
+:meth:`~django.forms.Field.get_bound_field()`.
diff --git a/docs/releases/1.9.txt b/docs/releases/1.9.txt
index 73b2cffb8c..d5ed37737f 100644
--- a/docs/releases/1.9.txt
+++ b/docs/releases/1.9.txt
@@ -364,6 +364,9 @@ Forms
* Form fields now support the :attr:`~django.forms.Field.disabled` argument,
allowing the field widget to be displayed disabled by browsers.
+* It's now possible to customize bound fields by overriding a field's
+ :meth:`~django.forms.Field.get_bound_field()` method.
+
Generic Views
^^^^^^^^^^^^^
diff --git a/tests/forms_tests/tests/test_forms.py b/tests/forms_tests/tests/test_forms.py
index ac96d775a2..d3618b1a8c 100644
--- a/tests/forms_tests/tests/test_forms.py
+++ b/tests/forms_tests/tests/test_forms.py
@@ -1903,6 +1903,17 @@ Password:
f = SampleForm(data={'name': 'bar'})
self.assertIsInstance(force_text(f['name']), SafeData)
+ def test_custom_boundfield(self):
+ class CustomField(CharField):
+ def get_bound_field(self, form, name):
+ return (form, name)
+
+ class SampleForm(Form):
+ name = CustomField()
+
+ f = SampleForm()
+ self.assertEqual(f['name'], (f, 'name'))
+
def test_initial_datetime_values(self):
now = datetime.datetime.now()
# Nix microseconds (since they should be ignored). #22502