-
xaralis authored
- All features in GeoFeatureDetailPage will be converted to FeatureCollection - Each collection may have multiple GeoJSON feature objects, each with its own properties. This enables us to provide customized title & description for individual features. You can set 'title' and 'description' properties and they will be used both in map and on detail page. - Each FeatureCollection sits on its own Leaflet layer. - Having each FeatureCollection on own layer enabled us to do precise muting support: layer will be completely removed/added which greatly helps to avoid visual clutter. - Muting support added to whole geo categories as well! - Fixed map collection block.
xaralis authored- All features in GeoFeatureDetailPage will be converted to FeatureCollection - Each collection may have multiple GeoJSON feature objects, each with its own properties. This enables us to provide customized title & description for individual features. You can set 'title' and 'description' properties and they will be used both in map and on detail page. - Each FeatureCollection sits on its own Leaflet layer. - Having each FeatureCollection on own layer enabled us to do precise muting support: layer will be completely removed/added which greatly helps to avoid visual clutter. - Muting support added to whole geo categories as well! - Fixed map collection block.
blocks.py 20.06 KiB
import logging
import re
import urllib
from django.core.files.images import ImageFile
from django.forms.utils import ErrorList
from wagtail.contrib.table_block.blocks import TableBlock
from wagtail.core import blocks
from wagtail.core.blocks.struct_block import StructBlockValidationError
from wagtail.core.models import Collection
from wagtail.images.blocks import ImageChooserBlock
from wagtail.images.models import Image
from maps_utils.blocks import MapFeatureCollectionBlock, MapPointBlock
from shared.const import RICH_TEXT_DEFAULT_FEATURES
logger = logging.getLogger(__name__)
class GalleryBlock(blocks.StructBlock):
gallery_items = blocks.ListBlock(
ImageChooserBlock(label="obrázek", required=True),
label="Galerie",
icon="image",
group="ostatní",
)
class Meta:
label = "Galerie"
icon = "image"
template = "styleguide/2.3.x/blocks/gallery_block.html"
class FigureBlock(blocks.StructBlock):
img = ImageChooserBlock(label="Obrázek", required=True)
caption = blocks.TextBlock(label="Popisek", required=False)
class Meta:
label = "Obrázek"
icon = "image"
template = "styleguide/2.3.x/blocks/figure_block.html"
class MenuItemBlock(blocks.StructBlock):
title = blocks.CharBlock(label="Titulek", required=True)
page = blocks.PageChooserBlock(label="Stránka", required=False)
link = blocks.URLBlock(label="Odkaz", required=False)
class Meta:
label = "Položka v menu"
template = "styleguide/2.3.x/menu_item.html"
def clean(self, value):
errors = {}
if value["page"] and value["link"]:
errors["page"] = ErrorList(
["Stránka nemůže být vybrána současně s odkazem."]
)
errors["link"] = ErrorList(
["Odkaz nemůže být vybrán současně se stránkou."]
)
if errors:
raise StructBlockValidationError(errors)
return super().clean(value)
class MenuParentBlock(blocks.StructBlock):
title = blocks.CharBlock(label="Titulek", required=True)
menu_items = blocks.ListBlock(
MenuItemBlock(),
)
class Meta:
label = "Podmenu"
template = "styleguide/2.3.x/menu_parent.html"
class ProgramItemBlock(blocks.StructBlock):
title = blocks.CharBlock(label="Název", required=True)
completion_percentage = blocks.IntegerBlock(
label="Procento dokončení", required=True
)
issue_link = blocks.URLBlock(label="Odkaz na Redmine issue", required=False)
class YouTubeVideoBlock(blocks.StructBlock):
poster_image = ImageChooserBlock(
label="Náhled videa (automatické pole)",
required=False,
help_text="Není třeba vyplňovat, náhled bude " "dohledán automaticky.",
)
video_url = blocks.URLBlock(
label="Odkaz na video",
required=False,
help_text="Odkaz na YouTube video bude automaticky "
"zkonvertován na ID videa a NEBUDE uložen.",
)
video_id = blocks.CharBlock(
label="ID videa (automatické pole)",
required=False,
help_text="Není třeba vyplňovat, bude automaticky " "načteno z odkazu.",
)
class Meta:
label = "YouTube video"
icon = "media"
template = "styleguide/2.3.x/blocks/video_block.html"
def clean(self, value):
errors = {}
if not value["video_url"] and not value["video_id"]:
errors["video_url"] = ErrorList(["Zadejte prosím odkaz na YouTube video."])
if value["video_url"]:
if not value["video_url"].startswith("https://youtu.be") and not value[
"video_url"
].startswith("https://www.youtube.com"):
errors["video_url"] = ErrorList(
[
'Odkaz na video musí začínat "https://www.youtube.com" '
'nebo "https://youtu.be"'
]
)
if value["video_id"]:
if not re.match("^[A-Za-z0-9_-]{11}$", value["video_id"]):
errors["video_url"] = ErrorList(
["Formát ID YouTube videa není validní"]
)
if errors:
raise StructBlockValidationError(errors)
return super().clean(value)
def get_prep_value(self, value):
value = super().get_prep_value(value)
if value["video_url"]:
value["video_id"] = self.convert_youtube_link_to_video_id(
value["video_url"]
)
value["poster_image"] = self.get_wagtail_image_id_for_youtube_poster(
value["video_id"]
)
value["video_url"] = ""
return value
@staticmethod
def convert_youtube_link_to_video_id(url):
reg_str = (
"((?<=(v|V)/)|(?<=youtu\.be/)|(?<=youtube\.com/watch(\?|\&)v=)"
"|(?<=embed/))([\w-]+)"
)
search_result = re.search(reg_str, url)
if search_result:
return search_result.group(0)
logger.warning(
"Nepodařilo se získat video ID z YouTube URL", extra={"url": url}
)
return ""
@classmethod
def get_wagtail_image_id_for_youtube_poster(cls, video_id) -> int or None:
image_url = "https://img.youtube.com/vi/{}/hqdefault.jpg".format(video_id)
img_path = "/tmp/{}.jpg".format(video_id)
urllib.request.urlretrieve(image_url, img_path)
file = ImageFile(open(img_path, "rb"), name=img_path)
return cls.get_image_id(file, "YT_poster_v_{}".format(video_id))
@classmethod
def get_image_id(cls, file: ImageFile, image_title: str) -> int:
try:
image = Image.objects.get(title=image_title)
except Image.DoesNotExist:
image = Image(
title=image_title, file=file, collection=cls.get_posters_collection()
)
image.save()
return image.id
@staticmethod
def get_posters_collection() -> Collection:
collection_name = "YouTube nahledy"
try:
collection = Collection.objects.get(name=collection_name)
except Collection.DoesNotExist:
root_collection = Collection.get_first_root_node()
collection = root_collection.add_child(name=collection_name)
return collection
class CardBlock(blocks.StructBlock):
img = ImageChooserBlock(label="Obrázek", required=False)
elevation = blocks.IntegerBlock(
label="Velikost stínu",
min_value=0,
max_value=21,
help_text="0 = žádný stín, 21 = maximální stín",
default=2,
)
headline = blocks.TextBlock(label="Titulek", required=False)
hoveractive = blocks.BooleanBlock(
label="Zvýraznit stín na hover",
default=False,
help_text="Pokud je zapnuto, stín se zvýrazní, když na kartu uživatel najede myší.",
required=False,
)
content = blocks.StreamBlock(
label="Obsah",
local_blocks=[
(
"text",
blocks.RichTextBlock(
label="Textový editor", features=RICH_TEXT_DEFAULT_FEATURES
),
),
("table", TableBlock(template="shared/blocks/table_block.html")),
("figure", FigureBlock()),
("youtube", YouTubeVideoBlock()),
("map_point", MapPointBlock(label="Špendlík na mapě")),
("map_collection", MapFeatureCollectionBlock(label="Mapová kolekce")),
],
required=False,
)
page = blocks.PageChooserBlock(label="Stránka", required=False)
link = blocks.URLBlock(label="Odkaz", required=False)
class Meta:
label = "Karta"
icon = "form"
template = "styleguide/2.3.x/blocks/card_block.html"
def clean(self, value):
errors = {}
if value["page"] and value["link"]:
errors["page"] = ErrorList(
["Stránka nemůže být vybrána současně s odkazem."]
)
errors["link"] = ErrorList(
["Odkaz nemůže být vybrán současně se stránkou."]
)
if errors:
raise StructBlockValidationError(errors)
return super().clean(value)
class ButtonBlock(blocks.StructBlock):
title = blocks.CharBlock(label="Titulek", max_length=128, required=True)
icon = blocks.CharBlock(
label="Ikonka",
max_length=128,
required=False,
help_text="Identifikátor ikonky ze styleguide (https://styleguide.pirati.cz/latest/?p=viewall-atoms-icons), např. ico--key.",
)
size = blocks.ChoiceBlock(
choices=(("sm", "Malá"), ("base", "Střední"), ("lg", "Velká")),
label="Velikost",
default="base",
)
color = blocks.ChoiceBlock(
choices=(
("black", "Černá"),
("white", "Bílá"),
("grey-125", "Světle šedá"),
("blue-300", "Modrá"),
("cyan-200", "Tyrkysová"),
("green-400", "Zelené"),
("violet-400", "Vínová"),
("red-600", "Červená"),
),
label="Barva",
default="base",
)
hoveractive = blocks.BooleanBlock(
label="Animovat na hover",
default=True,
help_text="Pokud je zapnuto, tlačítko mění barvu, když na něj uživatel najede myší.",
required=False,
)
mobile_fullwidth = blocks.BooleanBlock(
label="Plná šířka na mobilních zařízeních",
default=True,
help_text="Pokud je zapnuto, tlačítko se na mobilních zařízeních roztáhne na plnou šířku.",
required=False,
)
page = blocks.PageChooserBlock(label="Stránka", required=False)
link = blocks.URLBlock(label="Odkaz", required=False)
align = blocks.ChoiceBlock(
choices=(
("auto", "Automaticky"),
("center", "Na střed"),
),
label="Zarovnání",
default="auto",
)
class Meta:
label = "Tlačítko"
icon = "code"
template = "styleguide/2.3.x/blocks/button_block.html"
def clean(self, value):
errors = {}
if value["page"] and value["link"]:
errors["page"] = ErrorList(
["Stránka nemůže být vybrána současně s odkazem."]
)
errors["link"] = ErrorList(
["Odkaz nemůže být vybrán současně se stránkou."]
)
if not value["page"] and not value["link"]:
errors["page"] = ErrorList(["Stránka nebo odkaz musí být vyplněna."])
errors["link"] = ErrorList(["Stránka nebo odkaz musí být vyplněna."])
if errors:
raise StructBlockValidationError(errors)
return super().clean(value)
class ButtonGroupBlock(blocks.StructBlock):
buttons = blocks.ListBlock(ButtonBlock(), label="Tlačítka")
class Meta:
label = "Skupina tlačítek"
icon = "list-ul"
template = "styleguide/2.3.x/blocks/button_group_block.html"
class HeadlineBlock(blocks.StructBlock):
headline = blocks.CharBlock(label="Headline", max_length=300, required=True)
style = blocks.ChoiceBlock(
choices=(
("head-alt-xl", "Bebas XL"),
("head-alt-lg", "Bebas L"),
("head-alt-md", "Bebas M"),
("head-alt-base", "Bebas base"),
("head-alt-sm", "Bebas SM"),
("head-alt-xs", "Bebas XS"),
("head-alt-2xs", "Bebas 2XS"),
("head-heavy-base", "Roboto base"),
("head-heavy-sm", "Roboto SM"),
("head-heavy-xs", "Roboto XS"),
("head-heavy-2xs", "Roboto 2XS"),
("head-allcaps-2xs", "Allcaps 2XS"),
("head-allcaps-3xs", "Allcaps 3XS"),
("head-allcaps-4xs", "Allcaps 4XS"),
("head-heavy-allcaps-2xs", "Allcaps heavy 2XS"),
("head-heavy-allcaps-3xs", "Allcaps heavy 3XS"),
("head-heavy-allcaps-4xs", "Allcaps heavy 4XS"),
),
label="Styl",
help_text="Náhled si prohlédněte na https://styleguide.pir-test.eu/latest/?p=viewall-atoms-text.",
default="head-alt-md",
required=True,
)
tag = blocks.ChoiceBlock(
choices=(
("h1", "H1"),
("h2", "H2"),
("h3", "H3"),
("h4", "H4"),
("h5", "H5"),
("h6", "H6"),
),
label="Úroveň nadpisu",
help_text="Čím nižší číslo, tím vyšší úroveň.",
required=True,
default="h1",
)
align = blocks.ChoiceBlock(
choices=(
("auto", "Automaticky"),
("center", "Na střed"),
),
label="Zarovnání",
default="auto",
required=True,
)
def get_context(self, value, parent_context=None):
context = super().get_context(value, parent_context)
context["responsive_style"] = {
"head-alt-xl": "head-alt-lg md:head-alt-xl",
"head-alt-lg": "head-alt-md md:head-alt-lg",
"head-alt-md": "head-alt-md",
"head-alt-base": "head-alt-base",
"head-alt-sm": "head-alt-sm",
"head-alt-xs": "head-alt-xs",
"head-alt-2xs": "head-alt-2xs",
"head-heavy-base": "head-heavy-base",
"head-heavy-sm": "head-heavy-sm",
"head-heavy-xs": "head-heavy-xs",
"head-heavy-2xs": "head-heavy-2xs",
"head-allcaps-2xs": "head-allcaps-2xs",
"head-allcaps-3xs": "head-allcaps-3xs",
"head-allcaps-4xs": "head-allcaps-4xs",
"head-heavy-allcaps-2xs": "head-heavy-allcaps-2xs",
"head-heavy-allcaps-3xs": "head-heavy-allcaps-3xs",
"head-heavy-allcaps-4xs": "head-heavy-allcaps-4xs",
}[value["style"]]
return context
class Meta:
label = "Headline"
icon = "bold"
template = "styleguide/2.3.x/blocks/headline_block.html"
class TwoColumnBlock(blocks.StructBlock):
left_column_content = blocks.StreamBlock(
label="Obsah levého sloupce",
local_blocks=[
(
"text",
blocks.RichTextBlock(
label="Textový editor", features=RICH_TEXT_DEFAULT_FEATURES
),
),
("table", TableBlock(template="shared/blocks/table_block.html")),
("card", CardBlock()),
("figure", FigureBlock()),
("youtube", YouTubeVideoBlock()),
("map_point", MapPointBlock(label="Špendlík na mapě")),
("map_collection", MapFeatureCollectionBlock(label="Mapová kolekce")),
("button", ButtonBlock()),
("button_group", ButtonGroupBlock()),
],
required=True,
)
right_column_content = blocks.StreamBlock(
label="Obsah pravého sloupce",
local_blocks=[
(
"text",
blocks.RichTextBlock(
label="Textový editor", features=RICH_TEXT_DEFAULT_FEATURES
),
),
("table", TableBlock(template="shared/blocks/table_block.html")),
("card", CardBlock()),
("figure", FigureBlock()),
("youtube", YouTubeVideoBlock()),
("map_point", MapPointBlock(label="Špendlík na mapě")),
("map_collection", MapFeatureCollectionBlock(label="Mapová kolekce")),
("button", ButtonBlock()),
("button_group", ButtonGroupBlock()),
],
required=True,
)
class Meta:
label = "Dva sloupce"
icon = "grip"
template = "styleguide/2.3.x/blocks/two_column_block.html"
class ThreeColumnBlock(blocks.StructBlock):
left_column_content = blocks.StreamBlock(
label="Obsah levého sloupce",
local_blocks=[
(
"text",
blocks.RichTextBlock(
label="Textový editor", features=RICH_TEXT_DEFAULT_FEATURES
),
),
("table", TableBlock(template="shared/blocks/table_block.html")),
("card", CardBlock()),
("figure", FigureBlock()),
("youtube", YouTubeVideoBlock()),
("map_point", MapPointBlock(label="Špendlík na mapě")),
("map_collection", MapFeatureCollectionBlock(label="Mapová kolekce")),
("button", ButtonBlock()),
("button_group", ButtonGroupBlock()),
],
required=True,
)
middle_column_content = blocks.StreamBlock(
label="Obsah prostředního sloupce",
local_blocks=[
(
"text",
blocks.RichTextBlock(
label="Textový editor", features=RICH_TEXT_DEFAULT_FEATURES
),
),
("table", TableBlock(template="shared/blocks/table_block.html")),
("card", CardBlock()),
("figure", FigureBlock()),
("youtube", YouTubeVideoBlock()),
("map_point", MapPointBlock(label="Špendlík na mapě")),
("map_collection", MapFeatureCollectionBlock(label="Mapová kolekce")),
("button", ButtonBlock()),
("button_group", ButtonGroupBlock()),
],
required=True,
)
right_column_content = blocks.StreamBlock(
label="Obsah pravého sloupce",
local_blocks=[
(
"text",
blocks.RichTextBlock(
label="Textový editor", features=RICH_TEXT_DEFAULT_FEATURES
),
),
("table", TableBlock(template="shared/blocks/table_block.html")),
("card", CardBlock()),
("figure", FigureBlock()),
("youtube", YouTubeVideoBlock()),
("map_point", MapPointBlock(label="Špendlík na mapě")),
("map_collection", MapFeatureCollectionBlock(label="Mapová kolekce")),
("button", ButtonBlock()),
("button_group", ButtonGroupBlock()),
],
required=True,
)
class Meta:
label = "Tři sloupce"
icon = "grip"
template = "styleguide/2.3.x/blocks/three_column_block.html"
class ImageBannerBlock(blocks.StructBlock):
image = ImageChooserBlock(
label="Obrázek",
required=True,
)
headline = blocks.CharBlock(label="Headline", max_length=128, required=True)
content = blocks.StreamBlock(
label="Obsah pravého sloupce",
local_blocks=[
(
"text",
blocks.RichTextBlock(
label="Textový editor",
features=(
"h2",
"h3",
"h4",
"h5",
"bold",
"italic",
"ol",
"ul",
"hr",
"link",
"document-link",
"superscript",
"subscript",
"strikethrough",
"blockquote",
),
),
),
("button", ButtonBlock()),
("button_group", ButtonGroupBlock()),
],
required=False,
)
class Meta:
label = "Obrázkový banner"
icon = "image"
template = "styleguide/2.3.x/blocks/image_banner_block.html"
DEFAULT_CONTENT_BLOCKS = [
(
"text",
blocks.RichTextBlock(
label="Textový editor", features=RICH_TEXT_DEFAULT_FEATURES
),
),
("headline", HeadlineBlock()),
("table", TableBlock(template="shared/blocks/table_block.html")),
("gallery", GalleryBlock(label="Galerie")),
("figure", FigureBlock()),
("card", CardBlock()),
("two_columns", TwoColumnBlock()),
("three_columns", ThreeColumnBlock()),
("youtube", YouTubeVideoBlock(label="YouTube video")),
("map_point", MapPointBlock(label="Špendlík na mapě")),
("map_collection", MapFeatureCollectionBlock(label="Mapová kolekce")),
("button", ButtonBlock()),
("button_group", ButtonGroupBlock()),
("image_banner", ImageBannerBlock()),
]