kopia lustrzana https://github.com/wagtail/wagtail
280 wiersze
9.4 KiB
Python
280 wiersze
9.4 KiB
Python
from django import forms
|
|
from django.core.exceptions import ValidationError
|
|
from django.template.loader import render_to_string
|
|
from django.utils.functional import cached_property
|
|
from django.utils.translation import gettext_lazy as _
|
|
|
|
from wagtail.admin.compare import BlockComparison, StructBlockComparison
|
|
from wagtail.blocks import BooleanBlock, CharBlock, ChooserBlock, StructBlock
|
|
from wagtail.blocks.struct_block import StructBlockAdapter, StructBlockValidationError
|
|
from wagtail.images.models import AbstractImage
|
|
from wagtail.telepath import register
|
|
|
|
from .shortcuts import get_rendition_or_not_found
|
|
|
|
|
|
class ImageChooserBlock(ChooserBlock):
|
|
@cached_property
|
|
def target_model(self):
|
|
from wagtail.images import get_image_model
|
|
|
|
return get_image_model()
|
|
|
|
@cached_property
|
|
def widget(self):
|
|
from wagtail.images.widgets import AdminImageChooser
|
|
|
|
return AdminImageChooser()
|
|
|
|
def render_basic(self, value, context=None):
|
|
if value:
|
|
return get_rendition_or_not_found(value, "original").img_tag()
|
|
else:
|
|
return ""
|
|
|
|
def get_comparison_class(self):
|
|
return ImageChooserBlockComparison
|
|
|
|
class Meta:
|
|
icon = "image"
|
|
|
|
|
|
class ImageChooserBlockComparison(BlockComparison):
|
|
def htmlvalue(self, val):
|
|
return render_to_string(
|
|
"wagtailimages/widgets/compare.html",
|
|
{
|
|
"image_a": val,
|
|
"image_b": val,
|
|
},
|
|
)
|
|
|
|
def htmldiff(self):
|
|
return render_to_string(
|
|
"wagtailimages/widgets/compare.html",
|
|
{
|
|
"image_a": self.val_a,
|
|
"image_b": self.val_b,
|
|
},
|
|
)
|
|
|
|
|
|
class ImageBlock(StructBlock):
|
|
"""
|
|
An usage of ImageChooserBlock with support for alt text.
|
|
For backward compatibility, this block overrides necessary methods to change
|
|
the StructValue to be an Image model instance, making it compatible in
|
|
places where ImageChooserBlock was used.
|
|
"""
|
|
|
|
image = ImageChooserBlock(required=True)
|
|
decorative = BooleanBlock(
|
|
default=False, required=False, label=_("Image is decorative")
|
|
)
|
|
alt_text = CharBlock(required=False, label=_("Alt text"))
|
|
|
|
def __init__(self, required=True, **kwargs):
|
|
super().__init__(
|
|
[
|
|
("image", ImageChooserBlock(required=required)),
|
|
(
|
|
"decorative",
|
|
BooleanBlock(
|
|
default=False, required=False, label=_("Image is decorative")
|
|
),
|
|
),
|
|
("alt_text", CharBlock(required=False, label=_("Alt text"))),
|
|
],
|
|
**kwargs,
|
|
)
|
|
|
|
def deconstruct(self):
|
|
"""
|
|
For typical StructBlock subclasses, it makes sense for the deconstructed block object to be a basic StructBlock
|
|
with the child blocks passed to the constructor (because this is largely functionally identical to the
|
|
subclass, and avoids embedding a reference to a potentially-volatile custom class in migrations).
|
|
|
|
This is not the case for ImageBlock, as it overrides enough of StructBlock's behaviour that a basic StructBlock
|
|
is not a suitable substitute - and also has an incompatible constructor signature (as we don't want to support
|
|
passing child blocks to it).
|
|
|
|
Therefore, we opt out of the standard StructBlock deconstruction behaviour here, and always
|
|
deconstruct an ImageBlock as an ImageBlock.
|
|
"""
|
|
return ("wagtail.images.blocks.ImageBlock", [], self._constructor_kwargs)
|
|
|
|
def deconstruct_with_lookup(self, lookup):
|
|
return self.deconstruct()
|
|
|
|
@classmethod
|
|
def construct_from_lookup(cls, lookup, *args, **kwargs):
|
|
return cls(**kwargs)
|
|
|
|
def get_searchable_content(self, value):
|
|
if not self.search_index or not value:
|
|
return []
|
|
|
|
return self.child_blocks["alt_text"].get_searchable_content(
|
|
value.contextual_alt_text
|
|
)
|
|
|
|
def _struct_value_to_image(self, struct_value):
|
|
image = struct_value.get("image")
|
|
decorative = struct_value.get("decorative")
|
|
if image:
|
|
# If the image is decorative, set alt_text to an empty string
|
|
image.contextual_alt_text = (
|
|
"" if decorative else struct_value.get("alt_text")
|
|
)
|
|
image.decorative = decorative
|
|
return image
|
|
|
|
def _image_to_struct_value(self, image):
|
|
return {
|
|
"image": image,
|
|
"alt_text": image and image.contextual_alt_text,
|
|
"decorative": image and image.decorative,
|
|
}
|
|
|
|
def to_python(self, value):
|
|
# For backward compatibility with ImageChooserBlock
|
|
if isinstance(value, int):
|
|
image = self.child_blocks["image"].to_python(value)
|
|
struct_value = {"image": image, "decorative": False, "alt_text": None}
|
|
else:
|
|
struct_value = super().to_python(value)
|
|
return self._struct_value_to_image(struct_value)
|
|
|
|
def bulk_to_python(self, values):
|
|
values = list(values)
|
|
|
|
if values and all(value is None or isinstance(value, int) for value in values):
|
|
# `values` looks like a list of image IDs and/or None values (as we might encounter
|
|
# if an ImageChooserBlock has been changed to an ImageBlock with no data migration)
|
|
image_values = self.child_blocks["image"].bulk_to_python(values)
|
|
|
|
struct_values = [
|
|
{
|
|
"image": image,
|
|
"decorative": False,
|
|
"alt_text": None,
|
|
}
|
|
for image in image_values
|
|
]
|
|
|
|
else:
|
|
# Treat `values` as the standard ImageBlock representation - a (possibly empty) list of
|
|
# dicts containing `image`, `decorative` and `alt_text` keys to be handled by the
|
|
# StructBlock superclass
|
|
struct_values = super().bulk_to_python(values)
|
|
|
|
return [
|
|
self._struct_value_to_image(struct_value) for struct_value in struct_values
|
|
]
|
|
|
|
def value_from_datadict(self, data, files, prefix):
|
|
struct_value = super().value_from_datadict(data, files, prefix)
|
|
return self._struct_value_to_image(struct_value)
|
|
|
|
def clean(self, value):
|
|
try:
|
|
self.child_blocks["image"].clean(value)
|
|
except ValidationError as e:
|
|
raise StructBlockValidationError(
|
|
block_errors={"image": e},
|
|
)
|
|
|
|
if value and not value.contextual_alt_text and not value.decorative:
|
|
raise StructBlockValidationError(
|
|
block_errors={
|
|
"alt_text": ValidationError(
|
|
_(
|
|
"Please add some alt text for your image or mark it as decorative"
|
|
)
|
|
)
|
|
}
|
|
)
|
|
|
|
return value
|
|
|
|
def normalize(self, value):
|
|
if value is None or isinstance(value, AbstractImage):
|
|
return value
|
|
else:
|
|
struct_value = super().normalize(value)
|
|
return self._struct_value_to_image(struct_value)
|
|
|
|
def get_form_context(self, value, prefix="", errors=None):
|
|
dict_value = {
|
|
"image": value,
|
|
"alt_text": value and value.contextual_alt_text,
|
|
"decorative": value and value.decorative,
|
|
}
|
|
context = super().get_form_context(dict_value, prefix=prefix, errors=errors)
|
|
context["suggested_alt_text"] = value
|
|
return context
|
|
|
|
def get_form_state(self, value):
|
|
return {
|
|
"image": self.child_blocks["image"].get_form_state(value),
|
|
"alt_text": value and value.contextual_alt_text,
|
|
"decorative": value and value.decorative,
|
|
}
|
|
|
|
def get_prep_value(self, value):
|
|
return {
|
|
"image": self.child_blocks["image"].get_prep_value(value),
|
|
"alt_text": value and value.contextual_alt_text,
|
|
"decorative": value and value.decorative,
|
|
}
|
|
|
|
def extract_references(self, value):
|
|
return self.child_blocks["image"].extract_references(value)
|
|
|
|
def get_comparison_class(self):
|
|
return ImageBlockComparison
|
|
|
|
def get_api_representation(self, value, context=None):
|
|
return super().get_api_representation(
|
|
self._image_to_struct_value(value), context=context
|
|
)
|
|
|
|
def render_basic(self, value, context=None):
|
|
return self.child_blocks["image"].render_basic(value, context=context)
|
|
|
|
class Meta:
|
|
icon = "image"
|
|
template = "wagtailimages/widgets/image.html"
|
|
|
|
|
|
class ImageBlockAdapter(StructBlockAdapter):
|
|
js_constructor = "wagtail.images.blocks.ImageBlock"
|
|
|
|
@cached_property
|
|
def media(self):
|
|
structblock_media = super().media
|
|
return forms.Media(
|
|
js=structblock_media._js + ["wagtailimages/js/image-block.js"],
|
|
css=structblock_media._css,
|
|
)
|
|
|
|
|
|
register(ImageBlockAdapter(), ImageBlock)
|
|
|
|
|
|
class ImageBlockComparison(StructBlockComparison):
|
|
def __init__(self, block, exists_a, exists_b, val_a, val_b):
|
|
super().__init__(
|
|
block,
|
|
exists_a,
|
|
exists_b,
|
|
block._image_to_struct_value(val_a),
|
|
block._image_to_struct_value(val_b),
|
|
)
|
|
|
|
def htmlvalue(self, val):
|
|
if isinstance(val, AbstractImage):
|
|
return super().htmlvalue(self.block._image_to_struct_value(val))
|
|
else:
|
|
return super().htmlvalue(val)
|