Fixed #28889 -- Prevented double submission of admin forms.
diff --git a/django/contrib/admin/static/admin/js/change_form.js b/django/contrib/admin/static/admin/js/change_form.js
index 96a4c62ef4c35..0ba16efef9818 100644
--- a/django/contrib/admin/static/admin/js/change_form.js
+++ b/django/contrib/admin/static/admin/js/change_form.js
@@ -1,9 +1,23 @@
+/*global gettext*/
 'use strict';
 {
     const inputTags = ['BUTTON', 'INPUT', 'SELECT', 'TEXTAREA'];
     const modelName = document.getElementById('django-admin-form-add-constants').dataset.modelName;
+    let submitted = false;
+
     if (modelName) {
         const form = document.getElementById(modelName + '_form');
+
+        form.addEventListener('submit', (event) => {
+            event.preventDefault();
+            if (submitted) {
+                const answer = window.confirm(gettext('You have already submitted this form. Are you sure you want to submit it again?'));
+                if (!answer) {return;}
+            };
+            event.target.submit();
+            submitted = true;
+        });
+
         for (const element of form.elements) {
             // HTMLElement.offsetParent returns null when the element is not
             // rendered.
diff --git a/tests/admin_views/test_prevent_double_submission.py b/tests/admin_views/test_prevent_double_submission.py
new file mode 100644
index 0000000000000..1052a977bab50
--- /dev/null
+++ b/tests/admin_views/test_prevent_double_submission.py
@@ -0,0 +1,105 @@
+from django.contrib.admin.tests import AdminSeleniumTestCase
+from django.contrib.auth.models import User
+from django.test import override_settings
+from django.urls import reverse
+
+from .models import Bookmark
+
+
+@override_settings(ROOT_URLCONF="admin_views.urls")
+class SeleniumTests(AdminSeleniumTestCase):
+    available_apps = ["admin_views"] + AdminSeleniumTestCase.available_apps
+
+    def setUp(self):
+        self.BOOKMARK_ADD_URL = reverse("admin:admin_views_bookmark_add")
+        self.BOOKMARK_LIST_URL = reverse("admin:admin_views_bookmark_changelist")
+        self.ALERT_MESSAGE = (
+            "You have already submitted this form. "
+            "Are you sure you want to submit it again?"
+        )
+        self.superuser = User.objects.create_superuser(
+            username="super",
+            password="secret",
+            email="super@example.com",
+        )
+        self.admin_login(
+            username="super",
+            password="secret",
+            login_url=reverse("admin:index"),
+        )
+
+    def test_single_submit_click_is_success_without_alert(self):
+        from selenium.webdriver.common.by import By
+
+        self.selenium.get(self.live_server_url + self.BOOKMARK_ADD_URL)
+        input_ = self.selenium.find_element(By.ID, "id_name")
+        input_.send_keys("Bookmark name")
+        save_button = self.selenium.find_element(By.CSS_SELECTOR, "input[name=_save]")
+        save_button.click()
+        self.assertEqual(
+            self.selenium.current_url, self.live_server_url + self.BOOKMARK_LIST_URL
+        )
+        self.assertEqual(Bookmark.objects.count(), 1)
+
+    def _double_click_submit(self):
+        from selenium.webdriver.common.action_chains import ActionChains
+        from selenium.webdriver.common.by import By
+
+        self.selenium.get(self.live_server_url + self.BOOKMARK_ADD_URL)
+        input_ = self.selenium.find_element(By.ID, "id_name")
+        input_.send_keys("Bookmark name")
+        save_button = self.selenium.find_element(By.CSS_SELECTOR, "input[name=_save]")
+        ActionChains(self.selenium).double_click(save_button).perform()
+
+    def test_confirm_double_submit_alert(self):
+        self._double_click_submit()
+        alert = self.selenium.switch_to.alert
+        self.assertEqual(alert.text, self.ALERT_MESSAGE)
+        alert.accept()
+        self.wait_page_ready()
+
+        OBJECTS_CREATED = 1
+        if self.browser == "chrome":
+            OBJECTS_CREATED = 2
+        elif self.browser == "firefox":
+            pass
+
+        self.assertEqual(Bookmark.objects.count(), OBJECTS_CREATED)
+
+    def test_cancel_double_submit_alert(self):
+        self._double_click_submit()
+        alert = self.selenium.switch_to.alert
+        self.assertEqual(alert.text, self.ALERT_MESSAGE)
+        alert.dismiss()
+        self.wait_page_ready()
+        self.assertEqual(Bookmark.objects.count(), 1)
+
+    def test_submit_and_go_back(self):
+        from selenium.webdriver.common.by import By
+
+        self.selenium.get(self.live_server_url + self.BOOKMARK_ADD_URL)
+        input_ = self.selenium.find_element(By.ID, "id_name")
+        input_.send_keys("Bookmark name")
+
+        # submit by first time
+        save_button = self.selenium.find_element(By.CSS_SELECTOR, "input[name=_save]")
+        save_button.click()
+        self.assertEqual(Bookmark.objects.count(), 1)
+        self.assertEqual(
+            self.selenium.current_url, self.live_server_url + self.BOOKMARK_LIST_URL
+        )
+
+        # go back
+        self.selenium.back()
+        self.assertEqual(
+            self.selenium.current_url, self.live_server_url + self.BOOKMARK_ADD_URL
+        )
+
+        # submit again
+        input_ = self.selenium.find_element(By.ID, "id_name")
+        input_.clear()
+        input_.send_keys("Other bookmark name")
+        save_button = self.selenium.find_element(By.CSS_SELECTOR, "input[name=_save]")
+        save_button.click()
+
+        self.assertEqual(Bookmark.objects.count(), 2)

This is the "You already submitted (alert)" approach for [ticket](https://code.djangoproject.com/ticket/28889)

To run tests please do: `./runtests.py admin_views.test_prevent_double_submission --selenium=chrome,firefox`

Thanks!