Repository: encode/django-rest-framework

Issue:
Keeping values in browsable api on error
Values entered in the browsable api list view html form are currently cleared on an error. This means that a user would have to re-enter the entirety of what they put in before. Instead, the values should be kept so the user can remedy the incorrect fields and keep what they have already entered.

**Steps to reproduce:**
1. Navigate to browsable api list view page.
2. Enter some values into the html form fields, but leave some empty or invalid.
3. Post the form.
4. Error is produced.

This also occurs on detail view even though the form is already prefilled with information.



Discussion:
Yup clearly an issue that's worth addressing.

Thinking about tackling this one next. It'd definitely make using the browsable api way better.

First attempt at this. This is just making sure we pass data to the serializers used in `get_raw_data_form()` and `get_rendered_html_form()`. As you can see the `username` is missing from the raw data form and is `None` in the HTML form because since the serializer has an error `serializer.data` defaults to `serializer.get_initial()` which excludes fields with `empty` values. Ideas on how to proceed are welcomed.

<img width="988" alt="screen shot 2015-07-18 at 8 00 42 am" src="https://cloud.githubusercontent.com/assets/83319/8761517/24b75cd6-2d23-11e5-81fe-a4ddea4438a5.png">
<img width="973" alt="screen shot 2015-07-18 at 8 00 51 am" src="https://cloud.githubusercontent.com/assets/83319/8761518/24ba9dc4-2d23-11e5-8c8c-8a837c28b193.png">


Resolving patch:
diff --git a/rest_framework/exceptions.py b/rest_framework/exceptions.py
--- a/rest_framework/exceptions.py
+++ b/rest_framework/exceptions.py
@@ -14,6 +14,7 @@
 from django.utils.translation import ungettext
 
 from rest_framework import status
+from rest_framework.utils.serializer_helpers import ReturnDict, ReturnList
 
 
 def _force_text_recursive(data):
@@ -22,14 +23,20 @@ def _force_text_recursive(data):
     lazy translation strings into plain text.
     """
     if isinstance(data, list):
-        return [
+        ret = [
             _force_text_recursive(item) for item in data
         ]
+        if isinstance(data, ReturnList):
+            return ReturnList(ret, serializer=data.serializer)
+        return data
     elif isinstance(data, dict):
-        return dict([
+        ret = dict([
             (key, _force_text_recursive(value))
             for key, value in data.items()
         ])
+        if isinstance(data, ReturnDict):
+            return ReturnDict(ret, serializer=data.serializer)
+        return data
     return force_text(data)
 
 
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -204,7 +204,7 @@ def is_valid(self, raise_exception=False):
                 self._errors = {}
 
         if self._errors and raise_exception:
-            raise ValidationError(self._errors)
+            raise ValidationError(self.errors)
 
         return not bool(self._errors)
 
diff --git a/rest_framework/utils/serializer_helpers.py b/rest_framework/utils/serializer_helpers.py
--- a/rest_framework/utils/serializer_helpers.py
+++ b/rest_framework/utils/serializer_helpers.py
@@ -72,7 +72,7 @@ def __repr__(self):
         ))
 
     def as_form_field(self):
-        value = force_text(self.value)
+        value = '' if self.value is None else force_text(self.value)
         return self.__class__(self._field, value, self.errors, self._prefix)
 
 
@@ -100,7 +100,7 @@ def as_form_field(self):
             if isinstance(value, (list, dict)):
                 values[key] = value
             else:
-                values[key] = force_text(value)
+                values[key] = '' if value is None else force_text(value)
         return self.__class__(self._field, values, self.errors, self._prefix)
 
 


Test patch:
diff --git a/tests/test_generics.py b/tests/test_generics.py
--- a/tests/test_generics.py
+++ b/tests/test_generics.py
@@ -145,6 +145,16 @@ def test_post_cannot_set_id(self):
         created = self.objects.get(id=4)
         self.assertEqual(created.text, 'foobar')
 
+    def test_post_error_root_view(self):
+        """
+        POST requests to ListCreateAPIView in HTML should include a form error.
+        """
+        data = {'text': 'foobar' * 100}
+        request = factory.post('/', data, HTTP_ACCEPT='text/html')
+        response = self.view(request).render()
+        expected_error = '<span class="help-block">Ensure this field has no more than 100 characters.</span>'
+        self.assertIn(expected_error, response.rendered_content.decode('utf-8'))
+
 
 EXPECTED_QUERIES_FOR_PUT = 3 if django.VERSION < (1, 6) else 2
 
@@ -282,6 +292,16 @@ def test_patch_cannot_create_an_object(self):
         self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
         self.assertFalse(self.objects.filter(id=999).exists())
 
+    def test_put_error_instance_view(self):
+        """
+        Incorrect PUT requests in HTML should include a form error.
+        """
+        data = {'text': 'foobar' * 100}
+        request = factory.put('/', data, HTTP_ACCEPT='text/html')
+        response = self.view(request, pk=1).render()
+        expected_error = '<span class="help-block">Ensure this field has no more than 100 characters.</span>'
+        self.assertIn(expected_error, response.rendered_content.decode('utf-8'))
+
 
 class TestFKInstanceView(TestCase):
     def setUp(self):