From 7131a855326b385c04c1e35cef677c25ea1bbc4f Mon Sep 17 00:00:00 2001
From: Alex Tomkins <tomkins@darkzone.net>
Date: Sat, 7 Oct 2023 21:14:16 +0100
Subject: [PATCH] Use get_or_create for editor page subscriptions

Two processes loading the edit view of a page without a subscription could result in an integrity error.
See https://docs.djangoproject.com/en/4.2/ref/models/querysets/#get-or-create
Fixes #11016
---
 CHANGELOG.txt                               |  1 +
 docs/releases/5.2.md                        |  1 +
 wagtail/admin/tests/pages/test_edit_page.py |  5 +++++
 wagtail/admin/views/pages/edit.py           | 15 +++++++--------
 4 files changed, 14 insertions(+), 8 deletions(-)

diff --git a/CHANGELOG.txt b/CHANGELOG.txt
index 4a926f313f..1dfaebbcec 100644
--- a/CHANGELOG.txt
+++ b/CHANGELOG.txt
@@ -68,6 +68,7 @@ Changelog
  * Fix: Ensure very long words can wrap when viewing saved comments (Chiemezuo Akujobi)
  * Fix: Avoid forgotten password link text conflicting with the supplied aria-label (Thibaud Colas)
  * Fix: Fix log message to record the correct restriction type when removing a page view restriction (Rohit Sharma, Hazh. M. Adam)
+ * Fix: Avoid potential race condition with new Page subscriptions on the edit view (Alex Tomkins)
  * Docs: Document `WAGTAILADMIN_BASE_URL` on "Integrating Wagtail into a Django project" page (Shreshth Srivastava)
  * Docs: Replace incorrect screenshot for authors listing on tutorial (Shreshth Srivastava)
  * Docs: Add documentation for building non-model-based choosers using the _queryish_ library (Matt Westcott)
diff --git a/docs/releases/5.2.md b/docs/releases/5.2.md
index 23402b7870..a372a1653c 100644
--- a/docs/releases/5.2.md
+++ b/docs/releases/5.2.md
@@ -92,6 +92,7 @@ This feature was developed by Paarth Agarwal and Thibaud Colas as part of the Go
  * Ensure very long words can wrap when viewing saved comments (Chiemezuo Akujobi)
  * Avoid forgotten password link text conflicting with the supplied aria-label (Thibaud Colas)
  * Fix log message to record the correct restriction type when removing a page view restriction (Rohit Sharma, Hazh. M. Adam)
+ * Avoid potential race condition with new Page subscriptions on the edit view (Alex Tomkins)
 
 ### Documentation
 
diff --git a/wagtail/admin/tests/pages/test_edit_page.py b/wagtail/admin/tests/pages/test_edit_page.py
index 8882212abe..c723a73e7f 100644
--- a/wagtail/admin/tests/pages/test_edit_page.py
+++ b/wagtail/admin/tests/pages/test_edit_page.py
@@ -3105,6 +3105,11 @@ class TestPageSubscriptionSettings(WagtailTestUtils, TestCase):
             response,
             '<input type="checkbox" name="comment_notifications" id="id_comment_notifications">',
         )
+        self.assertTrue(
+            PageSubscription.objects.filter(
+                page=self.child_page, user=self.user, comment_notifications=False
+            ).exists()
+        )
 
     def test_commment_notifications_switched_on(self):
         PageSubscription.objects.create(
diff --git a/wagtail/admin/views/pages/edit.py b/wagtail/admin/views/pages/edit.py
index f4f2b7f521..984f144c75 100644
--- a/wagtail/admin/views/pages/edit.py
+++ b/wagtail/admin/views/pages/edit.py
@@ -376,14 +376,13 @@ class EditView(TemplateResponseMixin, ContextMixin, HookResponseMixin, View):
         if response:
             return response
 
-        try:
-            self.subscription = PageSubscription.objects.get(
-                page=self.page, user=self.request.user
-            )
-        except PageSubscription.DoesNotExist:
-            self.subscription = PageSubscription(
-                page=self.page, user=self.request.user, comment_notifications=False
-            )
+        self.subscription, created = PageSubscription.objects.get_or_create(
+            page=self.page,
+            user=self.request.user,
+            defaults={
+                "comment_notifications": False,
+            },
+        )
 
         self.edit_handler = self.page_class.get_edit_handler()
         self.form_class = self.edit_handler.get_form_class()