Repository: wagtail/wagtail

Issue:
Missing log information for wagtail.schedule.cancel
### Issue Summary

While testing my changes of #9590, I've found a (minor) issue. When canceling a scheduled publication, the log actually contains the date when the revision has been created, and when the page would have been published, but since `has_live_version` is never set in the following code:
https://github.com/wagtail/wagtail/blob/5a249d4e9c65ec742c8e920d44f2fda2038395e3/wagtail/models/__init__.py#L2731-L2745

...it always results in a `KeyError`, which gets caught, resulting in the generic fallback message "Page unscheduled from publishing".

https://github.com/wagtail/wagtail/blob/5a249d4e9c65ec742c8e920d44f2fda2038395e3/wagtail/wagtail_hooks.py#L258-L277

I will adapt the tests to check for the expected message and fix the code in a separate PR.

### Steps to Reproduce

1. Follow the steps in #9581, then cancel the scheduled publication
2. Check the site history log

Any other relevant information. For example, why do you consider this a bug and what did you expect to happen instead?

-   I have confirmed that this issue can be reproduced as described on a fresh Wagtail project: yes

### Technical details

-   Python version: 3.11
-   Django version: 4.1.3
-   Wagtail version: 4.1
-   Browser version: Firefox 106



Discussion:

Resolving patch:
diff --git a/wagtail/admin/auth.py b/wagtail/admin/auth.py
--- a/wagtail/admin/auth.py
+++ b/wagtail/admin/auth.py
@@ -2,12 +2,13 @@
 from functools import wraps
 
 import l18n
+from django.conf import settings
 from django.contrib.auth import get_user_model
 from django.core.exceptions import PermissionDenied
 from django.db.models import Q
 from django.shortcuts import redirect
 from django.urls import reverse
-from django.utils.timezone import activate as activate_tz
+from django.utils.timezone import override as override_tz
 from django.utils.translation import gettext as _
 from django.utils.translation import override
 
@@ -175,8 +176,9 @@ def decorated_view(request, *args, **kwargs):
                     )
                     l18n.set_language(preferred_language)
                     time_zone = user.wagtail_userprofile.get_current_time_zone()
-                    activate_tz(time_zone)
-                with LogContext(user=user):
+                else:
+                    time_zone = settings.TIME_ZONE
+                with override_tz(time_zone), LogContext(user=user):
                     if preferred_language:
                         with override(preferred_language):
                             response = view_func(request, *args, **kwargs)
diff --git a/wagtail/models/__init__.py b/wagtail/models/__init__.py
--- a/wagtail/models/__init__.py
+++ b/wagtail/models/__init__.py
@@ -2840,6 +2840,7 @@ def save(self, user=None, *args, **kwargs):
                         "go_live_at": object.go_live_at.strftime("%d %b %Y %H:%M")
                         if object.go_live_at
                         else None,
+                        "has_live_version": object.live,
                     }
                 },
                 user=user,


Test patch:
diff --git a/wagtail/admin/tests/test_audit_log.py b/wagtail/admin/tests/test_audit_log.py
--- a/wagtail/admin/tests/test_audit_log.py
+++ b/wagtail/admin/tests/test_audit_log.py
@@ -1,5 +1,6 @@
 from datetime import timedelta
 
+from django.conf import settings
 from django.contrib.auth.models import Group, Permission
 from django.test import TestCase
 from django.urls import reverse
@@ -255,3 +256,74 @@ def test_revert_and_publish_logs_reversion_and_publish(self):
             list(entries),
             ["wagtail.create", "wagtail.publish", "wagtail.rename", "wagtail.revert"],
         )
+
+    def test_page_history_after_unscheduled_publication(self):
+        # schedule for publishing
+        go_live_at = timezone.now() + timedelta(minutes=30)
+        if settings.USE_TZ:
+            go_live_at = timezone.localtime(go_live_at)
+        self.hello_page.go_live_at = go_live_at
+        revision = self.hello_page.save_revision(log_action=True)
+        revision.publish()
+
+        self.login(user=self.editor)
+
+        response = self.client.post(
+            reverse(
+                "wagtailadmin_pages:revisions_unschedule",
+                args=(self.hello_page.id, revision.id),
+            )
+        )
+        history_url = reverse(
+            "wagtailadmin_pages:history", kwargs={"page_id": self.hello_page.id}
+        )
+        self.assertRedirects(
+            response,
+            history_url,
+        )
+
+        response = self.client.get(history_url)
+        self.assertEqual(response.status_code, 200)
+        self.assertContains(
+            response,
+            f"Page unscheduled for publishing at {go_live_at.strftime('%d %b %Y %H:%M')}",
+        )
+
+    def test_page_history_after_unscheduled_revision(self):
+        # Prepare clean live page with revisions
+        test_page = SimplePage(title="About", slug="about", content="hello")
+        self.hello_page.add_child(instance=test_page)
+        revision = test_page.save_revision(log_action=True)
+        revision.publish()
+        test_page.refresh_from_db()
+
+        # Schedule a new version for publishing
+        go_live_at = timezone.now() + timedelta(minutes=30)
+        if settings.USE_TZ:
+            go_live_at = timezone.localtime(go_live_at)
+        test_page.go_live_at = go_live_at
+        revision = test_page.save_revision(log_action=True)
+        revision.publish()
+
+        self.login(user=self.editor)
+
+        response = self.client.post(
+            reverse(
+                "wagtailadmin_pages:revisions_unschedule",
+                args=(test_page.id, revision.id),
+            )
+        )
+        history_url = reverse(
+            "wagtailadmin_pages:history", kwargs={"page_id": test_page.id}
+        )
+        self.assertRedirects(
+            response,
+            history_url,
+        )
+
+        response = self.client.get(history_url)
+        self.assertEqual(response.status_code, 200)
+        self.assertContains(
+            response,
+            f"Revision {revision.id} from {revision.created_at.strftime('%d %b %Y %H:%M')} unscheduled from publishing at {go_live_at.strftime('%d %b %Y %H:%M')}.",
+        )
diff --git a/wagtail/tests/test_audit_log.py b/wagtail/tests/test_audit_log.py
--- a/wagtail/tests/test_audit_log.py
+++ b/wagtail/tests/test_audit_log.py
@@ -197,21 +197,25 @@ def test_revision_schedule_revert(self):
         )
 
     def test_revision_cancel_schedule(self):
+        go_live_at = datetime.now() + timedelta(days=1)
         if settings.USE_TZ:
-            self.home_page.go_live_at = timezone.make_aware(
-                datetime.now() + timedelta(days=1)
-            )
-        else:
-            self.home_page.go_live_at = datetime.now() + timedelta(days=1)
+            go_live_at = timezone.make_aware(go_live_at)
+        self.home_page.go_live_at = go_live_at
         revision = self.home_page.save_revision()
         revision.publish()
 
         revision.approved_go_live_at = None
         revision.save(update_fields=["approved_go_live_at"])
 
+        log_entries = PageLogEntry.objects.filter(action="wagtail.schedule.cancel")
+        self.assertEqual(log_entries.count(), 1)
+        self.assertEqual(log_entries[0].data["revision"]["id"], revision.id)
         self.assertEqual(
-            PageLogEntry.objects.filter(action="wagtail.schedule.cancel").count(), 1
+            log_entries[0].data["revision"]["go_live_at"],
+            go_live_at.strftime("%d %b %Y %H:%M"),
         )
+        # The home_page was live already and we've only cancelled the publication of the above revision.
+        self.assertTrue(log_entries[0].data["revision"]["has_live_version"])
 
     def test_page_lock_unlock(self):
         self.home_page.save(log_action="wagtail.lock")