diff --git a/docs/topics/html-and-forms.md b/docs/topics/html-and-forms.md
index 18774926b5c..0186adbce94 100644
--- a/docs/topics/html-and-forms.md
+++ b/docs/topics/html-and-forms.md
@@ -215,6 +215,7 @@ select.html | `ChoiceField` or relational field types | hide_label
radio.html | `ChoiceField` or relational field types | inline, hide_label
select_multiple.html | `MultipleChoiceField` or relational fields with `many=True` | hide_label
checkbox_multiple.html | `MultipleChoiceField` or relational fields with `many=True` | inline, hide_label
-checkbox.html | `BooleanField` | hide_label
+checkbox.html | `BooleanField` with `allow_null=False` | hide_label
+select_boolean.html | `BooleanField` with `allow_null=True` | hide_label
fieldset.html | Nested serializer | hide_label
list_fieldset.html | `ListField` or nested serializer with `many=True` | hide_label
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
index e4be54751db..d6473eab12e 100644
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -720,6 +720,10 @@ class BooleanField(Field):
}
NULL_VALUES = {'null', 'Null', 'NULL', '', None}
+ @property
+ def _is_nullable_boolean_field(self):
+ return self.allow_null
+
def to_internal_value(self, data):
try:
if data in self.TRUE_VALUES:
@@ -741,6 +745,14 @@ def to_representation(self, value):
return None
return bool(value)
+ def iter_options(self):
+ choices = {
+ "": _("Unknown"),
+ True: _("Yes"),
+ False: _("No"),
+ }
+ return iter_options(choices)
+
class NullBooleanField(BooleanField):
initial = None
diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py
index 5b7ba8a8c80..5f2c8605057 100644
--- a/rest_framework/renderers.py
+++ b/rest_framework/renderers.py
@@ -328,8 +328,11 @@ class HTMLFormRenderer(BaseRenderer):
def render_field(self, field, parent_style):
if isinstance(field._field, serializers.HiddenField):
return ''
-
- style = self.default_style[field].copy()
+ is_nullable_boolean_field = isinstance(field._field, serializers.BooleanField) and field._field.allow_null
+ if is_nullable_boolean_field:
+ style = {'base_template': 'select_boolean.html'}
+ else:
+ style = self.default_style[field].copy()
style.update(field.style)
if 'template_pack' not in style:
style['template_pack'] = parent_style.get('template_pack', self.template_pack)
diff --git a/rest_framework/templates/rest_framework/horizontal/select_boolean.html b/rest_framework/templates/rest_framework/horizontal/select_boolean.html
new file mode 100644
index 00000000000..bbdc3361e7c
--- /dev/null
+++ b/rest_framework/templates/rest_framework/horizontal/select_boolean.html
@@ -0,0 +1,25 @@
+
+
+ {% if field.label %}
+
+ {% endif %}
+
+
+
+ {% if field.errors %}
+ {% for error in field.errors %}
+ {{ error }}
+ {% endfor %}
+ {% endif %}
+
+ {% if field.help_text %}
+ {{ field.help_text|safe }}
+ {% endif %}
+
+
\ No newline at end of file
diff --git a/rest_framework/templates/rest_framework/inline/select_boolean.html b/rest_framework/templates/rest_framework/inline/select_boolean.html
new file mode 100644
index 00000000000..edb240b87e1
--- /dev/null
+++ b/rest_framework/templates/rest_framework/inline/select_boolean.html
@@ -0,0 +1,15 @@
+{% load rest_framework %}
+
+
+ {% if field.label %}
+
+ {% endif %}
+
+
+
\ No newline at end of file
diff --git a/rest_framework/templates/rest_framework/vertical/select_boolean.html b/rest_framework/templates/rest_framework/vertical/select_boolean.html
new file mode 100644
index 00000000000..715b7a21395
--- /dev/null
+++ b/rest_framework/templates/rest_framework/vertical/select_boolean.html
@@ -0,0 +1,23 @@
+