diff --git a/network-api/networkapi/mozfest/customblocks/__init__.py b/network-api/networkapi/mozfest/customblocks/__init__.py new file mode 100644 index 00000000000..1a1c7d2b609 --- /dev/null +++ b/network-api/networkapi/mozfest/customblocks/__init__.py @@ -0,0 +1,2 @@ +# flake8: noqa +from .card_block import MozfestCardGridBlock diff --git a/network-api/networkapi/mozfest/customblocks/card_block.py b/network-api/networkapi/mozfest/customblocks/card_block.py new file mode 100644 index 00000000000..90703b2871e --- /dev/null +++ b/network-api/networkapi/mozfest/customblocks/card_block.py @@ -0,0 +1,38 @@ +from wagtail import blocks +from wagtail.images.blocks import ImageChooserBlock + +""" +The following card and grid definitions are specifically for Mozfest. +They are based on wagtailpages.pagemodels.customblocks.card_grid.py +but have been modified to fit the Mozfest design. +""" + + +class MozfestCardGrid(blocks.StructBlock): + image = ImageChooserBlock() + + alt_text = blocks.CharBlock(required=False, help_text="Alt text for card's image.") + + title = blocks.CharBlock(help_text="Heading for the card.") + + body = blocks.TextBlock(help_text="Body text of the card.", required=False) + + link_url = blocks.CharBlock( + required=False, + help_text="Optional URL that this card should link out to. " "(Note: If left blank, link will not render.) ", + ) + + link_label = blocks.CharBlock( + required=False, + help_text="Optional Label for the URL link above. " "(Note: If left blank, link will not render.) ", + ) + + +class MozfestCardGridBlock(blocks.StructBlock): + heading = blocks.CharBlock(help_text="Heading for the block.") + cards = blocks.ListBlock(MozfestCardGrid(), help_text="Please use a minimum of 2 cards.") + + class Meta: + icon = "placeholder" + template = "fragments/blocks/mozfest_card_grid_block.html" + label = "Mozfest Card Grid" diff --git a/network-api/networkapi/mozfest/factory.py b/network-api/networkapi/mozfest/factory.py index a1854fc8b75..68ae099e6e8 100644 --- a/network-api/networkapi/mozfest/factory.py +++ b/network-api/networkapi/mozfest/factory.py @@ -10,7 +10,7 @@ from networkapi.wagtailpages.factory.image_factory import ImageFactory from networkapi.wagtailpages.factory.signup import SignupFactory -streamfield_fields = ["paragraph", "image", "spacer", "quote"] +streamfield_fields = ["paragraph", "image", "spacer", "quote", "mozfest_card_grid"] Faker.add_provider(StreamfieldProvider) is_review_app = False diff --git a/network-api/networkapi/mozfest/migrations/0031_alter_mozfestprimarypage_body.py b/network-api/networkapi/mozfest/migrations/0031_alter_mozfestprimarypage_body.py new file mode 100644 index 00000000000..02e001ad6fd --- /dev/null +++ b/network-api/networkapi/mozfest/migrations/0031_alter_mozfestprimarypage_body.py @@ -0,0 +1,1661 @@ +# Generated by Django 3.2.23 on 2023-12-11 16:31 + +import wagtail.blocks +import wagtail.blocks.static_block +import wagtail.documents.blocks +import wagtail.embeds.blocks +import wagtail.fields +import wagtail.images.blocks +import wagtail.snippets.blocks +import wagtailmedia.blocks +from django.db import migrations + +import networkapi.wagtailpages.pagemodels.blog.blog_topic +import networkapi.wagtailpages.pagemodels.profiles + + +class Migration(migrations.Migration): + dependencies = [ + ("mozfest", "0030_adds_new_banner_fields"), + ] + + operations = [ + migrations.AlterField( + model_name="mozfestprimarypage", + name="body", + field=wagtail.fields.StreamField( + [ + ( + "paragraph", + wagtail.blocks.RichTextBlock( + features=[ + "bold", + "italic", + "link", + "h2", + "h3", + "h4", + "h5", + "ol", + "ul", + "hr", + "document-link", + "large", + ], + template="wagtailpages/blocks/rich_text_block.html", + ), + ), + ( + "card_grid", + wagtail.blocks.StructBlock( + [ + ( + "cards", + wagtail.blocks.ListBlock( + wagtail.blocks.StructBlock( + [ + ("image", wagtail.images.blocks.ImageChooserBlock()), + ( + "alt_text", + wagtail.blocks.CharBlock( + help_text="Alt text for card's image.", required=False + ), + ), + ("title", wagtail.blocks.CharBlock(help_text="Heading for the card.")), + ("body", wagtail.blocks.TextBlock(help_text="Body text of the card.")), + ( + "link_url", + wagtail.blocks.CharBlock( + help_text="Optional URL that this card should link out to. (Note: If left blank, link will not render.) ", + required=False, + ), + ), + ( + "link_label", + wagtail.blocks.CharBlock( + help_text="Optional Label for the URL link above. (Note: If left blank, link will not render.) ", + required=False, + ), + ), + ] + ), + help_text="Please use a minimum of 2 cards.", + ), + ) + ] + ), + ), + ( + "image", + wagtail.blocks.StructBlock( + [ + ("image", wagtail.images.blocks.ImageChooserBlock()), + ( + "altText", + wagtail.blocks.CharBlock( + help_text="Image description (for screen readers).", required=True + ), + ), + ("caption", wagtail.blocks.CharBlock(required=False)), + ( + "captionURL", + wagtail.blocks.CharBlock( + help_text="Optional URL that this caption should link out to.", required=False + ), + ), + ( + "image_width", + wagtail.blocks.ChoiceBlock( + choices=[("normal", "Normal"), ("wide", "Wide"), ("full_width", "Full Width")], + help_text="Wide images are col-12, Full-Width Images reach both ends of the screen (16:6 images recommended for full width)", + ), + ), + ] + ), + ), + ( + "image_text", + wagtail.blocks.StructBlock( + [ + ("image", wagtail.images.blocks.ImageChooserBlock()), + ( + "altText", + wagtail.blocks.CharBlock( + help_text="Image description (for screen readers).", required=True + ), + ), + ( + "text", + wagtail.blocks.RichTextBlock( + features=[ + "bold", + "italic", + "link", + "h2", + "h3", + "h4", + "h5", + "ol", + "ul", + "hr", + "document-link", + ] + ), + ), + ( + "url", + wagtail.blocks.CharBlock( + help_text="Optional URL that this image should link out to.", required=False + ), + ), + ( + "top_divider", + wagtail.blocks.BooleanBlock( + help_text="Optional divider above content block.", required=False + ), + ), + ( + "bottom_divider", + wagtail.blocks.BooleanBlock( + help_text="Optional divider below content block.", required=False + ), + ), + ] + ), + ), + ( + "image_text_mini", + wagtail.blocks.StructBlock( + [ + ("image", wagtail.images.blocks.ImageChooserBlock()), + ( + "altText", + wagtail.blocks.CharBlock( + help_text="Image description (for screen readers).", required=True + ), + ), + ("text", wagtail.blocks.RichTextBlock(features=["bold", "italic", "link"])), + ] + ), + ), + ( + "image_grid", + wagtail.blocks.StructBlock( + [ + ( + "grid_items", + wagtail.blocks.ListBlock( + wagtail.blocks.StructBlock( + [ + ("image", wagtail.images.blocks.ImageChooserBlock()), + ( + "alt_text", + wagtail.blocks.CharBlock( + help_text="Alt text for this image.", required=False + ), + ), + ( + "caption", + wagtail.blocks.CharBlock( + help_text="Please remember to properly attribute any images we use.", + required=False, + ), + ), + ( + "url", + wagtail.blocks.CharBlock( + help_text="Optional URL that this figure should link out to.", + required=False, + ), + ), + ( + "square_image", + wagtail.blocks.BooleanBlock( + default=True, + help_text="If left checked, the image will be cropped to be square.", + required=False, + ), + ), + ] + ) + ), + ) + ] + ), + ), + ( + "video", + wagtail.blocks.StructBlock( + [ + ( + "url", + wagtail.blocks.CharBlock( + help_text="For YouTube: go to your YouTube video and click “Share,” then “Embed,” and then copy and paste the provided URL only. For example: https://www.youtube.com/embed/3FIVXBawyQw For Vimeo: follow similar steps to grab the embed URL. For example: https://player.vimeo.com/video/9004979", + label="Embed URL", + ), + ), + ("caption", wagtail.blocks.CharBlock(required=False)), + ( + "captionURL", + wagtail.blocks.CharBlock( + help_text="Optional URL for caption to link to.", required=False + ), + ), + ( + "video_width", + wagtail.blocks.ChoiceBlock( + choices=[("normal", "Normal"), ("wide", "Wide"), ("full_width", "Full Width")], + help_text="Wide videos are col-12, Full-Width videos reach both ends of the screen.", + ), + ), + ] + ), + ), + ( + "iframe", + wagtail.blocks.StructBlock( + [ + ( + "url", + wagtail.blocks.CharBlock( + help_text="Please note that only URLs from allow-listed domains will work." + ), + ), + ( + "height", + wagtail.blocks.IntegerBlock( + help_text="Optional integer pixel value for custom iFrame height", + required=False, + ), + ), + ("caption", wagtail.blocks.CharBlock(required=False)), + ( + "captionURL", + wagtail.blocks.CharBlock( + help_text="Optional URL that this caption should link out to.", required=False + ), + ), + ( + "iframe_width", + wagtail.blocks.ChoiceBlock( + choices=[("normal", "Normal"), ("wide", "Wide"), ("full_width", "Full Width")], + help_text="Wide iframes are col-12, Full-Width iframes reach both ends of the screen", + ), + ), + ( + "disable_scroll", + wagtail.blocks.BooleanBlock( + default=False, + help_text='Checking this will add "scrolling=no" to the iframe. Use this if your iframe is rendering an unnecessary scroll bar or whitespace below it.', + required=False, + ), + ), + ] + ), + ), + ( + "linkbutton", + wagtail.blocks.StructBlock( + [ + ("label", wagtail.blocks.CharBlock()), + ("URL", wagtail.blocks.CharBlock()), + ( + "styling", + wagtail.blocks.ChoiceBlock( + choices=[ + ("btn-primary", "Primary button"), + ("btn-secondary", "Secondary button"), + ] + ), + ), + ] + ), + ), + ( + "spacer", + wagtail.blocks.StructBlock( + [ + ( + "size", + wagtail.blocks.ChoiceBlock( + choices=[ + ("1", "quarter spacing"), + ("2", "half spacing"), + ("3", "single spacing"), + ("4", "one and a half spacing"), + ("5", "triple spacing"), + ] + ), + ) + ] + ), + ), + ( + "single_quote", + wagtail.blocks.StructBlock( + [ + ("quote", wagtail.blocks.RichTextBlock(features=["bold"])), + ("attribution", wagtail.blocks.CharBlock(required=False)), + ( + "attribution_info", + wagtail.blocks.RichTextBlock(features=["bold", "link", "large"], required=False), + ), + ] + ), + ), + ( + "pulse_listing", + wagtail.blocks.StructBlock( + [ + ( + "search_terms", + wagtail.blocks.CharBlock( + help_text="Test your search at mozillapulse.org/search", + label="Search", + required=False, + ), + ), + ( + "max_number_of_results", + wagtail.blocks.IntegerBlock( + default=6, + help_text="Choose 1-12. If you want visitors to see more, link to a search or tag on Pulse.", + max_value=12, + min_value=0, + required=True, + ), + ), + ( + "only_featured_entries", + wagtail.blocks.BooleanBlock( + default=False, + help_text="Featured items are selected by Pulse moderators.", + label="Display only featured entries", + required=False, + ), + ), + ( + "newest_first", + wagtail.blocks.ChoiceBlock( + choices=[ + ("True", "Show newer entries first"), + ("False", "Show older entries first"), + ], + label="Sort", + ), + ), + ( + "advanced_filter_header", + wagtail.blocks.static_block.StaticBlock( + admin_text="-------- ADVANCED FILTERS: OPTIONS TO DISPLAY FEWER, MORE TARGETED RESULTS. --------", + label=" ", + ), + ), + ( + "issues", + wagtail.blocks.ChoiceBlock( + choices=[ + ("all", "All"), + ("Decentralization", "Decentralization"), + ("Digital Inclusion", "Digital Inclusion"), + ("Online Privacy & Security", "Online Privacy & Security"), + ("Open Innovation", "Open Innovation"), + ("Web Literacy", "Web Literacy"), + ] + ), + ), + ( + "help", + wagtail.blocks.ChoiceBlock( + choices=[ + ("all", "All"), + ("Attend", "Attend"), + ("Create content", "Create content"), + ("Code", "Code"), + ("Design", "Design"), + ("Fundraise", "Fundraise"), + ("Join community", "Join community"), + ("Localize & translate", "Localize & translate"), + ("Mentor", "Mentor"), + ("Plan & organize", "Plan & organize"), + ("Promote", "Promote"), + ("Take action", "Take action"), + ("Test & feedback", "Test & feedback"), + ("Write documentation", "Write documentation"), + ], + label="Type of help needed", + ), + ), + ( + "direct_link", + wagtail.blocks.BooleanBlock( + default=False, + help_text="Checked: user goes to project link. Unchecked: user goes to pulse entry", + label="Direct link", + required=False, + ), + ), + ] + ), + ), + ( + "profile_listing", + wagtail.blocks.StructBlock( + [ + ( + "max_number_of_results", + wagtail.blocks.IntegerBlock( + default=12, + help_text="Pick up to 48 profiles.", + max_value=48, + min_value=1, + required=True, + ), + ), + ( + "advanced_filter_header", + wagtail.blocks.static_block.StaticBlock( + admin_text="-------- ADVANCED FILTERS: OPTIONS TO DISPLAY FEWER, MORE TARGETED RESULTS. --------", + label=" ", + ), + ), + ( + "profile_type", + wagtail.blocks.CharBlock(default="", help_text="Example: Fellow.", required=False), + ), + ( + "program_type", + wagtail.blocks.CharBlock( + default="", help_text="Example: Tech Policy.", required=False + ), + ), + ("year", wagtail.blocks.CharBlock(default="", required=False)), + ] + ), + ), + ( + "profile_by_id", + wagtail.blocks.StructBlock( + [ + ( + "ids", + wagtail.blocks.CharBlock( + help_text="Show profiles for pulse users with specific profile ids (mozillapulse.org/profile/[##]). For multiple profiles, specify a comma separated list (e.g. 85,105,332).", + label="Profile by ID", + ), + ) + ] + ), + ), + ( + "profile_directory", + wagtail.blocks.StructBlock( + [ + ( + "max_number_of_results", + wagtail.blocks.IntegerBlock( + default=12, + help_text="Pick up to 48 profiles.", + max_value=48, + min_value=1, + required=True, + ), + ), + ( + "advanced_filter_header", + wagtail.blocks.static_block.StaticBlock( + admin_text="-------- ADVANCED FILTERS: OPTIONS TO DISPLAY FEWER, MORE TARGETED RESULTS. --------", + label=" ", + ), + ), + ( + "profile_type", + wagtail.blocks.CharBlock(default="", help_text="Example: Fellow.", required=False), + ), + ( + "program_type", + wagtail.blocks.CharBlock( + default="", help_text="Example: Tech Policy.", required=False + ), + ), + ("year", wagtail.blocks.CharBlock(default="", required=False)), + ( + "filter_values", + wagtail.blocks.CharBlock( + default="2019,2018,2017,2016,2015,2014,2013", + help_text="Example: 2019,2018,2017,2016,2015,2014,2013", + required=True, + ), + ), + ] + ), + ), + ( + "recent_blog_entries", + wagtail.blocks.StructBlock( + [ + ("title", wagtail.blocks.CharBlock(required=False)), + ( + "tag_filter", + wagtail.blocks.CharBlock( + help_text="Test this filter at foundation.mozilla.org/blog/tags/", + label="Filter by Tag", + required=False, + ), + ), + ( + "topic_filter", + wagtail.blocks.ChoiceBlock( + choices=networkapi.wagtailpages.pagemodels.blog.blog_topic.BlogPageTopic.get_topics, + help_text="Test this filter at foundation.mozilla.org/blog/topic/", + label="Filter by Topic", + required=False, + ), + ), + ( + "top_divider", + wagtail.blocks.BooleanBlock( + help_text="Optional divider above content block.", required=False + ), + ), + ( + "bottom_divider", + wagtail.blocks.BooleanBlock( + help_text="Optional divider below content block.", required=False + ), + ), + ] + ), + ), + ( + "blog_set", + wagtail.blocks.StructBlock( + [ + ("title", wagtail.blocks.CharBlock()), + ( + "top_divider", + wagtail.blocks.BooleanBlock( + help_text="Optional divider above content block.", required=False + ), + ), + ( + "bottom_divider", + wagtail.blocks.BooleanBlock( + help_text="Optional divider below content block.", required=False + ), + ), + ( + "blog_pages", + wagtail.blocks.ListBlock( + wagtail.blocks.PageChooserBlock(page_type=["wagtailpages.BlogPage"]) + ), + ), + ] + ), + ), + ( + "airtable", + wagtail.blocks.StructBlock( + [ + ( + "url", + wagtail.blocks.URLBlock( + help_text="Copied from the Airtable embed code. The word 'embed' will be in the url" + ), + ), + ( + "height", + wagtail.blocks.IntegerBlock( + default=533, + help_text="The pixel height on desktop view, usually copied from the Airtable embed code", + ), + ), + ] + ), + ), + ( + "typeform", + wagtail.blocks.StructBlock( + [ + ( + "embed_id", + wagtail.blocks.CharBlock( + help_text="The embed id of your Typeform page (e.g. if the form is on admin.typeform.com/form/e8zScc6t, the id will be: e8zScc6t)", + required=True, + ), + ), + ( + "button_type", + wagtail.blocks.ChoiceBlock( + choices=[ + ("btn-primary", "Primary button"), + ("btn-secondary", "Secondary button"), + ] + ), + ), + ( + "button_text", + wagtail.blocks.CharBlock( + help_text="This is a text prompt for users to open the typeform content", + required=True, + ), + ), + ] + ), + ), + ( + "datawrapper", + wagtail.embeds.blocks.EmbedBlock( + help_text='Enter the "visualization only" link of the Datawrapper chart. It looks something like this: https://datawrapper.dwcdn.net/KwSKp/1/', + icon="image", + template="wagtailpages/blocks/datawrapper_block.html", + ), + ), + ( + "listing", + wagtail.blocks.StructBlock( + [ + ( + "cards", + wagtail.blocks.ListBlock( + wagtail.blocks.StructBlock( + [ + ("image", wagtail.images.blocks.ImageChooserBlock()), + ( + "alt_text", + wagtail.blocks.CharBlock( + help_text="Alt text for card's image.", required=False + ), + ), + ("title", wagtail.blocks.CharBlock(help_text="Heading for the card.")), + ( + "body", + wagtail.blocks.RichTextBlock( + features=["bold"], help_text="Body text of the card." + ), + ), + ( + "link_page", + wagtail.blocks.PageChooserBlock( + help_text="Page that this should link out to.", required=False + ), + ), + ( + "link_url", + wagtail.blocks.CharBlock( + help_text="Optional URL that the header should link out to.", + required=False, + ), + ), + ] + ), + help_text="Please use a minimum of 2 cards.", + min_num=2, + ), + ) + ] + ), + ), + ( + "profiles", + wagtail.blocks.StructBlock( + [ + ( + "profiles", + wagtail.blocks.ListBlock( + wagtail.blocks.StructBlock( + [ + ( + "profile", + wagtail.snippets.blocks.SnippetChooserBlock( + networkapi.wagtailpages.pagemodels.profiles.Profile + ), + ) + ] + ), + min_num=1, + ), + ) + ] + ), + ), + ( + "article_teaser_block", + wagtail.blocks.StructBlock( + [ + ( + "cards", + wagtail.blocks.ListBlock( + wagtail.blocks.StructBlock( + [ + ( + "article", + wagtail.blocks.PageChooserBlock( + help_text="Page that this should link out to.", + page_type=["wagtailpages.ArticlePage"], + required=False, + ), + ) + ] + ), + help_text="Please use a minimum of 3 cards.", + min_num=3, + ), + ) + ] + ), + ), + ( + "group_listing_block", + wagtail.blocks.StructBlock( + [ + ( + "title", + wagtail.blocks.CharBlock( + help_text="Heading for the group of cards.", required=False + ), + ), + ( + "cards", + wagtail.blocks.ListBlock( + wagtail.blocks.StructBlock( + [ + ("image", wagtail.images.blocks.ImageChooserBlock()), + ( + "alt_text", + wagtail.blocks.CharBlock( + help_text="Alt text for card's image.", required=False + ), + ), + ( + "meta_data", + wagtail.blocks.CharBlock( + help_text="Optional meta data information for the card.", + required=False, + ), + ), + ("title", wagtail.blocks.CharBlock(help_text="Heading for the card.")), + ( + "body", + wagtail.blocks.RichTextBlock( + features=["bold", "link"], help_text="Body text of the card." + ), + ), + ( + "url", + wagtail.blocks.CharBlock( + help_text="The URL this card should link to.", required=False + ), + ), + ] + ) + ), + ), + ] + ), + ), + ( + "image_feature", + wagtail.blocks.StructBlock( + [ + ("image", wagtail.images.blocks.ImageChooserBlock()), + ( + "alt_text", + wagtail.blocks.CharBlock(help_text="Image description (for screen readers)."), + ), + ("metadata", wagtail.blocks.CharBlock(required=False)), + ("title", wagtail.blocks.CharBlock(help_text="Heading for the card.")), + ( + "title_link", + wagtail.blocks.PageChooserBlock( + help_text="Page that the title should link out to.", required=False + ), + ), + ("body", wagtail.blocks.CharBlock(required=False)), + ] + ), + ), + ( + "image_teaser_block", + wagtail.blocks.StructBlock( + [ + ("title", wagtail.blocks.CharBlock(help_text="Heading for the card.")), + ("text", wagtail.blocks.RichTextBlock(features=["bold"])), + ("image", wagtail.images.blocks.ImageChooserBlock()), + ( + "altText", + wagtail.blocks.CharBlock( + help_text="Image description (for screen readers).", required=True + ), + ), + ("url_label", wagtail.blocks.CharBlock(required=False)), + ("url", wagtail.blocks.CharBlock(required=False)), + ( + "styling", + wagtail.blocks.ChoiceBlock( + choices=[ + ("btn-primary", "Primary button"), + ("btn-secondary", "Secondary button"), + ] + ), + ), + ( + "top_divider", + wagtail.blocks.BooleanBlock( + help_text="Optional divider above content block.", required=False + ), + ), + ( + "bottom_divider", + wagtail.blocks.BooleanBlock( + help_text="Optional divider below content block.", required=False + ), + ), + ] + ), + ), + ( + "text_only_teaser", + wagtail.blocks.StructBlock( + [ + ( + "cards", + wagtail.blocks.ListBlock( + wagtail.blocks.StructBlock( + [ + ( + "heading", + wagtail.blocks.CharBlock(help_text="Heading for the card."), + ), + ( + "link_page", + wagtail.blocks.PageChooserBlock( + help_text="Page that the header should link out to.", + required=False, + ), + ), + ( + "link_url", + wagtail.blocks.CharBlock( + help_text="Optional URL that the header should link out to.", + required=False, + ), + ), + ("meta_data", wagtail.blocks.CharBlock(max_length=500)), + ( + "body", + wagtail.blocks.TextBlock( + help_text="Body text of the card.", + max_length=200, + required=False, + ), + ), + ] + ), + help_text="Please use a minimum of 3 cards.", + min_num=3, + ), + ) + ] + ), + ), + ( + "block_with_aside", + wagtail.blocks.StructBlock( + [ + ( + "content", + wagtail.blocks.StreamBlock( + [ + ( + "listing", + wagtail.blocks.StructBlock( + [ + ( + "cards", + wagtail.blocks.ListBlock( + wagtail.blocks.StructBlock( + [ + ( + "image", + wagtail.images.blocks.ImageChooserBlock(), + ), + ( + "alt_text", + wagtail.blocks.CharBlock( + help_text="Alt text for card's image.", + required=False, + ), + ), + ( + "title", + wagtail.blocks.CharBlock( + help_text="Heading for the card." + ), + ), + ( + "body", + wagtail.blocks.RichTextBlock( + features=["bold"], + help_text="Body text of the card.", + ), + ), + ( + "link_page", + wagtail.blocks.PageChooserBlock( + help_text="Page that this should link out to.", + required=False, + ), + ), + ( + "link_url", + wagtail.blocks.CharBlock( + help_text="Optional URL that the header should link out to.", + required=False, + ), + ), + ] + ), + help_text="Please use a minimum of 2 cards.", + min_num=2, + ), + ) + ] + ), + ), + ( + "paragraph", + wagtail.blocks.RichTextBlock( + features=[ + "bold", + "italic", + "link", + "h2", + "h3", + "h4", + "h5", + "ol", + "ul", + "hr", + "document-link", + ] + ), + ), + ], + help_text="The wider block that appears on the left on desktop", + icon="doc-full", + max_num=1, + ), + ), + ( + "aside", + wagtail.blocks.StreamBlock( + [ + ( + "aside_content", + wagtail.blocks.StructBlock( + [ + ( + "title", + wagtail.blocks.CharBlock( + help_text="Heading for the card.", required=False + ), + ), + ( + "body", + wagtail.blocks.TextBlock( + help_text="Body text of the card.", required=False + ), + ), + ] + ), + ), + ( + "linkbutton", + wagtail.blocks.StructBlock( + [ + ("label", wagtail.blocks.CharBlock()), + ("URL", wagtail.blocks.CharBlock()), + ( + "styling", + wagtail.blocks.ChoiceBlock( + choices=[ + ("btn-primary", "Primary button"), + ("btn-secondary", "Secondary button"), + ] + ), + ), + ] + ), + ), + ( + "spacer", + wagtail.blocks.StructBlock( + [ + ( + "size", + wagtail.blocks.ChoiceBlock( + choices=[ + ("1", "quarter spacing"), + ("2", "half spacing"), + ("3", "single spacing"), + ("4", "one and a half spacing"), + ("5", "triple spacing"), + ] + ), + ) + ] + ), + ), + ], + help_text="Elements here will appear in the column on the right side of the page on desktop. This can be left blank if you would just like a 2/3 column on the left", + icon="doc-full", + required=False, + ), + ), + ] + ), + ), + ( + "accordion", + wagtail.blocks.StructBlock( + [ + ( + "accordion_items", + wagtail.blocks.ListBlock( + wagtail.blocks.StructBlock( + [ + ( + "title", + wagtail.blocks.CharBlock( + help_text="Heading for the Accordion Item" + ), + ), + ( + "content", + wagtail.blocks.StreamBlock( + [ + ( + "rich_text", + wagtail.blocks.RichTextBlock( + blank=True, + features=[ + "bold", + "italic", + "link", + "ul", + "ol", + "document-link", + ], + ), + ), + ( + "datawrapper", + wagtail.embeds.blocks.EmbedBlock( + help_text='Enter the "visualization only" link of the Datawrapper chart. It looks something like this: https://datawrapper.dwcdn.net/KwSKp/1/', + icon="image", + template="wagtailpages/blocks/datawrapper_block.html", + ), + ), + ( + "image", + wagtail.blocks.StructBlock( + [ + ( + "image", + wagtail.images.blocks.ImageChooserBlock(), + ), + ( + "altText", + wagtail.blocks.CharBlock( + help_text="Image description (for screen readers).", + required=True, + ), + ), + ] + ), + ), + ( + "video", + wagtail.blocks.StructBlock( + [ + ( + "url", + wagtail.blocks.CharBlock( + help_text="For YouTube: go to your YouTube video and click “Share,” then “Embed,” and then copy and paste the provided URL only. For example: https://www.youtube.com/embed/3FIVXBawyQw For Vimeo: follow similar steps to grab the embed URL. For example: https://player.vimeo.com/video/9004979", + label="Embed URL", + ), + ), + ( + "caption", + wagtail.blocks.CharBlock(required=False), + ), + ( + "captionURL", + wagtail.blocks.CharBlock( + help_text="Optional URL for caption to link to.", + required=False, + ), + ), + ( + "video_width", + wagtail.blocks.ChoiceBlock( + choices=[ + ("normal", "Normal"), + ("wide", "Wide"), + ("full_width", "Full Width"), + ], + help_text="Wide videos are col-12, Full-Width videos reach both ends of the screen.", + ), + ), + ] + ), + ), + ] + ), + ), + ] + ) + ), + ) + ] + ), + ), + ( + "session_slider", + wagtail.blocks.StructBlock( + [ + ("title", wagtail.blocks.CharBlock(help_text="Heading for the slider.")), + ( + "session_items", + wagtail.blocks.StreamBlock( + [ + ( + "session_item", + wagtail.blocks.StructBlock( + [ + ( + "title", + wagtail.blocks.CharBlock( + help_text="Heading for the card." + ), + ), + ( + "author_subheading", + wagtail.blocks.CharBlock( + help_text="Author of this session.", required=False + ), + ), + ( + "image", + wagtail.images.blocks.ImageChooserBlock( + help_text="The image associated with this session." + ), + ), + ( + "body", + wagtail.blocks.RichTextBlock( + help_text="Body text of this card." + ), + ), + ( + "video", + wagtailmedia.blocks.VideoChooserBlock( + help_text="Video that will autoplay when this card is hovered on", + required=False, + ), + ), + ( + "link", + wagtail.blocks.StreamBlock( + [ + ( + "internal", + wagtail.blocks.StructBlock( + [ + ( + "link", + wagtail.blocks.PageChooserBlock( + help_text="Page that this should link out to." + ), + ) + ] + ), + ), + ( + "external", + wagtail.blocks.StructBlock( + [ + ( + "link", + wagtail.blocks.URLBlock( + help_text="URL that this should link out to." + ), + ) + ] + ), + ), + ], + help_text="Page or external URL this card will link out to.", + max_num=1, + ), + ), + ] + ), + ) + ], + help_text="A list of sessions in the slider.", + ), + ), + ( + "button", + wagtail.blocks.StreamBlock( + [ + ( + "internal", + wagtail.blocks.StructBlock( + [ + ( + "label", + wagtail.blocks.CharBlock(help_text="Label for this link."), + ), + ( + "link", + wagtail.blocks.PageChooserBlock( + help_text="Page that this should link out to." + ), + ), + ] + ), + ), + ( + "external", + wagtail.blocks.StructBlock( + [ + ( + "label", + wagtail.blocks.CharBlock(help_text="Label for this link."), + ), + ( + "link", + wagtail.blocks.URLBlock( + help_text="URL that this should link out to." + ), + ), + ] + ), + ), + ], + help_text="Button that appears below the slider.", + max_num=1, + required=False, + ), + ), + ] + ), + ), + ( + "current_events_slider", + wagtail.blocks.StructBlock( + [ + ("title", wagtail.blocks.CharBlock(help_text="Heading for the slider.")), + ( + "current_events", + wagtail.blocks.StreamBlock( + [ + ( + "current_event", + wagtail.blocks.StructBlock( + [ + ( + "title", + wagtail.blocks.CharBlock(help_text="Heading of the card."), + ), + ( + "subheading_link", + wagtail.blocks.StreamBlock( + [ + ( + "internal", + wagtail.blocks.StructBlock( + [ + ( + "label", + wagtail.blocks.CharBlock( + help_text="Label for this link." + ), + ), + ( + "link", + wagtail.blocks.PageChooserBlock( + help_text="Page that this should link out to." + ), + ), + ] + ), + ), + ( + "external", + wagtail.blocks.StructBlock( + [ + ( + "label", + wagtail.blocks.CharBlock( + help_text="Label for this link." + ), + ), + ( + "link", + wagtail.blocks.URLBlock( + help_text="URL that this should link out to." + ), + ), + ] + ), + ), + ], + help_text="The link that appears below the card heading.", + max_num=1, + required=False, + ), + ), + ( + "image", + wagtail.images.blocks.ImageChooserBlock( + help_text="The image associated with this event." + ), + ), + ( + "body", + wagtail.blocks.TextBlock( + help_text="Body text of the card." + ), + ), + ( + "buttons", + wagtail.blocks.StreamBlock( + [ + ( + "internal", + wagtail.blocks.StructBlock( + [ + ( + "label", + wagtail.blocks.CharBlock( + help_text="Label for this link." + ), + ), + ( + "link", + wagtail.blocks.PageChooserBlock( + help_text="Page that this should link out to." + ), + ), + ] + ), + ), + ( + "external", + wagtail.blocks.StructBlock( + [ + ( + "label", + wagtail.blocks.CharBlock( + help_text="Label for this link." + ), + ), + ( + "link", + wagtail.blocks.URLBlock( + help_text="URL that this should link out to." + ), + ), + ] + ), + ), + ( + "document", + wagtail.blocks.StructBlock( + [ + ( + "label", + wagtail.blocks.CharBlock( + help_text="Label for this link." + ), + ), + ( + "document", + wagtail.documents.blocks.DocumentChooserBlock( + help_text="Document that this should link out to." + ), + ), + ], + help_text='An iCal document can be attached here for an "Add to Calendar" button.', + ), + ), + ], + help_text="A list of buttons that will appear at the bottom of the card.", + max_num=2, + ), + ), + ] + ), + ) + ], + help_text="A list of current events in the slider.", + ), + ), + ] + ), + ), + ( + "spaces", + wagtail.blocks.StructBlock( + [ + ("title", wagtail.blocks.CharBlock()), + ( + "cards", + wagtail.blocks.StreamBlock( + [ + ( + "space_card", + wagtail.blocks.StructBlock( + [ + ("image", wagtail.images.blocks.ImageChooserBlock()), + ( + "title", + wagtail.blocks.CharBlock( + help_text="Heading for the card." + ), + ), + ( + "body", + wagtail.blocks.TextBlock( + help_text="Body text of the card." + ), + ), + ( + "link", + wagtail.blocks.StreamBlock( + [ + ( + "internal", + wagtail.blocks.StructBlock( + [ + ( + "link", + wagtail.blocks.PageChooserBlock( + help_text="Page that this should link out to." + ), + ) + ] + ), + ), + ( + "external", + wagtail.blocks.StructBlock( + [ + ( + "link", + wagtail.blocks.URLBlock( + help_text="URL that this should link out to." + ), + ) + ] + ), + ), + ], + help_text="Page or external URL this card will link out to.", + max_num=1, + ), + ), + ] + ), + ) + ], + help_text="A list of Spaces Cards.", + ), + ), + ] + ), + ), + ( + "tito_widget", + wagtail.blocks.StructBlock( + [ + ( + "button_label", + wagtail.blocks.CharBlock(help_text="The text to show on the Tito button."), + ), + ( + "styling", + wagtail.blocks.ChoiceBlock( + choices=[ + ("btn-primary", "Primary button"), + ("btn-secondary", "Secondary button"), + ] + ), + ), + ( + "event", + wagtail.snippets.blocks.SnippetChooserBlock( + "events.TitoEvent", help_event="The Tito event to be displayed" + ), + ), + ( + "releases", + wagtail.blocks.CharBlock( + help_text='Comma-separated list of ticket/release IDs to limit to, e.g. "3elajg6qcxu,6qiiw4socs4"', + required=False, + ), + ), + ] + ), + ), + ( + "tabbed_profile_directory", + wagtail.blocks.StructBlock( + [ + ( + "tabs", + wagtail.snippets.blocks.SnippetChooserBlock( + "wagtailpages.PulseFilter", + help_text=( + "Tabs are created based on the selected pulse filter ", + "and the first option in the snippet will be the first tab showing open on the page.", + ), + ), + ), + ( + "subfilters", + wagtail.blocks.StreamBlock( + [ + ( + "filter", + wagtail.snippets.blocks.SnippetChooserBlock( + "wagtailpages.PulseFilter" + ), + ) + ], + max_num=1, + required=False, + ), + ), + ( + "advanced_filter_header", + wagtail.blocks.static_block.StaticBlock( + admin_text="Note that the filter not be used if selected as the tabs filter or as one of the subfilters. For example, if the tabs filter profile types, the profile type field below will be ignored.", + label="-------- ADVANCED FILTERS: OPTIONS TO DISPLAY FEWER, MORE TARGETED RESULTS. --------", + ), + ), + ( + "profile_type", + wagtail.blocks.CharBlock(default="", help_text="Example: Fellow.", required=False), + ), + ( + "program_type", + wagtail.blocks.CharBlock( + default="", help_text="Example: Tech Policy.", required=False + ), + ), + ("year", wagtail.blocks.CharBlock(default="", required=False)), + ] + ), + ), + ( + "newsletter_signup", + wagtail.blocks.StructBlock( + [("signup", wagtail.snippets.blocks.SnippetChooserBlock("wagtailpages.Signup"))] + ), + ), + ( + "mozfest_card_grid", + wagtail.blocks.StructBlock( + [ + ("heading", wagtail.blocks.CharBlock(help_text="Heading for the block.")), + ( + "cards", + wagtail.blocks.ListBlock( + wagtail.blocks.StructBlock( + [ + ("image", wagtail.images.blocks.ImageChooserBlock()), + ( + "alt_text", + wagtail.blocks.CharBlock( + help_text="Alt text for card's image.", required=False + ), + ), + ("title", wagtail.blocks.CharBlock(help_text="Heading for the card.")), + ( + "body", + wagtail.blocks.TextBlock( + help_text="Body text of the card.", required=False + ), + ), + ( + "link_url", + wagtail.blocks.CharBlock( + help_text="Optional URL that this card should link out to. (Note: If left blank, link will not render.) ", + required=False, + ), + ), + ( + "link_label", + wagtail.blocks.CharBlock( + help_text="Optional Label for the URL link above. (Note: If left blank, link will not render.) ", + required=False, + ), + ), + ] + ), + help_text="Please use a minimum of 2 cards.", + ), + ), + ] + ), + ), + ], + use_json_field=True, + ), + ), + ] diff --git a/network-api/networkapi/mozfest/models.py b/network-api/networkapi/mozfest/models.py index c8ecc6809de..a642250e260 100644 --- a/network-api/networkapi/mozfest/models.py +++ b/network-api/networkapi/mozfest/models.py @@ -5,6 +5,7 @@ from wagtail.models import Locale, Page from wagtail_localize.fields import SynchronizedField, TranslatableField +from networkapi.mozfest import customblocks as mozfest_blocks from networkapi.wagtailpages.models import ( FoundationBannerInheritanceMixin, FoundationMetadataPageMixin, @@ -51,6 +52,7 @@ class MozfestPrimaryPage(FoundationMetadataPageMixin, FoundationBannerInheritanc ("tito_widget", customblocks.TitoWidgetBlock()), ("tabbed_profile_directory", customblocks.TabbedProfileDirectory()), ("newsletter_signup", customblocks.NewsletterSignupBlock()), + ("mozfest_card_grid", mozfest_blocks.MozfestCardGridBlock()), ], use_json_field=True, ) diff --git a/network-api/networkapi/mozfest/templates/fragments/blocks/mozfest_card_grid_block.html b/network-api/networkapi/mozfest/templates/fragments/blocks/mozfest_card_grid_block.html new file mode 100644 index 00000000000..55356555b15 --- /dev/null +++ b/network-api/networkapi/mozfest/templates/fragments/blocks/mozfest_card_grid_block.html @@ -0,0 +1,60 @@ +{% extends "wagtailpages/blocks/base_streamfield_block.html" %} +{% load wagtailcore_tags wagtailimages_tags %} + +{% block block_content %} +

{{ self.heading }}

+
+ {% for block in self.cards %} + {% with card=block count=self.cards|length %} +
+
+ +
+ + {% comment %} + Because of the card layout, the image size is not increasing consistently with the device size. + The image sizes are based on the layout on a banner page with wide layout. + The designed image aspect ratio is 16:9. + + Screen sizes: + small: "576px" + medium: "768px" + large: "992px" + xlarge: "1200px" + "2xl": "1400px" + + The picture element will use the first source with a matching media query. + Using min-media queries, we need to list the breakpoints in decending order, otherwise larger screens will always use the first listed (because that min-width query would be fulfilled). + {% endcomment %} + {% image card.image fill-512x288 as img_small %} + {% image card.image fill-1024x576 as img_small_2x %} + {% image card.image fill-336x189 as img_medium %} + {% image card.image fill-672x378 as img_medium_2x %} + {% image card.image fill-448x252 as img_large %} + {% image card.image fill-896x504 as img_large_2x %} + {% image card.image fill-592x333 as img_xlarge %} + {% image card.image fill-1184x666 as img_xlarge_2x %} + + + + + {{ img_small.alt_text }} + +
+ +
+
+

{{ card.title }}

+

{{ card.body }}

+ {% if card.link_label and card.link_url %} + {{ card.link_label }} + {% endif %} +
+
+ +
+
+ {% endwith %} + {% endfor %} +
+{% endblock %} diff --git a/network-api/networkapi/utility/faker/streamfield_provider.py b/network-api/networkapi/utility/faker/streamfield_provider.py index 84a923ff83e..f911cacead1 100644 --- a/network-api/networkapi/utility/faker/streamfield_provider.py +++ b/network-api/networkapi/utility/faker/streamfield_provider.py @@ -280,6 +280,25 @@ def generate_card_grid_field(): return generate_field("card_grid", {"cards": cards}) +def generate_mozfest_card_grid_field(): + heading = fake.sentence(nb_words=4, variable_nb_words=True) + + cards = [] + + for n in range(2): + cards.append( + { + "image": choice(Image.objects.all()).id, + "title": fake.paragraph(nb_sentences=1, variable_nb_sentences=False), + "body": fake.paragraph(nb_sentences=10, variable_nb_sentences=True), + "link_label": " ".join(fake.words(nb=3)), + "link_url": fake.url(schemes=["https"]), + } + ) + + return generate_field("mozfest_card_grid", {"cards": cards, "heading": heading}) + + def generate_pulse_listing_field(): return generate_field( "pulse_listing", @@ -511,6 +530,7 @@ def streamfield(self, fields=None): "current_events_slider": generate_current_events_slider_field, "callout_box": generate_blog_index_callout_box_field, "blog_newsletter_signup": generate_blog_newsletter_signup_field, + "mozfest_card_grid": generate_mozfest_card_grid_field, } streamfield_data = []