diff --git a/requirements/requirements-testing.txt b/requirements/requirements-testing.txt
index fb6dbf05f..8e8f08f4d 100644
--- a/requirements/requirements-testing.txt
+++ b/requirements/requirements-testing.txt
@@ -3,3 +3,4 @@ pytest>=6.2.0,<8.0
pytest-cov>=4.0.0,<5.0
pytest-django>=4.5.2,<5.0
importlib-metadata<5.0
+selenium==4.7.2
diff --git a/rest_framework/static/rest_framework/js/load-ajax-form.js b/rest_framework/static/rest_framework/js/load-ajax-form.js
index 09daf0888..1fe37393b 100644
--- a/rest_framework/static/rest_framework/js/load-ajax-form.js
+++ b/rest_framework/static/rest_framework/js/load-ajax-form.js
@@ -1,3 +1,4 @@
$(document).ready(function() {
$('form').ajaxForm();
+ persistPostRawDataForm()
});
diff --git a/rest_framework/static/rest_framework/js/store-data-form.js b/rest_framework/static/rest_framework/js/store-data-form.js
new file mode 100644
index 000000000..f7ccb2032
--- /dev/null
+++ b/rest_framework/static/rest_framework/js/store-data-form.js
@@ -0,0 +1,22 @@
+/**
+ * Preserve form's raw input after do POST
+ */
+function persistPostRawDataForm() {
+ var formContainerId = '#post-generic-content-form'
+ var $form = $(`${formContainerId} form`)
+ var $formInput = $(`${formContainerId} textarea`);
+ var localStorageKey = 'rawDataSubmitted'
+ var formAction = $form.attr('action')
+
+ $form.submit(function () {
+ var data = sessionStorage.getItem(localStorageKey)
+ data = data ? JSON.parse(data) : {}
+ data[formAction] = $formInput.val()
+ sessionStorage.setItem(localStorageKey, JSON.stringify(data))
+ });
+
+ if (sessionStorage.getItem(localStorageKey)) {
+ var data = JSON.parse(sessionStorage.getItem(localStorageKey))
+ $formInput.text(data[formAction])
+ };
+}
\ No newline at end of file
diff --git a/rest_framework/templates/rest_framework/base.html b/rest_framework/templates/rest_framework/base.html
index 533db1378..7d6d17ceb 100644
--- a/rest_framework/templates/rest_framework/base.html
+++ b/rest_framework/templates/rest_framework/base.html
@@ -295,6 +295,7 @@
+
diff --git a/tests/browsable_api/test_browsable_api.py b/tests/browsable_api/test_browsable_api.py
index a76d11fe3..de8f3eb32 100644
--- a/tests/browsable_api/test_browsable_api.py
+++ b/tests/browsable_api/test_browsable_api.py
@@ -1,8 +1,16 @@
from django.contrib.auth.models import User
+from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from django.test import TestCase, override_settings
+from selenium.webdriver.common.by import By
+from selenium.webdriver.firefox.webdriver import WebDriver
+from rest_framework import serializers
from rest_framework.permissions import IsAuthenticated
+from rest_framework.routers import SimpleRouter
from rest_framework.test import APIClient
+from rest_framework.utils import json
+from rest_framework.viewsets import ModelViewSet
+from tests.models import BasicModel
from .views import BasicModelWithUsersViewSet, OrganizationPermissions
@@ -99,3 +107,90 @@ class NoDropdownWithoutAuthTests(TestCase):
response = self.client.get('/')
content = response.content.decode()
assert '
' not in content
+
+
+class BasicModelSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = BasicModel
+ fields = "__all__"
+
+
+class ResourceViewSet(ModelViewSet):
+ serializer_class = BasicModelSerializer
+ queryset = BasicModel.objects.all()
+
+
+router = SimpleRouter()
+router.register(r'resources', ResourceViewSet)
+router.register(r'resources-2', ResourceViewSet)
+urlpatterns = []
+urlpatterns += router.urls
+
+
+@override_settings(ROOT_URLCONF='tests.browsable_api.test_browsable_api')
+class SeleniumTests(StaticLiveServerTestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+ cls.selenium = WebDriver()
+ cls.selenium.implicitly_wait(10)
+
+ @classmethod
+ def tearDownClass(cls):
+ cls.selenium.quit()
+ super().tearDownClass()
+
+ def test_post_raw_data(self):
+ self.selenium.get('%s%s' % (self.live_server_url, '/resources'))
+ raw_tab = self.selenium.find_element(By.NAME, "raw-tab")
+ raw_tab.click()
+
+ # Raw data textarea is pre-populated with empty value.
+ text_input = self.selenium.find_element(By.ID, "id__content")
+ self.assertEqual(json.loads(text_input.text), {'text': ''})
+
+ # Do submit and the data will be presisted then.
+ text_input.clear()
+ json_data = json.dumps({"text": "text description"}, indent=4)
+ text_input.send_keys(json_data)
+ post_button = self.selenium.find_element(By.CSS_SELECTOR, '#post-generic-content-form div.form-actions button')
+ post_button.click()
+ text_input = self.selenium.find_element(By.ID, "id__content")
+ self.assertEqual(json.loads(text_input.text), {'text': 'text description'})
+ self.assertEqual(BasicModel.objects.last().text, 'text description')
+
+ # Moving to another resource view, the raw data will be clean.
+ self.selenium.get('%s%s' % (self.live_server_url, '/resources-2'))
+ text_input = self.selenium.find_element(By.ID, "id__content")
+ self.assertEqual(json.loads(text_input.text), {'text': ''})
+
+ # Do a second submit from the second view.
+ text_input.clear()
+ json_data = json.dumps({"text": "text description 2"}, indent=4)
+ text_input.send_keys(json_data)
+ post_button = self.selenium.find_element(By.CSS_SELECTOR, '#post-generic-content-form div.form-actions button')
+ post_button.click()
+ text_input = self.selenium.find_element(By.ID, "id__content")
+ self.assertEqual(json.loads(text_input.text), {'text': 'text description 2'})
+ self.assertEqual(BasicModel.objects.last().text, 'text description 2')
+
+ # Do a third submit, from the same view, with the previous data
+ # (which was pre-populated).
+ post_button = self.selenium.find_element(By.CSS_SELECTOR, '#post-generic-content-form div.form-actions button')
+ post_button.click()
+ self.assertEqual(BasicModel.objects.count(), 3)
+ self.assertEqual(BasicModel.objects.last().text, 'text description 2')
+
+ # Only two keys were stored.
+ session_storage_data = self.selenium.execute_script(
+ 'return JSON.parse(sessionStorage.getItem("rawDataSubmitted"))'
+ )
+ self.assertEqual(
+ ['/resources-2/', '/resources/'],
+ list(session_storage_data.keys())
+ )
+ self.assertEqual(
+ ['{\n "text": "text description 2"\n}', '{\n "text": "text description"\n}'],
+ list(session_storage_data.values())
+ )