Skip to content

Commit

Permalink
#114 use a new gallery with layout struct block to be able to use a t…
Browse files Browse the repository at this point in the history
…emplate based on the layout -> this needs a body migration
  • Loading branch information
ephes committed Dec 23, 2023
1 parent 15bd0ff commit fc56525
Show file tree
Hide file tree
Showing 9 changed files with 345 additions and 51 deletions.
132 changes: 91 additions & 41 deletions cast/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@
from django.template.loader import TemplateDoesNotExist, get_template
from django.utils.functional import cached_property
from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _
from pygments import highlight
from pygments.formatters import HtmlFormatter
from pygments.lexers import ClassNotFound, get_lexer_by_name
from wagtail.blocks import CharBlock, ChooserBlock, ListBlock, StructBlock, TextBlock
from wagtail.images.blocks import ImageChooserBlock
from wagtail.blocks import CharBlock, ChoiceBlock, ListBlock, StructBlock, TextBlock
from wagtail.images.blocks import ChooserBlock, ImageChooserBlock
from wagtail.images.models import AbstractImage, AbstractRendition

from . import appsettings as settings
from .models import Gallery
from .renditions import (
Height,
ImageForSlot,
Expand Down Expand Up @@ -108,49 +108,99 @@ def get_context(self, image: AbstractImage, parent_context: Optional[dict] = Non
return super().get_context(image, parent_context=parent_context)


def add_prev_next(images: QuerySet[AbstractImage]) -> None:
"""
For each image in the queryset, add the previous and next image.
"""
for previous_image, current_image, next_image in previous_and_next(images):
current_image.prev = "false" if previous_image is None else f"img-{previous_image.pk}"
current_image.next = "false" if next_image is None else f"img-{next_image.pk}"


def add_image_thumbnails(images: QuerySet[AbstractImage], context: dict) -> None:
"""
For each image in the queryset, add the thumbnail and modal image data to the image.
"""
modal_slot, thumbnail_slot = (
Rectangle(Width(w), Height(h)) for w, h in settings.CAST_GALLERY_IMAGE_SLOT_DIMENSIONS
)
for image in images:
fetched_renditions = {
r.filter_spec: r for r in context.get("renditions_for_posts", {}) if r.image_id == image.pk
}
images_for_slots = get_srcset_images_for_slots(image, "gallery", fetched_renditions=fetched_renditions)
image.modal = images_for_slots[modal_slot]
image.thumbnail = images_for_slots[thumbnail_slot]


def prepare_context_for_gallery(images: QuerySet[AbstractImage], context: dict) -> dict:
"""
Add the previous and next image and the thumbnail and modal image data to each
image of the gallery and then the images to the context.
"""
add_prev_next(images)
add_image_thumbnails(images, context=context)
context["images"] = images
return context


def get_gallery_block_template(default_template_name: str, context: Optional[dict]) -> str:
if context is None:
return default_template_name

template_base_dir = context.get("template_base_dir")
if template_base_dir is None:
return default_template_name

template_from_theme = f"cast/{template_base_dir}/gallery.html"
try:
get_template(template_from_theme)
return template_from_theme
except TemplateDoesNotExist:
return default_template_name


class GalleryBlock(ListBlock):
default_template_name = "cast/gallery.html"
class Meta:
icon = "image"
label = "Gallery"
template = "cast/gallery.html"

@staticmethod
def add_prev_next(gallery: QuerySet[Gallery]) -> None:
for previous_image, current_image, next_image in previous_and_next(gallery):
current_image.prev = "false" if previous_image is None else f"img-{previous_image.pk}"
current_image.next = "false" if next_image is None else f"img-{next_image.pk}"
def get_template(self, images: Optional[QuerySet[AbstractImage]] = None, context: Optional[dict] = None) -> str:
default_template_name = super().get_template(images, context)
return get_gallery_block_template(default_template_name, context)

def get_template(self, context: Optional[dict] = None) -> str:
if context is None:
return self.default_template_name
def get_context(self, images: QuerySet[AbstractImage], parent_context: Optional[dict] = None) -> dict:
context = super().get_context(images, parent_context=parent_context)
return prepare_context_for_gallery(images, context)

template_base_dir = context.get("template_base_dir")
if template_base_dir is None:
return self.default_template_name

template_from_theme = f"cast/{template_base_dir}/gallery.html"
try:
get_template(template_from_theme)
return template_from_theme
except TemplateDoesNotExist:
return self.default_template_name

@staticmethod
def add_image_thumbnails(gallery: QuerySet[Gallery], parent_context: dict) -> None:
modal_slot, thumbnail_slot = (
Rectangle(Width(w), Height(h)) for w, h in settings.CAST_GALLERY_IMAGE_SLOT_DIMENSIONS
)
for image in gallery:
fetched_renditions = {
r.filter_spec: r for r in parent_context.get("renditions_for_posts", {}) if r.image_id == image.pk
}
images_for_slots = get_srcset_images_for_slots(image, "gallery", fetched_renditions=fetched_renditions)
image.modal = images_for_slots[modal_slot]
image.thumbnail = images_for_slots[thumbnail_slot]

def get_context(self, gallery: QuerySet[Gallery], parent_context: Optional[dict] = None) -> dict:
if parent_context is None:
parent_context = {}
self.add_prev_next(gallery)
self.add_image_thumbnails(gallery, parent_context=parent_context)
return super().get_context(gallery, parent_context=parent_context)
class GalleryBlockWithLayout(StructBlock):
"""
A gallery block with a layout. The layout parameter controls
which template is used to render the gallery.
"""

gallery = GalleryBlock(ImageChooserBlock())
layout = ChoiceBlock(
choices=[
("default", _("Web Component with Modal")),
("htmx", _("HTMX based layout")),
],
default="default",
)

class Meta:
icon = "image"
label = "Gallery with Layout"

def get_template(self, value=None, context=None):
default_template_name = super().get_template(value, context)
return get_gallery_block_template(default_template_name, context)

def get_context(self, value, parent_context: Optional[dict] = None):
context = super().get_context(value, parent_context=parent_context)
return prepare_context_for_gallery(value["gallery"], context)


class VideoChooserBlock(ChooserBlock):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
# Generated by Django 5.0 on 2023-12-22 22:25

import cast.blocks
import wagtail.blocks
import wagtail.embeds.blocks
import wagtail.fields
import wagtail.images.blocks
from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("cast", "0051_use_own_image_chooser_block"),
]

operations = [
migrations.AlterField(
model_name="blog",
name="template_base_dir",
field=models.CharField(
blank=True,
choices=[
("bootstrap4", "Bootstrap 4"),
("plain", "Just HTML"),
("vue", "Vue.js"),
("bootstrap5", "Bootstrap 5"),
],
default=None,
help_text="The theme to use for this blog implemented as a template base directory. If not set, the template base directory will be determined by a site setting.",
max_length=128,
null=True,
),
),
migrations.AlterField(
model_name="post",
name="body",
field=wagtail.fields.StreamField(
[
(
"overview",
wagtail.blocks.StreamBlock(
[
(
"heading",
wagtail.blocks.CharBlock(
form_classname="full title"
),
),
("paragraph", wagtail.blocks.RichTextBlock()),
(
"code",
wagtail.blocks.StructBlock(
[
(
"language",
wagtail.blocks.CharBlock(
help_text="The language of the code block"
),
),
(
"source",
wagtail.blocks.TextBlock(
help_text="The source code of the block",
rows=8,
),
),
],
icon="code",
),
),
(
"image",
cast.blocks.CastImageChooserBlock(
template="cast/image/image.html"
),
),
(
"gallery",
wagtail.blocks.StructBlock(
[
(
"gallery",
cast.blocks.GalleryBlock(
wagtail.images.blocks.ImageChooserBlock()
),
),
(
"layout",
wagtail.blocks.ChoiceBlock(
choices=[
(
"web_component",
"Web Component with Modal",
),
("htmx", "HTMX based layout"),
]
),
),
]
),
),
("embed", wagtail.embeds.blocks.EmbedBlock()),
(
"video",
cast.blocks.VideoChooserBlock(
icon="media", template="cast/video/video.html"
),
),
(
"audio",
cast.blocks.AudioChooserBlock(
icon="media", template="cast/audio/audio.html"
),
),
]
),
),
(
"detail",
wagtail.blocks.StreamBlock(
[
(
"heading",
wagtail.blocks.CharBlock(
form_classname="full title"
),
),
("paragraph", wagtail.blocks.RichTextBlock()),
(
"code",
wagtail.blocks.StructBlock(
[
(
"language",
wagtail.blocks.CharBlock(
help_text="The language of the code block"
),
),
(
"source",
wagtail.blocks.TextBlock(
help_text="The source code of the block",
rows=8,
),
),
],
icon="code",
),
),
(
"image",
cast.blocks.CastImageChooserBlock(
template="cast/image/image.html"
),
),
(
"gallery",
wagtail.blocks.StructBlock(
[
(
"gallery",
cast.blocks.GalleryBlock(
wagtail.images.blocks.ImageChooserBlock()
),
),
(
"layout",
wagtail.blocks.ChoiceBlock(
choices=[
(
"web_component",
"Web Component with Modal",
),
("htmx", "HTMX based layout"),
]
),
),
]
),
),
("embed", wagtail.embeds.blocks.EmbedBlock()),
(
"video",
cast.blocks.VideoChooserBlock(
icon="media", template="cast/video/video.html"
),
),
(
"audio",
cast.blocks.AudioChooserBlock(
icon="media", template="cast/audio/audio.html"
),
),
]
),
),
],
use_json_field=True,
),
),
migrations.AlterField(
model_name="templatebasedirectory",
name="name",
field=models.CharField(
choices=[
("bootstrap4", "Bootstrap 4"),
("plain", "Just HTML"),
("vue", "Vue.js"),
("bootstrap5", "Bootstrap 5"),
],
default="bootstrap4",
help_text="The theme to use for this site implemented as a template base directory. It's possible to overwrite this setting for each blog.If you want to use a custom theme, you have to create a new directory in your template directory named cast/<your-theme-name>/ and put all required templates in there.",
max_length=128,
),
),
]
23 changes: 23 additions & 0 deletions cast/migrations/0053_rename_default_layout.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 5.0 on 2023-12-23 08:13

import cast.blocks
import wagtail.blocks
import wagtail.embeds.blocks
import wagtail.fields
import wagtail.images.blocks
from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('cast', '0052_alter_blog_template_base_dir_alter_post_body_and_more'),
]

operations = [
migrations.AlterField(
model_name='post',
name='body',
field=wagtail.fields.StreamField([('overview', wagtail.blocks.StreamBlock([('heading', wagtail.blocks.CharBlock(form_classname='full title')), ('paragraph', wagtail.blocks.RichTextBlock()), ('code', wagtail.blocks.StructBlock([('language', wagtail.blocks.CharBlock(help_text='The language of the code block')), ('source', wagtail.blocks.TextBlock(help_text='The source code of the block', rows=8))], icon='code')), ('image', cast.blocks.CastImageChooserBlock(template='cast/image/image.html')), ('gallery', wagtail.blocks.StructBlock([('gallery', cast.blocks.GalleryBlock(wagtail.images.blocks.ImageChooserBlock())), ('layout', wagtail.blocks.ChoiceBlock(choices=[('default', 'Web Component with Modal'), ('htmx', 'HTMX based layout')]))])), ('embed', wagtail.embeds.blocks.EmbedBlock()), ('video', cast.blocks.VideoChooserBlock(icon='media', template='cast/video/video.html')), ('audio', cast.blocks.AudioChooserBlock(icon='media', template='cast/audio/audio.html'))])), ('detail', wagtail.blocks.StreamBlock([('heading', wagtail.blocks.CharBlock(form_classname='full title')), ('paragraph', wagtail.blocks.RichTextBlock()), ('code', wagtail.blocks.StructBlock([('language', wagtail.blocks.CharBlock(help_text='The language of the code block')), ('source', wagtail.blocks.TextBlock(help_text='The source code of the block', rows=8))], icon='code')), ('image', cast.blocks.CastImageChooserBlock(template='cast/image/image.html')), ('gallery', wagtail.blocks.StructBlock([('gallery', cast.blocks.GalleryBlock(wagtail.images.blocks.ImageChooserBlock())), ('layout', wagtail.blocks.ChoiceBlock(choices=[('default', 'Web Component with Modal'), ('htmx', 'HTMX based layout')]))])), ('embed', wagtail.embeds.blocks.EmbedBlock()), ('video', cast.blocks.VideoChooserBlock(icon='media', template='cast/video/video.html')), ('audio', cast.blocks.AudioChooserBlock(icon='media', template='cast/audio/audio.html'))]))], use_json_field=True),
),
]
Loading

0 comments on commit fc56525

Please sign in to comment.