From e4748e45de4fedc60cb428d91fa94d4cc24e410d Mon Sep 17 00:00:00 2001
From: Sage Abdullah <sage.abdullah@torchbox.com>
Date: Wed, 29 Jan 2025 15:21:32 +0000
Subject: [PATCH] Fix Block.is_previewable detection on blocks with non-None
 unset default values

---
 wagtail/blocks/base.py         |  6 +++++-
 wagtail/blocks/list_block.py   |  3 ++-
 wagtail/blocks/stream_block.py |  4 ++++
 wagtail/blocks/struct_block.py |  4 ++++
 wagtail/tests/test_blocks.py   | 11 +++++++++++
 5 files changed, 26 insertions(+), 2 deletions(-)

diff --git a/wagtail/blocks/base.py b/wagtail/blocks/base.py
index e0128ba9af..c37d359a56 100644
--- a/wagtail/blocks/base.py
+++ b/wagtail/blocks/base.py
@@ -298,6 +298,10 @@ class Block(metaclass=BaseBlock):
             return self.normalize(self.meta.preview_value)
         return self.get_default()
 
+    @cached_property
+    def _has_default(self):
+        return getattr(self.meta, "default", None) is not None
+
     @cached_property
     def is_previewable(self):
         # To prevent showing a broken preview if the block preview has not been
@@ -314,7 +318,7 @@ class Block(metaclass=BaseBlock):
         )
         has_preview_value = (
             hasattr(self.meta, "preview_value")
-            or getattr(self.meta, "default", None) is not None
+            or self._has_default
             or self.__class__.get_preview_context is not Block.get_preview_context
             or self.__class__.get_preview_value is not Block.get_preview_value
         )
diff --git a/wagtail/blocks/list_block.py b/wagtail/blocks/list_block.py
index 3fcbdcff9e..c7505e387e 100644
--- a/wagtail/blocks/list_block.py
+++ b/wagtail/blocks/list_block.py
@@ -147,7 +147,8 @@ class ListBlock(Block):
         else:
             self.child_block = child_block
 
-        if not hasattr(self.meta, "default"):
+        self._has_default = hasattr(self.meta, "default")
+        if not self._has_default:
             # Default to a list consisting of one empty (i.e. default-valued) child item
             self.meta.default = [self.child_block.get_default()]
 
diff --git a/wagtail/blocks/stream_block.py b/wagtail/blocks/stream_block.py
index a8ab0f7010..6c38789e83 100644
--- a/wagtail/blocks/stream_block.py
+++ b/wagtail/blocks/stream_block.py
@@ -460,6 +460,10 @@ class BaseStreamBlock(Block):
 
         return errors
 
+    @cached_property
+    def _has_default(self):
+        return self.meta.default is not BaseStreamBlock._meta_class.default
+
     class Meta:
         # No icon specified here, because that depends on the purpose that the
         # block is being used for. Feel encouraged to specify an icon in your
diff --git a/wagtail/blocks/struct_block.py b/wagtail/blocks/struct_block.py
index 05a717cb8c..a4e8f93d23 100644
--- a/wagtail/blocks/struct_block.py
+++ b/wagtail/blocks/struct_block.py
@@ -382,6 +382,10 @@ class BaseStructBlock(Block):
             "prefix": prefix,
         }
 
+    @cached_property
+    def _has_default(self):
+        return self.meta.default is not BaseStructBlock._meta_class.default
+
     class Meta:
         default = {}
         form_classname = "struct-block"
diff --git a/wagtail/tests/test_blocks.py b/wagtail/tests/test_blocks.py
index 3ef4b6c556..6dc4ee0a5a 100644
--- a/wagtail/tests/test_blocks.py
+++ b/wagtail/tests/test_blocks.py
@@ -93,6 +93,11 @@ class TestBlock(SimpleTestCase):
             "specific_template_and_custom_value": [
                 blocks.Block(preview_template="foo.html", preview_value="bar"),
             ],
+            "unset_default_not_none": [
+                blocks.ListBlock(blocks.Block()),
+                blocks.StreamBlock(),
+                blocks.StructBlock(),
+            ],
         }
 
         # Test without a global template override
@@ -109,6 +114,9 @@ class TestBlock(SimpleTestCase):
             # Providing both a preview template and value also makes the block
             # previewable, this is the same as providing a custom template only
             ("specific_template_and_custom_value", True),
+            # These blocks define their own unset default value that is not
+            # `None`, and that value should not make it previewable
+            ("unset_default_not_none", False),
         ]
         for variant, is_previewable in cases:
             with self.subTest(variant=variant, custom_global_template=False):
@@ -134,6 +142,9 @@ class TestBlock(SimpleTestCase):
                 ("custom_value", True),
                 # Unchanged – providing both also makes the block previewable
                 ("specific_template_and_custom_value", True),
+                # Unchanged – even after providing a global template override,
+                # these blocks should not be previewable
+                ("unset_default_not_none", False),
             ]
             for variant, is_previewable in cases:
                 with self.subTest(variant=variant, custom_global_template=True):