diff --git a/docs/api-guide/renderers.md b/docs/api-guide/renderers.md
index ca3a29b82..954fb3bb9 100644
--- a/docs/api-guide/renderers.md
+++ b/docs/api-guide/renderers.md
@@ -103,6 +103,16 @@ Unlike other renderers, the data passed to the `Response` does not need to be se
The TemplateHTMLRenderer will create a `RequestContext`, using the `response.data` as the context dict, and determine a template name to use to render the context.
+---
+
+**Note:** When used with a view that makes use of a serializer the `Response` sent for rendering may not be a dictionay and will need to be wrapped in a dict before returning to allow the TemplateHTMLRenderer to render it. For example:
+
+```
+response.data = {'results': response.data}
+```
+
+---
+
The template name is determined by (in order of preference):
1. An explicit `template_name` argument passed to the response.
diff --git a/docs/index.md b/docs/index.md
index 0273da9f1..0e6bb48f2 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -190,11 +190,6 @@ For support please see the [REST framework discussion group][group], try the `#
For priority support please sign up for a [professional or premium sponsorship plan](https://fund.django-rest-framework.org/topics/funding/).
-For updates on REST framework development, you may also want to follow [the author][twitter] on Twitter.
-
-
-
-
## Security
If you believe you’ve found something in Django REST framework which has security implications, please **do not raise the issue in a public forum**.
diff --git a/requirements/requirements-testing.txt b/requirements/requirements-testing.txt
index ad246e857..99463560e 100644
--- a/requirements/requirements-testing.txt
+++ b/requirements/requirements-testing.txt
@@ -2,3 +2,4 @@
pytest>=5.4.1,<5.5
pytest-django>=3.9.0,<3.10
pytest-cov>=2.7.1
+six>=1.14.0
diff --git a/rest_framework/exceptions.py b/rest_framework/exceptions.py
index 943dcc88c..fee8f024f 100644
--- a/rest_framework/exceptions.py
+++ b/rest_framework/exceptions.py
@@ -20,7 +20,7 @@ def _get_error_details(data, default_code=None):
Descend into a nested data structure, forcing any
lazy translation strings or strings into `ErrorDetail`.
"""
- if isinstance(data, list):
+ if isinstance(data, (list, tuple)):
ret = [
_get_error_details(item, default_code) for item in data
]
@@ -150,7 +150,9 @@ class ValidationError(APIException):
# For validation failures, we may collect many errors together,
# so the details should always be coerced to a list if not already.
- if not isinstance(detail, dict) and not isinstance(detail, list):
+ if isinstance(detail, tuple):
+ detail = list(detail)
+ elif not isinstance(detail, dict) and not isinstance(detail, list):
detail = [detail]
self.detail = _get_error_details(detail, code)
diff --git a/tests/test_validation_error.py b/tests/test_validation_error.py
index 562fe37e6..341c4342a 100644
--- a/tests/test_validation_error.py
+++ b/tests/test_validation_error.py
@@ -2,6 +2,7 @@ from django.test import TestCase
from rest_framework import serializers, status
from rest_framework.decorators import api_view
+from rest_framework.exceptions import ValidationError
from rest_framework.response import Response
from rest_framework.settings import api_settings
from rest_framework.test import APIRequestFactory
@@ -99,3 +100,12 @@ class TestValidationErrorWithCodes(TestCase):
response = view(request)
assert response.status_code == status.HTTP_400_BAD_REQUEST
assert response.data == self.expected_response_data
+
+
+class TestValidationErrorConvertsTuplesToLists(TestCase):
+ def test_validation_error_details(self):
+ error = ValidationError(detail=('message1', 'message2'))
+ assert isinstance(error.detail, list)
+ assert len(error.detail) == 2
+ assert str(error.detail[0]) == 'message1'
+ assert str(error.detail[1]) == 'message2'