Repository: wagtail/wagtail
File: wagtail/blocks/base.py (Python)

Code before:
        # will be used for a complete view, not just the block. Instead, the
        # default preview context uses `{% include_block %}`, which will use
        # `get_context`.
        return parent_context or {}

    def get_preview_template(self, value, context=None):
        # We do not fall back to `get_template` here, because the template will
        # be used for a complete view, not just the block. In most cases, the
        # block's template cannot stand alone for the preview, as it would be
        # missing the necessary static assets.
        #
        # Instead, the default preview template uses `{% include_block %}`,
        # which will use `get_template` if a template is defined.
        return (
            getattr(self.meta, "preview_template", None)
            or self.DEFAULT_PREVIEW_TEMPLATE
        )

    def get_preview_value(self):
        if hasattr(self.meta, "preview_value"):
            return self.normalize(self.meta.preview_value)
        return self.get_default()

    @cached_property
    def is_previewable(self):
        # To prevent showing a broken preview if the block preview has not been
        # configured, consider the block to be previewable if either:
        # - a specific preview template is configured for the block
        # - a preview value is provided and the global template has been overridden
        # which are the intended ways to configure block previews.
        #
        # If a block is made previewable by other means, the `is_previewable`
        # property should be overridden to return `True`.
        has_specific_template = (
            hasattr(self.meta, "preview_template")
            or self.__class__.get_preview_template is not Block.get_preview_template
        )
        has_preview_value = (
            hasattr(self.meta, "preview_value")
            or getattr(self.meta, "default", None) is not None
            or self.__class__.get_preview_context is not Block.get_preview_context
            or self.__class__.get_preview_value is not Block.get_preview_value
        )
        has_global_template = template_is_overridden(
            self.DEFAULT_PREVIEW_TEMPLATE,
            "templates",
        )
        return has_specific_template or (has_preview_value and has_global_template)

    def get_description(self):
        return getattr(self.meta, "description", "")

Review comment (suggestion):
This is inspired from [Django's `Field.has_default()`](https://github.com/django/django/blob/5a2c1bc07d126ce32efaa157e712a8f3a7457b74/django/db/models/fields/__init__.py#L1014-L1016), but I made it a property so we can easily change the value in `ListBlock.__init__()`.

I'm not sure if this is the best approach, though. An alternative approach might be to remove `Meta.default` in all built-in blocks, and then do a `hasattr(self.meta, "default")` check instead. Then, any "unset" defaults are provided in a `get_default()` implementation, e.g.

```py
# StreamBlock
def get_default(self):
    return super().get_default() or []

# StructBlock
def get_default(self):
    return super().get_default() or {}

# ListBlock
def get_default(self):
    return super().get_default() or [self.child_block.get_default()]
```

Code after:
        # will be used for a complete view, not just the block. Instead, the
        # default preview context uses `{% include_block %}`, which will use
        # `get_context`.
        return parent_context or {}

    def get_preview_template(self, value, context=None):
        # We do not fall back to `get_template` here, because the template will
        # be used for a complete view, not just the block. In most cases, the
        # block's template cannot stand alone for the preview, as it would be
        # missing the necessary static assets.
        #
        # Instead, the default preview template uses `{% include_block %}`,
        # which will use `get_template` if a template is defined.
        return (
            getattr(self.meta, "preview_template", None)
            or self.DEFAULT_PREVIEW_TEMPLATE
        )

    def get_preview_value(self):
        if hasattr(self.meta, "preview_value"):
            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
        # configured, consider the block to be previewable if either:
        # - a specific preview template is configured for the block
        # - a preview value is provided and the global template has been overridden
        # which are the intended ways to configure block previews.
        #
        # If a block is made previewable by other means, the `is_previewable`
        # property should be overridden to return `True`.
        has_specific_template = (
            hasattr(self.meta, "preview_template")
            or self.__class__.get_preview_template is not Block.get_preview_template
        )
        has_preview_value = (
            hasattr(self.meta, "preview_value")
            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
        )
        has_global_template = template_is_overridden(
            self.DEFAULT_PREVIEW_TEMPLATE,
            "templates",
        )