From af23f95c55f8654978f368deae0913bca620f5e5 Mon Sep 17 00:00:00 2001 From: OndraRehounek <ondra.rehounek@seznam.cz> Date: Thu, 21 Apr 2022 11:14:11 +0200 Subject: [PATCH] ArticleMixin: YouTubeVideoBlock --- ...er_districtarticlepage_content_and_more.py | 269 ++++++++++++++++++ ...lter_regionarticlepage_content_and_more.py | 269 ++++++++++++++++++ shared/blocks.py | 80 ++++++ shared/jekyll_import.py | 1 - shared/models.py | 8 +- shared/static/shared/css/helpers.css | 4 + .../styleguide/2.3.x/blocks/video_block.html | 10 + .../0024_alter_uniwebarticlepage_content.py | 88 ++++++ 8 files changed, 727 insertions(+), 2 deletions(-) create mode 100644 district/migrations/0055_alter_districtarticlepage_content_and_more.py create mode 100644 region/migrations/0030_alter_regionarticlepage_content_and_more.py create mode 100644 shared/templates/styleguide/2.3.x/blocks/video_block.html create mode 100644 uniweb/migrations/0024_alter_uniwebarticlepage_content.py diff --git a/district/migrations/0055_alter_districtarticlepage_content_and_more.py b/district/migrations/0055_alter_districtarticlepage_content_and_more.py new file mode 100644 index 00000000..67289e95 --- /dev/null +++ b/district/migrations/0055_alter_districtarticlepage_content_and_more.py @@ -0,0 +1,269 @@ +# Generated by Django 4.0.3 on 2022-04-21 08:57 + +import wagtail.contrib.table_block.blocks +import wagtail.core.blocks +import wagtail.core.fields +import wagtail.images.blocks +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("district", "0054_alter_districtcenterpage_content_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="districtarticlepage", + name="content", + field=wagtail.core.fields.StreamField( + [ + ( + "text", + wagtail.core.blocks.RichTextBlock( + features=[ + "h2", + "h3", + "h4", + "bold", + "italic", + "ol", + "embed", + "ul", + "link", + "document-link", + "image", + ], + label="Textový editor", + ), + ), + ( + "gallery", + wagtail.core.blocks.StructBlock( + [ + ( + "gallery_items", + wagtail.core.blocks.ListBlock( + wagtail.images.blocks.ImageChooserBlock( + label="obrázek", required=True + ), + group="ostatní", + icon="image", + label="Galerie", + ), + ) + ], + label="Galerie", + ), + ), + ( + "youtube", + wagtail.core.blocks.StructBlock( + [ + ( + "video_url", + wagtail.core.blocks.URLBlock( + help_text="Odkaz na YouTube video bude automaticky zkonvertován na ID videa a NEBUDE uložen.", + label="Odkaz na video", + required=False, + ), + ), + ( + "video_id", + wagtail.core.blocks.CharBlock( + help_text="Není třeba vyplňovat, bude automaticky načteno z odkazu.", + label="ID videa (automatické pole)", + required=False, + ), + ), + ], + label="YouTube video", + ), + ), + ], + blank=True, + verbose_name="Článek", + ), + ), + migrations.AlterField( + model_name="districtcenterpage", + name="content", + field=wagtail.core.fields.StreamField( + [ + ("text", wagtail.core.blocks.RichTextBlock()), + ("table", wagtail.contrib.table_block.blocks.TableBlock()), + ], + blank=True, + verbose_name="Obsah", + ), + ), + migrations.AlterField( + model_name="districtcustompage", + name="content", + field=wagtail.core.fields.StreamField( + [ + ("text", wagtail.core.blocks.RichTextBlock()), + ("table", wagtail.contrib.table_block.blocks.TableBlock()), + ( + "people_group", + wagtail.core.blocks.StructBlock( + [ + ( + "group_title", + wagtail.core.blocks.CharBlock( + label="Titulek", required=True + ), + ), + ( + "person_list", + wagtail.core.blocks.ListBlock( + wagtail.core.blocks.PageChooserBlock( + label="Osoba", + page_type=[ + "district.DistrictPersonPage", + "region.RegionPersonPage", + ], + ), + label="List osob", + ), + ), + ] + ), + ), + ], + blank=True, + verbose_name="Obsah", + ), + ), + migrations.AlterField( + model_name="districtprogrampage", + name="content", + field=wagtail.core.fields.StreamField( + [ + ( + "static_program_block", + wagtail.core.blocks.StructBlock( + [ + ( + "headline", + wagtail.core.blocks.CharBlock( + label="Titulek bloku", required=True + ), + ), + ( + "perex", + wagtail.core.blocks.TextBlock( + label="Krátký text pod nadpisem", required=True + ), + ), + ( + "person", + wagtail.core.blocks.PageChooserBlock( + label="Garant", + page_type=["district.DistrictPersonPage"], + ), + ), + ( + "completion_percentage", + wagtail.core.blocks.IntegerBlock( + label="Procento dokončení", required=True + ), + ), + ( + "program_items", + wagtail.core.blocks.ListBlock( + wagtail.core.blocks.StructBlock( + [ + ( + "title", + wagtail.core.blocks.CharBlock( + label="Název", required=True + ), + ), + ( + "completion_percentage", + wagtail.core.blocks.IntegerBlock( + label="Procento dokončení", + required=True, + ), + ), + ] + ), + label="Seznam bodů", + ), + ), + ] + ), + ), + ( + "redmine_program_block", + wagtail.core.blocks.StructBlock( + [ + ( + "headline", + wagtail.core.blocks.CharBlock( + label="Titulek bloku", required=True + ), + ), + ( + "perex", + wagtail.core.blocks.TextBlock( + label="Krátký text pod nadpisem", required=True + ), + ), + ( + "person", + wagtail.core.blocks.PageChooserBlock( + label="Garant", + page_type=["district.DistrictPersonPage"], + ), + ), + ( + "redmine_issue", + wagtail.core.blocks.IntegerBlock( + label="Číslo Redmine issue", required=True + ), + ), + ( + "completion_percentage", + wagtail.core.blocks.IntegerBlock( + help_text="Hodnota se automaticky načte s Redmine", + label="Procento dokončení - bude doplněno automaticky", + required=False, + ), + ), + ( + "program_items", + wagtail.core.blocks.ListBlock( + wagtail.core.blocks.StructBlock( + [ + ( + "title", + wagtail.core.blocks.CharBlock( + label="Název", required=True + ), + ), + ( + "completion_percentage", + wagtail.core.blocks.IntegerBlock( + label="Procento dokončení", + required=True, + ), + ), + ] + ), + help_text="Hodnota se automaticky načte s Redmine", + label="Seznam bodů - bude doplněno automaticky", + required=False, + ), + ), + ] + ), + ), + ], + blank=True, + verbose_name="obsah stránky", + ), + ), + ] diff --git a/region/migrations/0030_alter_regionarticlepage_content_and_more.py b/region/migrations/0030_alter_regionarticlepage_content_and_more.py new file mode 100644 index 00000000..eac00125 --- /dev/null +++ b/region/migrations/0030_alter_regionarticlepage_content_and_more.py @@ -0,0 +1,269 @@ +# Generated by Django 4.0.3 on 2022-04-21 08:57 + +import wagtail.contrib.table_block.blocks +import wagtail.core.blocks +import wagtail.core.fields +import wagtail.images.blocks +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("region", "0029_alter_regioncenterpage_content_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="regionarticlepage", + name="content", + field=wagtail.core.fields.StreamField( + [ + ( + "text", + wagtail.core.blocks.RichTextBlock( + features=[ + "h2", + "h3", + "h4", + "bold", + "italic", + "ol", + "embed", + "ul", + "link", + "document-link", + "image", + ], + label="Textový editor", + ), + ), + ( + "gallery", + wagtail.core.blocks.StructBlock( + [ + ( + "gallery_items", + wagtail.core.blocks.ListBlock( + wagtail.images.blocks.ImageChooserBlock( + label="obrázek", required=True + ), + group="ostatní", + icon="image", + label="Galerie", + ), + ) + ], + label="Galerie", + ), + ), + ( + "youtube", + wagtail.core.blocks.StructBlock( + [ + ( + "video_url", + wagtail.core.blocks.URLBlock( + help_text="Odkaz na YouTube video bude automaticky zkonvertován na ID videa a NEBUDE uložen.", + label="Odkaz na video", + required=False, + ), + ), + ( + "video_id", + wagtail.core.blocks.CharBlock( + help_text="Není třeba vyplňovat, bude automaticky načteno z odkazu.", + label="ID videa (automatické pole)", + required=False, + ), + ), + ], + label="YouTube video", + ), + ), + ], + blank=True, + verbose_name="Článek", + ), + ), + migrations.AlterField( + model_name="regioncenterpage", + name="content", + field=wagtail.core.fields.StreamField( + [ + ("text", wagtail.core.blocks.RichTextBlock()), + ("table", wagtail.contrib.table_block.blocks.TableBlock()), + ], + blank=True, + verbose_name="Obsah", + ), + ), + migrations.AlterField( + model_name="regioncustompage", + name="content", + field=wagtail.core.fields.StreamField( + [ + ("text", wagtail.core.blocks.RichTextBlock()), + ("table", wagtail.contrib.table_block.blocks.TableBlock()), + ( + "people_group", + wagtail.core.blocks.StructBlock( + [ + ( + "group_title", + wagtail.core.blocks.CharBlock( + label="Titulek", required=True + ), + ), + ( + "person_list", + wagtail.core.blocks.ListBlock( + wagtail.core.blocks.PageChooserBlock( + label="Osoba", + page_type=[ + "district.DistrictPersonPage", + "region.RegionPersonPage", + ], + ), + label="List osob", + ), + ), + ] + ), + ), + ], + blank=True, + verbose_name="Obsah", + ), + ), + migrations.AlterField( + model_name="regionprogrampage", + name="content", + field=wagtail.core.fields.StreamField( + [ + ( + "static_program_block", + wagtail.core.blocks.StructBlock( + [ + ( + "headline", + wagtail.core.blocks.CharBlock( + label="Titulek bloku", required=True + ), + ), + ( + "perex", + wagtail.core.blocks.TextBlock( + label="Krátký text pod nadpisem", required=True + ), + ), + ( + "person", + wagtail.core.blocks.PageChooserBlock( + label="Garant", + page_type=["region.RegionPersonPage"], + ), + ), + ( + "completion_percentage", + wagtail.core.blocks.IntegerBlock( + label="Procento dokončení", required=True + ), + ), + ( + "program_items", + wagtail.core.blocks.ListBlock( + wagtail.core.blocks.StructBlock( + [ + ( + "title", + wagtail.core.blocks.CharBlock( + label="Název", required=True + ), + ), + ( + "completion_percentage", + wagtail.core.blocks.IntegerBlock( + label="Procento dokončení", + required=True, + ), + ), + ] + ), + label="Seznam bodů", + ), + ), + ] + ), + ), + ( + "redmine_program_block", + wagtail.core.blocks.StructBlock( + [ + ( + "headline", + wagtail.core.blocks.CharBlock( + label="Titulek bloku", required=True + ), + ), + ( + "perex", + wagtail.core.blocks.TextBlock( + label="Krátký text pod nadpisem", required=True + ), + ), + ( + "person", + wagtail.core.blocks.PageChooserBlock( + label="Garant", + page_type=["region.RegionPersonPage"], + ), + ), + ( + "redmine_issue", + wagtail.core.blocks.IntegerBlock( + label="Číslo Redmine issue", required=True + ), + ), + ( + "completion_percentage", + wagtail.core.blocks.IntegerBlock( + help_text="Hodnota se automaticky načte s Redmine", + label="Procento dokončení - bude doplněno automaticky", + required=False, + ), + ), + ( + "program_items", + wagtail.core.blocks.ListBlock( + wagtail.core.blocks.StructBlock( + [ + ( + "title", + wagtail.core.blocks.CharBlock( + label="Název", required=True + ), + ), + ( + "completion_percentage", + wagtail.core.blocks.IntegerBlock( + label="Procento dokončení", + required=True, + ), + ), + ] + ), + help_text="Hodnota se automaticky načte s Redmine", + label="Seznam bodů - bude doplněno automaticky", + required=False, + ), + ), + ] + ), + ), + ], + blank=True, + verbose_name="obsah stránky", + ), + ), + ] diff --git a/shared/blocks.py b/shared/blocks.py index 846a082a..ecb3382a 100644 --- a/shared/blocks.py +++ b/shared/blocks.py @@ -1,8 +1,13 @@ +import logging +import re + from django.forms.utils import ErrorList from wagtail.core import blocks from wagtail.core.blocks.struct_block import StructBlockValidationError from wagtail.images.blocks import ImageChooserBlock +logger = logging.getLogger(__name__) + class GalleryBlock(blocks.StructBlock): gallery_items = blocks.ListBlock( @@ -14,6 +19,7 @@ class GalleryBlock(blocks.StructBlock): class Meta: label = "Galerie" + icon = "image" template = "styleguide/2.3.x/blocks/gallery_block.html" @@ -57,3 +63,77 @@ class ProgramItemBlock(blocks.StructBlock): completion_percentage = blocks.IntegerBlock( label="Procento dokončení", required=True ) + + +class YouTubeVideoBlock(blocks.StructBlock): + 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["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 "" diff --git a/shared/jekyll_import.py b/shared/jekyll_import.py index 96fe829f..c348b692 100644 --- a/shared/jekyll_import.py +++ b/shared/jekyll_import.py @@ -24,7 +24,6 @@ from markdown.inlinepatterns import InlineProcessor from wagtail.contrib.redirects.models import Redirect from wagtail.core.models import Page from wagtail.core.models.collections import Collection -from wagtail.core.rich_text import RichText from wagtail.images.models import Image from yaml.scanner import ScannerError diff --git a/shared/models.py b/shared/models.py index 09c39f31..adc305d0 100644 --- a/shared/models.py +++ b/shared/models.py @@ -11,7 +11,12 @@ from wagtail.core.fields import StreamField from wagtail.core.models import Page from wagtail.images.edit_handlers import ImageChooserPanel -from shared.blocks import GalleryBlock, MenuItemBlock, MenuParentBlock +from shared.blocks import ( + GalleryBlock, + MenuItemBlock, + MenuParentBlock, + YouTubeVideoBlock, +) class SubpageMixin: @@ -61,6 +66,7 @@ class ArticleMixin(models.Model): RichTextBlock(label="Textový editor", features=RICH_TEXT_FEATURES), ), ("gallery", GalleryBlock(label="Galerie")), + ("youtube", YouTubeVideoBlock(label="YouTube video")), ], verbose_name="Článek", blank=True, diff --git a/shared/static/shared/css/helpers.css b/shared/static/shared/css/helpers.css index 7123673d..781d11ac 100644 --- a/shared/static/shared/css/helpers.css +++ b/shared/static/shared/css/helpers.css @@ -7,6 +7,10 @@ table caption { position: relative; } +.responsive-object.ratio-16-9 { + padding-bottom: calc(100% / 16 * 9); +} + .responsive-object iframe, .responsive-object object, .responsive-object embed { diff --git a/shared/templates/styleguide/2.3.x/blocks/video_block.html b/shared/templates/styleguide/2.3.x/blocks/video_block.html new file mode 100644 index 00000000..31ab43a6 --- /dev/null +++ b/shared/templates/styleguide/2.3.x/blocks/video_block.html @@ -0,0 +1,10 @@ +{% load wagtailimages_tags %} +<div class="content-block responsive-object ratio-16-9 w-full mb-10"> + <iframe + src="https://www.youtube-nocookie.com/embed/{{ self.video_id }}" + title="YouTube video player" + frameborder="0" + allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" + allowfullscreen + ></iframe> +</div> diff --git a/uniweb/migrations/0024_alter_uniwebarticlepage_content.py b/uniweb/migrations/0024_alter_uniwebarticlepage_content.py new file mode 100644 index 00000000..b08c5067 --- /dev/null +++ b/uniweb/migrations/0024_alter_uniwebarticlepage_content.py @@ -0,0 +1,88 @@ +# Generated by Django 4.0.3 on 2022-04-21 08:57 + +import wagtail.core.blocks +import wagtail.core.fields +import wagtail.images.blocks +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("uniweb", "0023_alter_uniwebarticlepage_content"), + ] + + operations = [ + migrations.AlterField( + model_name="uniwebarticlepage", + name="content", + field=wagtail.core.fields.StreamField( + [ + ( + "text", + wagtail.core.blocks.RichTextBlock( + features=[ + "h2", + "h3", + "h4", + "bold", + "italic", + "ol", + "embed", + "ul", + "link", + "document-link", + "image", + ], + label="Textový editor", + ), + ), + ( + "gallery", + wagtail.core.blocks.StructBlock( + [ + ( + "gallery_items", + wagtail.core.blocks.ListBlock( + wagtail.images.blocks.ImageChooserBlock( + label="obrázek", required=True + ), + group="ostatní", + icon="image", + label="Galerie", + ), + ) + ], + label="Galerie", + ), + ), + ( + "youtube", + wagtail.core.blocks.StructBlock( + [ + ( + "video_url", + wagtail.core.blocks.URLBlock( + help_text="Odkaz na YouTube video bude automaticky zkonvertován na ID videa a NEBUDE uložen.", + label="Odkaz na video", + required=False, + ), + ), + ( + "video_id", + wagtail.core.blocks.CharBlock( + help_text="Není třeba vyplňovat, bude automaticky načteno z odkazu.", + label="ID videa (automatické pole)", + required=False, + ), + ), + ], + label="YouTube video", + ), + ), + ], + blank=True, + verbose_name="Článek", + ), + ), + ] -- GitLab