From 69a68944bdcb6bdd7df662f3bd63bff21bff75cf Mon Sep 17 00:00:00 2001 From: Jhonatan Lopes Date: Wed, 13 Mar 2024 11:33:22 -0300 Subject: [PATCH 01/18] [Main Nav] Nav link block [TP-292](#12033) * Add `nav` app * Make only page, external and relative links available on base link block * Make label part of the base link block * Define a NavLinkBlock * Add NavLinkBlock factory * Add tests for block * Make `link_to` field required --- .../0006_add_linkblock_to_linkbuttonblock.py | 6 +- ...08_remove_old_url_fields_from_linkblock.py | 6 +- .../migrations/0009_make_linkto_required.py | 1348 ++++ ...nkblock_to_linkbuttonblock_and_ctablock.py | 10 +- ...48_remove_old_url_fields_from_linkblock.py | 10 +- .../migrations/0049_make_linkto_required.py | 1814 +++++ network-api/networkapi/nav/__init__.py | 0 network-api/networkapi/nav/apps.py | 5 + network-api/networkapi/nav/blocks.py | 27 + network-api/networkapi/nav/factories.py | 52 + network-api/networkapi/nav/tests/__init__.py | 0 .../networkapi/nav/tests/test_blocks.py | 65 + network-api/networkapi/settings.py | 1 + .../0125_add_linkblock_to_linkbuttonblock.py | 36 +- ...27_remove_old_url_fields_from_linkblock.py | 36 +- .../0129_allow_spacer_on_articlepage_body.py | 4 +- .../migrations/0130_make_linkto_required.py | 6296 +++++++++++++++++ .../customblocks/common/base_link_block.py | 23 +- .../pagemodels/customblocks/link_block.py | 33 +- 19 files changed, 9663 insertions(+), 109 deletions(-) create mode 100644 network-api/networkapi/donate/migrations/0009_make_linkto_required.py create mode 100644 network-api/networkapi/mozfest/migrations/0049_make_linkto_required.py create mode 100644 network-api/networkapi/nav/__init__.py create mode 100644 network-api/networkapi/nav/apps.py create mode 100644 network-api/networkapi/nav/blocks.py create mode 100644 network-api/networkapi/nav/factories.py create mode 100644 network-api/networkapi/nav/tests/__init__.py create mode 100644 network-api/networkapi/nav/tests/test_blocks.py create mode 100644 network-api/networkapi/wagtailpages/migrations/0130_make_linkto_required.py diff --git a/network-api/networkapi/donate/migrations/0006_add_linkblock_to_linkbuttonblock.py b/network-api/networkapi/donate/migrations/0006_add_linkblock_to_linkbuttonblock.py index 7c3426592b1..b8a7500dff5 100644 --- a/network-api/networkapi/donate/migrations/0006_add_linkblock_to_linkbuttonblock.py +++ b/network-api/networkapi/donate/migrations/0006_add_linkblock_to_linkbuttonblock.py @@ -344,9 +344,7 @@ class Migration(migrations.Migration): help_text='An id attribute of an element on the current page. For example, "#section-1"', max_length=300, required=False, - validators=[ - networkapi.wagtailpages.pagemodels.customblocks.common.base_link_block.AnchorLinkValidator() - ], + validators=[networkapi.wagtailpages.validators.AnchorLinkValidator()], ), ), ("email", wagtail.blocks.EmailBlock(required=False)), @@ -1179,7 +1177,7 @@ class Migration(migrations.Migration): max_length=300, required=False, validators=[ - networkapi.wagtailpages.pagemodels.customblocks.common.base_link_block.AnchorLinkValidator() + networkapi.wagtailpages.validators.AnchorLinkValidator() ], ), ), diff --git a/network-api/networkapi/donate/migrations/0008_remove_old_url_fields_from_linkblock.py b/network-api/networkapi/donate/migrations/0008_remove_old_url_fields_from_linkblock.py index 89a1044505c..7b0e6cf1fa0 100644 --- a/network-api/networkapi/donate/migrations/0008_remove_old_url_fields_from_linkblock.py +++ b/network-api/networkapi/donate/migrations/0008_remove_old_url_fields_from_linkblock.py @@ -344,9 +344,7 @@ class Migration(migrations.Migration): help_text='An id attribute of an element on the current page. For example, "#section-1"', max_length=300, required=False, - validators=[ - networkapi.wagtailpages.pagemodels.customblocks.common.base_link_block.AnchorLinkValidator() - ], + validators=[networkapi.wagtailpages.validators.AnchorLinkValidator()], ), ), ("email", wagtail.blocks.EmailBlock(required=False)), @@ -1178,7 +1176,7 @@ class Migration(migrations.Migration): max_length=300, required=False, validators=[ - networkapi.wagtailpages.pagemodels.customblocks.common.base_link_block.AnchorLinkValidator() + networkapi.wagtailpages.validators.AnchorLinkValidator() ], ), ), diff --git a/network-api/networkapi/donate/migrations/0009_make_linkto_required.py b/network-api/networkapi/donate/migrations/0009_make_linkto_required.py new file mode 100644 index 00000000000..cfbbb7f14e9 --- /dev/null +++ b/network-api/networkapi/donate/migrations/0009_make_linkto_required.py @@ -0,0 +1,1348 @@ +# Generated by Django 4.2.10 on 2024-03-13 14:00 + +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 +from django.db import migrations + +import networkapi.wagtailpages.pagemodels.blog.blog_topic +import networkapi.wagtailpages.pagemodels.profiles +import networkapi.wagtailpages.validators + + +class Migration(migrations.Migration): + + dependencies = [ + ("donate", "0008_remove_old_url_fields_from_linkblock"), + ] + + operations = [ + migrations.AlterField( + model_name="donatehelppage", + 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()), + ( + "link_to", + wagtail.blocks.ChoiceBlock( + choices=[ + ("page", "Page"), + ("external_url", "External URL"), + ("relative_url", "Relative URL"), + ("email", "Email"), + ("anchor", "Anchor"), + ("file", "File"), + ("phone", "Phone"), + ], + label="Link to", + ), + ), + ("page", wagtail.blocks.PageChooserBlock(label="Page", required=False)), + ( + "external_url", + wagtail.blocks.URLBlock( + help_text="Enter a full URL including http:// or https://", + label="External URL", + max_length=300, + required=False, + ), + ), + ( + "relative_url", + wagtail.blocks.CharBlock( + help_text='A path relative to this domain. For example, "/foo/bar"', + label="Relative URL", + max_length=300, + required=False, + validators=[networkapi.wagtailpages.validators.RelativeURLValidator()], + ), + ), + ( + "anchor", + wagtail.blocks.CharBlock( + help_text='An id attribute of an element on the current page. For example, "#section-1"', + label="#", + max_length=300, + required=False, + validators=[networkapi.wagtailpages.validators.AnchorLinkValidator()], + ), + ), + ("email", wagtail.blocks.EmailBlock(required=False)), + ("file", wagtail.documents.blocks.DocumentChooserBlock(label="File", required=False)), + ("phone", wagtail.blocks.CharBlock(label="Phone", max_length=30, required=False)), + ( + "new_window", + wagtail.blocks.BooleanBlock(label="Open in new window", required=False), + ), + ( + "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( + [ + ( + "heading", + wagtail.blocks.CharBlock(help_text="Heading for the block.", 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 + ), + ), + ("title", wagtail.blocks.CharBlock(help_text="Heading for the card.")), + ( + "highlighted_metadata", + wagtail.blocks.CharBlock( + help_text="Metadata to highlight on the card.", required=False + ), + ), + ( + "metadata", + wagtail.blocks.CharBlock( + help_text="Generic metadata.", required=False + ), + ), + ( + "body", + wagtail.blocks.RichTextBlock( + features=["bold"], + help_text="Body text of the card.", + required=False, + ), + ), + ( + "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, + ), + ), + ( + "cards_per_row", + wagtail.blocks.ChoiceBlock( + choices=[(2, "2"), (3, "3")], + help_text="Number of cards per row. Note: this is a max and fewer might be used if is less space is available.", + required=False, + ), + ), + ] + ), + ), + ( + "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( + [ + ( + "heading", + wagtail.blocks.CharBlock( + help_text="Heading for the block.", 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, + ), + ), + ( + "title", + wagtail.blocks.CharBlock( + help_text="Heading for the card." + ), + ), + ( + "highlighted_metadata", + wagtail.blocks.CharBlock( + help_text="Metadata to highlight on the card.", + required=False, + ), + ), + ( + "metadata", + wagtail.blocks.CharBlock( + help_text="Generic metadata.", + required=False, + ), + ), + ( + "body", + wagtail.blocks.RichTextBlock( + features=["bold"], + help_text="Body text of the card.", + required=False, + ), + ), + ( + "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, + ), + ), + ( + "cards_per_row", + wagtail.blocks.ChoiceBlock( + choices=[(2, "2"), (3, "3")], + help_text="Number of cards per row. Note: this is a max and fewer might be used if is less space is available.", + required=False, + ), + ), + ] + ), + ), + ( + "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()), + ( + "link_to", + wagtail.blocks.ChoiceBlock( + choices=[ + ("page", "Page"), + ("external_url", "External URL"), + ("relative_url", "Relative URL"), + ("email", "Email"), + ("anchor", "Anchor"), + ("file", "File"), + ("phone", "Phone"), + ], + label="Link to", + ), + ), + ( + "page", + wagtail.blocks.PageChooserBlock( + label="Page", required=False + ), + ), + ( + "external_url", + wagtail.blocks.URLBlock( + help_text="Enter a full URL including http:// or https://", + label="External URL", + max_length=300, + required=False, + ), + ), + ( + "relative_url", + wagtail.blocks.CharBlock( + help_text='A path relative to this domain. For example, "/foo/bar"', + label="Relative URL", + max_length=300, + required=False, + validators=[ + networkapi.wagtailpages.validators.RelativeURLValidator() + ], + ), + ), + ( + "anchor", + wagtail.blocks.CharBlock( + help_text='An id attribute of an element on the current page. For example, "#section-1"', + label="#", + max_length=300, + required=False, + validators=[ + networkapi.wagtailpages.validators.AnchorLinkValidator() + ], + ), + ), + ("email", wagtail.blocks.EmailBlock(required=False)), + ( + "file", + wagtail.documents.blocks.DocumentChooserBlock( + label="File", required=False + ), + ), + ( + "phone", + wagtail.blocks.CharBlock( + label="Phone", max_length=30, required=False + ), + ), + ( + "new_window", + wagtail.blocks.BooleanBlock( + label="Open in new window", required=False + ), + ), + ( + "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.", + ), + ), + ] + ), + ), + ] + ), + ), + ] + ) + ), + ) + ] + ), + ), + ], + blank=True, + use_json_field=True, + ), + ), + ] diff --git a/network-api/networkapi/mozfest/migrations/0046_add_target_linkblock_to_linkbuttonblock_and_ctablock.py b/network-api/networkapi/mozfest/migrations/0046_add_target_linkblock_to_linkbuttonblock_and_ctablock.py index 47d833604bf..3bdcf693de9 100644 --- a/network-api/networkapi/mozfest/migrations/0046_add_target_linkblock_to_linkbuttonblock_and_ctablock.py +++ b/network-api/networkapi/mozfest/migrations/0046_add_target_linkblock_to_linkbuttonblock_and_ctablock.py @@ -333,9 +333,7 @@ class Migration(migrations.Migration): help_text='An id attribute of an element on the current page. For example, "#section-1"', max_length=300, required=False, - validators=[ - networkapi.wagtailpages.pagemodels.customblocks.common.base_link_block.AnchorLinkValidator() - ], + validators=[networkapi.wagtailpages.validators.AnchorLinkValidator()], ), ), ("email", wagtail.blocks.EmailBlock(required=False)), @@ -952,7 +950,7 @@ class Migration(migrations.Migration): max_length=300, required=False, validators=[ - networkapi.wagtailpages.pagemodels.customblocks.common.base_link_block.AnchorLinkValidator() + networkapi.wagtailpages.validators.AnchorLinkValidator() ], ), ), @@ -1676,9 +1674,7 @@ class Migration(migrations.Migration): help_text='An id attribute of an element on the current page. For example, "#section-1"', max_length=300, required=False, - validators=[ - networkapi.wagtailpages.pagemodels.customblocks.common.base_link_block.AnchorLinkValidator() - ], + validators=[networkapi.wagtailpages.validators.AnchorLinkValidator()], ), ), ("email", wagtail.blocks.EmailBlock(required=False)), diff --git a/network-api/networkapi/mozfest/migrations/0048_remove_old_url_fields_from_linkblock.py b/network-api/networkapi/mozfest/migrations/0048_remove_old_url_fields_from_linkblock.py index d98a3e6c1a9..49ac6cd8b38 100644 --- a/network-api/networkapi/mozfest/migrations/0048_remove_old_url_fields_from_linkblock.py +++ b/network-api/networkapi/mozfest/migrations/0048_remove_old_url_fields_from_linkblock.py @@ -345,9 +345,7 @@ class Migration(migrations.Migration): help_text='An id attribute of an element on the current page. For example, "#section-1"', max_length=300, required=False, - validators=[ - networkapi.wagtailpages.pagemodels.customblocks.common.base_link_block.AnchorLinkValidator() - ], + validators=[networkapi.wagtailpages.validators.AnchorLinkValidator()], ), ), ("email", wagtail.blocks.EmailBlock(required=False)), @@ -973,7 +971,7 @@ class Migration(migrations.Migration): max_length=300, required=False, validators=[ - networkapi.wagtailpages.pagemodels.customblocks.common.base_link_block.AnchorLinkValidator() + networkapi.wagtailpages.validators.AnchorLinkValidator() ], ), ), @@ -1696,9 +1694,7 @@ class Migration(migrations.Migration): help_text='An id attribute of an element on the current page. For example, "#section-1"', max_length=300, required=False, - validators=[ - networkapi.wagtailpages.pagemodels.customblocks.common.base_link_block.AnchorLinkValidator() - ], + validators=[networkapi.wagtailpages.validators.AnchorLinkValidator()], ), ), ("email", wagtail.blocks.EmailBlock(required=False)), diff --git a/network-api/networkapi/mozfest/migrations/0049_make_linkto_required.py b/network-api/networkapi/mozfest/migrations/0049_make_linkto_required.py new file mode 100644 index 00000000000..18ad728a166 --- /dev/null +++ b/network-api/networkapi/mozfest/migrations/0049_make_linkto_required.py @@ -0,0 +1,1814 @@ +# Generated by Django 4.2.10 on 2024-03-13 14:00 + +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 +import networkapi.wagtailpages.validators + + +class Migration(migrations.Migration): + + dependencies = [ + ("mozfest", "0048_remove_old_url_fields_from_linkblock"), + ] + + 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()), + ( + "link_to", + wagtail.blocks.ChoiceBlock( + choices=[ + ("page", "Page"), + ("external_url", "External URL"), + ("relative_url", "Relative URL"), + ("email", "Email"), + ("anchor", "Anchor"), + ("file", "File"), + ("phone", "Phone"), + ], + label="Link to", + ), + ), + ("page", wagtail.blocks.PageChooserBlock(label="Page", required=False)), + ( + "external_url", + wagtail.blocks.URLBlock( + help_text="Enter a full URL including http:// or https://", + label="External URL", + max_length=300, + required=False, + ), + ), + ( + "relative_url", + wagtail.blocks.CharBlock( + help_text='A path relative to this domain. For example, "/foo/bar"', + label="Relative URL", + max_length=300, + required=False, + validators=[networkapi.wagtailpages.validators.RelativeURLValidator()], + ), + ), + ( + "anchor", + wagtail.blocks.CharBlock( + help_text='An id attribute of an element on the current page. For example, "#section-1"', + label="#", + max_length=300, + required=False, + validators=[networkapi.wagtailpages.validators.AnchorLinkValidator()], + ), + ), + ("email", wagtail.blocks.EmailBlock(required=False)), + ("file", wagtail.documents.blocks.DocumentChooserBlock(label="File", required=False)), + ("phone", wagtail.blocks.CharBlock(label="Phone", max_length=30, required=False)), + ( + "new_window", + wagtail.blocks.BooleanBlock(label="Open in new window", required=False), + ), + ( + "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), + ), + ] + ), + ), + ( + "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"]) + ), + ), + ] + ), + ), + ( + "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( + [ + ( + "heading", + wagtail.blocks.CharBlock(help_text="Heading for the block.", 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 + ), + ), + ("title", wagtail.blocks.CharBlock(help_text="Heading for the card.")), + ( + "highlighted_metadata", + wagtail.blocks.CharBlock( + help_text="Metadata to highlight on the card.", required=False + ), + ), + ( + "metadata", + wagtail.blocks.CharBlock( + help_text="Generic metadata.", required=False + ), + ), + ( + "body", + wagtail.blocks.RichTextBlock( + features=["bold"], + help_text="Body text of the card.", + required=False, + ), + ), + ( + "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, + ), + ), + ( + "cards_per_row", + wagtail.blocks.ChoiceBlock( + choices=[(2, "2"), (3, "3")], + help_text="Number of cards per row. Note: this is a max and fewer might be used if is less space is available.", + required=False, + ), + ), + ] + ), + ), + ( + "profiles", + wagtail.blocks.StructBlock( + [ + ( + "profiles", + wagtail.blocks.ListBlock( + wagtail.blocks.StructBlock( + [ + ( + "profile", + wagtail.snippets.blocks.SnippetChooserBlock( + networkapi.wagtailpages.pagemodels.profiles.Profile + ), + ) + ] + ), + min_num=1, + ), + ) + ] + ), + ), + ( + "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( + [ + ( + "heading", + wagtail.blocks.CharBlock( + help_text="Heading for the block.", 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, + ), + ), + ( + "title", + wagtail.blocks.CharBlock( + help_text="Heading for the card." + ), + ), + ( + "highlighted_metadata", + wagtail.blocks.CharBlock( + help_text="Metadata to highlight on the card.", + required=False, + ), + ), + ( + "metadata", + wagtail.blocks.CharBlock( + help_text="Generic metadata.", + required=False, + ), + ), + ( + "body", + wagtail.blocks.RichTextBlock( + features=["bold"], + help_text="Body text of the card.", + required=False, + ), + ), + ( + "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, + ), + ), + ( + "cards_per_row", + wagtail.blocks.ChoiceBlock( + choices=[(2, "2"), (3, "3")], + help_text="Number of cards per row. Note: this is a max and fewer might be used if is less space is available.", + required=False, + ), + ), + ] + ), + ), + ( + "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()), + ( + "link_to", + wagtail.blocks.ChoiceBlock( + choices=[ + ("page", "Page"), + ("external_url", "External URL"), + ("relative_url", "Relative URL"), + ("email", "Email"), + ("anchor", "Anchor"), + ("file", "File"), + ("phone", "Phone"), + ], + label="Link to", + ), + ), + ( + "page", + wagtail.blocks.PageChooserBlock( + label="Page", required=False + ), + ), + ( + "external_url", + wagtail.blocks.URLBlock( + help_text="Enter a full URL including http:// or https://", + label="External URL", + max_length=300, + required=False, + ), + ), + ( + "relative_url", + wagtail.blocks.CharBlock( + help_text='A path relative to this domain. For example, "/foo/bar"', + label="Relative URL", + max_length=300, + required=False, + validators=[ + networkapi.wagtailpages.validators.RelativeURLValidator() + ], + ), + ), + ( + "anchor", + wagtail.blocks.CharBlock( + help_text='An id attribute of an element on the current page. For example, "#section-1"', + label="#", + max_length=300, + required=False, + validators=[ + networkapi.wagtailpages.validators.AnchorLinkValidator() + ], + ), + ), + ("email", wagtail.blocks.EmailBlock(required=False)), + ( + "file", + wagtail.documents.blocks.DocumentChooserBlock( + label="File", required=False + ), + ), + ( + "phone", + wagtail.blocks.CharBlock( + label="Phone", max_length=30, required=False + ), + ), + ( + "new_window", + wagtail.blocks.BooleanBlock( + label="Open in new window", required=False + ), + ), + ( + "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, + ), + ), + ] + ), + ), + ( + "newsletter_signup", + wagtail.blocks.StructBlock( + [ + ( + "snippet", + wagtail.snippets.blocks.SnippetChooserBlock( + "mozfest.NewsletterSignupWithBackground" + ), + ) + ] + ), + ), + ( + "statistics", + wagtail.blocks.StructBlock( + [ + ( + "statistics", + wagtail.blocks.ListBlock( + wagtail.blocks.StructBlock( + [ + ( + "title", + wagtail.blocks.CharBlock( + help_text="The statistic figure, e.g '1000+' or '10%'" + ), + ), + ( + "description", + wagtail.blocks.CharBlock( + help_text="Context or description for the statistic" + ), + ), + ] + ), + help_text="Please use a minimum of 2 cards.", + max_num=4, + min_num=2, + ), + ) + ] + ), + ), + ( + "carousel_and_text", + wagtail.blocks.StructBlock( + [ + ( + "heading", + wagtail.blocks.CharBlock(help_text="Heading for the block.", required=False), + ), + ("text", wagtail.blocks.RichTextBlock(features=["bold", "italic", "link"])), + ( + "link_url", + wagtail.blocks.URLBlock( + help_text="A CTA URL for a link displayed", required=False + ), + ), + ( + "link_label", + wagtail.blocks.CharBlock(help_text="Label for the CTA link.", required=False), + ), + ( + "carousel_images", + wagtail.blocks.ListBlock( + wagtail.blocks.StructBlock( + [ + ("image", wagtail.images.blocks.ImageChooserBlock()), + ( + "altText", + wagtail.blocks.CharBlock( + help_text="Image description (for screen readers).", + required=True, + ), + ), + ] + ), + max_num=4, + ), + ), + ] + ), + ), + ( + "tickets", + wagtail.blocks.StructBlock( + [ + ( + "heading", + wagtail.blocks.CharBlock(help_text="Heading for the block.", required=False), + ), + ( + "tickets", + wagtail.blocks.ListBlock( + wagtail.snippets.blocks.SnippetChooserBlock("mozfest.Ticket"), max_num=3 + ), + ), + ] + ), + ), + ( + "dark_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), + ), + ] + ), + ), + ( + "cta", + wagtail.blocks.StructBlock( + [ + ("label", wagtail.blocks.CharBlock()), + ( + "link_to", + wagtail.blocks.ChoiceBlock( + choices=[ + ("page", "Page"), + ("external_url", "External URL"), + ("relative_url", "Relative URL"), + ("email", "Email"), + ("anchor", "Anchor"), + ("file", "File"), + ("phone", "Phone"), + ], + label="Link to", + ), + ), + ("page", wagtail.blocks.PageChooserBlock(label="Page", required=False)), + ( + "external_url", + wagtail.blocks.URLBlock( + help_text="Enter a full URL including http:// or https://", + label="External URL", + max_length=300, + required=False, + ), + ), + ( + "relative_url", + wagtail.blocks.CharBlock( + help_text='A path relative to this domain. For example, "/foo/bar"', + label="Relative URL", + max_length=300, + required=False, + validators=[networkapi.wagtailpages.validators.RelativeURLValidator()], + ), + ), + ( + "anchor", + wagtail.blocks.CharBlock( + help_text='An id attribute of an element on the current page. For example, "#section-1"', + label="#", + max_length=300, + required=False, + validators=[networkapi.wagtailpages.validators.AnchorLinkValidator()], + ), + ), + ("email", wagtail.blocks.EmailBlock(required=False)), + ("file", wagtail.documents.blocks.DocumentChooserBlock(label="File", required=False)), + ("phone", wagtail.blocks.CharBlock(label="Phone", max_length=30, required=False)), + ( + "new_window", + wagtail.blocks.BooleanBlock(label="Open in new window", required=False), + ), + ("heading", wagtail.blocks.CharBlock(required=False)), + ("text", wagtail.blocks.CharBlock(required=False)), + ("dark_background", wagtail.blocks.BooleanBlock(required=False)), + ] + ), + ), + ( + "mixed_content", + wagtail.blocks.StructBlock( + [ + ( + "video", + wagtail.blocks.StructBlock( + [ + ( + "url", + wagtail.blocks.URLBlock( + 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( + help_text="Optional caption for the video, displayed next to the play button.", + max_length=25, + required=False, + ), + ), + ( + "title", + wagtail.blocks.CharBlock( + help_text="Optional title for the video.", + max_length=50, + required=False, + ), + ), + ( + "text", + wagtail.blocks.CharBlock( + help_text="Text content to display with the video title.", + required=False, + ), + ), + ( + "thumbnail", + wagtail.images.blocks.ImageChooserBlock( + help_text="The image to show before the video is played." + ), + ), + ] + ), + ), + ( + "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.")), + ( + "highlighted_metadata", + wagtail.blocks.CharBlock( + help_text="Metadata to highlight on the card.", required=False + ), + ), + ( + "metadata", + wagtail.blocks.CharBlock( + help_text="Generic metadata.", required=False + ), + ), + ( + "body", + wagtail.blocks.RichTextBlock( + features=["bold"], + help_text="Body text of the card.", + required=False, + ), + ), + ( + "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, + ), + ), + ] + ), + max_num=4, + min_num=1, + ), + ), + ("link_url", wagtail.blocks.URLBlock(required=False)), + ("link_text", wagtail.blocks.CharBlock(max_length=50, required=False)), + ] + ), + ), + ], + use_json_field=True, + ), + ), + ] diff --git a/network-api/networkapi/nav/__init__.py b/network-api/networkapi/nav/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/network-api/networkapi/nav/apps.py b/network-api/networkapi/nav/apps.py new file mode 100644 index 00000000000..66da72568a3 --- /dev/null +++ b/network-api/networkapi/nav/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class NavConfig(AppConfig): + name = "networkapi.nav" diff --git a/network-api/networkapi/nav/blocks.py b/network-api/networkapi/nav/blocks.py new file mode 100644 index 00000000000..d71df666ff6 --- /dev/null +++ b/network-api/networkapi/nav/blocks.py @@ -0,0 +1,27 @@ +from wagtail.telepath import register + +from networkapi.wagtailpages.pagemodels.customblocks.common.base_link_block import ( + BaseLinkBlock, + BaseLinkBlockAdapter, + BaseLinkValue, +) + + +class NavLinkValue(BaseLinkValue): + @property + def open_in_new_window(self) -> bool: + link_to = self.get("link_to") + if link_to == "external_url": + return True + return False + + +class NavLinkBlock(BaseLinkBlock): + class Meta: + value_class = NavLinkValue + label = "Navigation Link" + icon = "link" + template = "nav/blocks/nav_link_block.html" + + +register(BaseLinkBlockAdapter(), NavLinkBlock) diff --git a/network-api/networkapi/nav/factories.py b/network-api/networkapi/nav/factories.py new file mode 100644 index 00000000000..aec8757a02b --- /dev/null +++ b/network-api/networkapi/nav/factories.py @@ -0,0 +1,52 @@ +import factory +import wagtail_factories +from wagtail import models as wagtail_models + +from networkapi.nav.blocks import NavLinkBlock, NavLinkValue + + +class NavLinkBlockFactory(wagtail_factories.StructBlockFactory): + """Factory for NavLinkBlock. + + Use traits to create instances based on the type of link needed: + - page_link: link to a page + - external_url_link: link to a custom external URL + - relative_url_link: link to a relative URL + + Example: + ``` + block = NavLinkBlockFactory(page_link=True) + block = NavLinkBlockFactory(external_url_link=True) + block = NavLinkBlockFactory(relative_url_link=True) + ``` + """ + + class Meta: + model = NavLinkBlock + + @classmethod + def _construct_struct_value(cls, block_class, params): + """Use NavLinkValue to create the StructValue instance.""" + return NavLinkValue( + block_class(), + [(name, value) for name, value in params.items()], + ) + + class Params: + page_link = factory.Trait( + link_to="page", + page=factory.Iterator(wagtail_models.Page.objects.filter(locale_id="1")), + ) + external_url_link = factory.Trait(link_to="external_url", external_url=factory.Faker("url")) + relative_url_link = factory.Trait(link_to="relative_url", relative_url=f'/{factory.Faker("uri_path")}') + + label = factory.Faker("sentence", nb_words=3) + + # Setup default link as external URL (it won't pass validation without a link type defined though + # so it's still necessary to use the factory with traits) + link_to = "external_url" + # Set all link types to None by default. Only define the needed link type in the factory + # trait to avoid conflicts + page = None + external_url = "" + relative_url = "" diff --git a/network-api/networkapi/nav/tests/__init__.py b/network-api/networkapi/nav/tests/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/network-api/networkapi/nav/tests/test_blocks.py b/network-api/networkapi/nav/tests/test_blocks.py new file mode 100644 index 00000000000..b1a04c798a7 --- /dev/null +++ b/network-api/networkapi/nav/tests/test_blocks.py @@ -0,0 +1,65 @@ +from django.test import TestCase +from wagtail.blocks import StreamBlockValidationError +from wagtail.models import Locale, Page + +from networkapi.nav.blocks import NavLinkBlock +from networkapi.nav.factories import NavLinkBlockFactory + + +class TestLinkBlock(TestCase): + def test_default(self): + """Assert that default NavLinkBlock factory works and is an external URL.""" + block = NavLinkBlockFactory() + + # Assert that the page link is custom URL and that it is correct + url = block["external_url"] + self.assertEqual(block.url, url) + + def test_page_link(self): + """Create a NavLinkBlock with a page link.""" + block = NavLinkBlockFactory(page_link=True) + + page = block["page"] + self.assertIsNotNone(page) + self.assertTrue(isinstance(page, Page)) + default_locale = Locale.get_default() + self.assertEqual(page.locale, default_locale) + + self.assertFalse(block.open_in_new_window) + + # Assert that other fields are empty + self.assertEqual(block["external_url"], "") + self.assertEqual(block["relative_url"], "") + + def test_external_url_link(self): + """Create a NavLinkBlock with a custom/external URL.""" + block = NavLinkBlockFactory(external_url_link=True) + + # Assert that the URL is a URL + url = block["external_url"] + self.assertIsNotNone(url) + + self.assertTrue(block.open_in_new_window) + + # Assert that other fields are empty + self.assertIsNone(block["page"]) + self.assertEqual(block["relative_url"], "") + + def test_relative_url_link(self): + """Create a NavLinkBlock with a relative URL.""" + block = NavLinkBlockFactory(relative_url_link=True) + + # Assert that the URL is a URL + url = block["relative_url"] + self.assertIsNotNone(url) + + self.assertFalse(block.open_in_new_window) + + # Assert that other fields are empty + self.assertIsNone(block["page"]) + self.assertEqual(block["external_url"], "") + + def test_needs_to_provide_at_least_one_link(self): + with self.assertRaises(StreamBlockValidationError): + block = NavLinkBlockFactory() + NavLinkBlock().clean(block) diff --git a/network-api/networkapi/settings.py b/network-api/networkapi/settings.py index 49a7ed08794..89d76140c4b 100644 --- a/network-api/networkapi/settings.py +++ b/network-api/networkapi/settings.py @@ -270,6 +270,7 @@ "networkapi.donate", "networkapi.donate_banner", "networkapi.reports", + "networkapi.nav", "pattern_library" if PATTERN_LIBRARY_ENABLED else None, "networkapi.project_styleguide", ], diff --git a/network-api/networkapi/wagtailpages/migrations/0125_add_linkblock_to_linkbuttonblock.py b/network-api/networkapi/wagtailpages/migrations/0125_add_linkblock_to_linkbuttonblock.py index 8fe19344051..e314b0e88b2 100644 --- a/network-api/networkapi/wagtailpages/migrations/0125_add_linkblock_to_linkbuttonblock.py +++ b/network-api/networkapi/wagtailpages/migrations/0125_add_linkblock_to_linkbuttonblock.py @@ -483,9 +483,7 @@ class Migration(migrations.Migration): help_text='An id attribute of an element on the current page. For example, "#section-1"', max_length=300, required=False, - validators=[ - networkapi.wagtailpages.pagemodels.customblocks.common.base_link_block.AnchorLinkValidator() - ], + validators=[networkapi.wagtailpages.validators.AnchorLinkValidator()], ), ), ("email", wagtail.blocks.EmailBlock(required=False)), @@ -1032,7 +1030,7 @@ class Migration(migrations.Migration): max_length=300, required=False, validators=[ - networkapi.wagtailpages.pagemodels.customblocks.common.base_link_block.AnchorLinkValidator() + networkapi.wagtailpages.validators.AnchorLinkValidator() ], ), ), @@ -1192,7 +1190,7 @@ class Migration(migrations.Migration): max_length=300, required=False, validators=[ - networkapi.wagtailpages.pagemodels.customblocks.common.base_link_block.AnchorLinkValidator() + networkapi.wagtailpages.validators.AnchorLinkValidator() ], ), ), @@ -1527,9 +1525,7 @@ class Migration(migrations.Migration): help_text='An id attribute of an element on the current page. For example, "#section-1"', max_length=300, required=False, - validators=[ - networkapi.wagtailpages.pagemodels.customblocks.common.base_link_block.AnchorLinkValidator() - ], + validators=[networkapi.wagtailpages.validators.AnchorLinkValidator()], ), ), ("email", wagtail.blocks.EmailBlock(required=False)), @@ -2317,9 +2313,7 @@ class Migration(migrations.Migration): help_text='An id attribute of an element on the current page. For example, "#section-1"', max_length=300, required=False, - validators=[ - networkapi.wagtailpages.pagemodels.customblocks.common.base_link_block.AnchorLinkValidator() - ], + validators=[networkapi.wagtailpages.validators.AnchorLinkValidator()], ), ), ("email", wagtail.blocks.EmailBlock(required=False)), @@ -3102,9 +3096,7 @@ class Migration(migrations.Migration): help_text='An id attribute of an element on the current page. For example, "#section-1"', max_length=300, required=False, - validators=[ - networkapi.wagtailpages.pagemodels.customblocks.common.base_link_block.AnchorLinkValidator() - ], + validators=[networkapi.wagtailpages.validators.AnchorLinkValidator()], ), ), ("email", wagtail.blocks.EmailBlock(required=False)), @@ -3773,9 +3765,7 @@ class Migration(migrations.Migration): help_text='An id attribute of an element on the current page. For example, "#section-1"', max_length=300, required=False, - validators=[ - networkapi.wagtailpages.pagemodels.customblocks.common.base_link_block.AnchorLinkValidator() - ], + validators=[networkapi.wagtailpages.validators.AnchorLinkValidator()], ), ), ("email", wagtail.blocks.EmailBlock(required=False)), @@ -4608,7 +4598,7 @@ class Migration(migrations.Migration): max_length=300, required=False, validators=[ - networkapi.wagtailpages.pagemodels.customblocks.common.base_link_block.AnchorLinkValidator() + networkapi.wagtailpages.validators.AnchorLinkValidator() ], ), ), @@ -5104,9 +5094,7 @@ class Migration(migrations.Migration): help_text='An id attribute of an element on the current page. For example, "#section-1"', max_length=300, required=False, - validators=[ - networkapi.wagtailpages.pagemodels.customblocks.common.base_link_block.AnchorLinkValidator() - ], + validators=[networkapi.wagtailpages.validators.AnchorLinkValidator()], ), ), ("email", wagtail.blocks.EmailBlock(required=False)), @@ -5939,7 +5927,7 @@ class Migration(migrations.Migration): max_length=300, required=False, validators=[ - networkapi.wagtailpages.pagemodels.customblocks.common.base_link_block.AnchorLinkValidator() + networkapi.wagtailpages.validators.AnchorLinkValidator() ], ), ), @@ -6174,7 +6162,7 @@ class Migration(migrations.Migration): max_length=300, required=False, validators=[ - networkapi.wagtailpages.pagemodels.customblocks.common.base_link_block.AnchorLinkValidator() + networkapi.wagtailpages.validators.AnchorLinkValidator() ], ), ), @@ -6279,7 +6267,7 @@ class Migration(migrations.Migration): max_length=300, required=False, validators=[ - networkapi.wagtailpages.pagemodels.customblocks.common.base_link_block.AnchorLinkValidator() + networkapi.wagtailpages.validators.AnchorLinkValidator() ], ), ), diff --git a/network-api/networkapi/wagtailpages/migrations/0127_remove_old_url_fields_from_linkblock.py b/network-api/networkapi/wagtailpages/migrations/0127_remove_old_url_fields_from_linkblock.py index f1a68778e69..b5b41ca076f 100644 --- a/network-api/networkapi/wagtailpages/migrations/0127_remove_old_url_fields_from_linkblock.py +++ b/network-api/networkapi/wagtailpages/migrations/0127_remove_old_url_fields_from_linkblock.py @@ -483,9 +483,7 @@ class Migration(migrations.Migration): help_text='An id attribute of an element on the current page. For example, "#section-1"', max_length=300, required=False, - validators=[ - networkapi.wagtailpages.pagemodels.customblocks.common.base_link_block.AnchorLinkValidator() - ], + validators=[networkapi.wagtailpages.validators.AnchorLinkValidator()], ), ), ("email", wagtail.blocks.EmailBlock(required=False)), @@ -1031,7 +1029,7 @@ class Migration(migrations.Migration): max_length=300, required=False, validators=[ - networkapi.wagtailpages.pagemodels.customblocks.common.base_link_block.AnchorLinkValidator() + networkapi.wagtailpages.validators.AnchorLinkValidator() ], ), ), @@ -1190,7 +1188,7 @@ class Migration(migrations.Migration): max_length=300, required=False, validators=[ - networkapi.wagtailpages.pagemodels.customblocks.common.base_link_block.AnchorLinkValidator() + networkapi.wagtailpages.validators.AnchorLinkValidator() ], ), ), @@ -1524,9 +1522,7 @@ class Migration(migrations.Migration): help_text='An id attribute of an element on the current page. For example, "#section-1"', max_length=300, required=False, - validators=[ - networkapi.wagtailpages.pagemodels.customblocks.common.base_link_block.AnchorLinkValidator() - ], + validators=[networkapi.wagtailpages.validators.AnchorLinkValidator()], ), ), ("email", wagtail.blocks.EmailBlock(required=False)), @@ -2313,9 +2309,7 @@ class Migration(migrations.Migration): help_text='An id attribute of an element on the current page. For example, "#section-1"', max_length=300, required=False, - validators=[ - networkapi.wagtailpages.pagemodels.customblocks.common.base_link_block.AnchorLinkValidator() - ], + validators=[networkapi.wagtailpages.validators.AnchorLinkValidator()], ), ), ("email", wagtail.blocks.EmailBlock(required=False)), @@ -3097,9 +3091,7 @@ class Migration(migrations.Migration): help_text='An id attribute of an element on the current page. For example, "#section-1"', max_length=300, required=False, - validators=[ - networkapi.wagtailpages.pagemodels.customblocks.common.base_link_block.AnchorLinkValidator() - ], + validators=[networkapi.wagtailpages.validators.AnchorLinkValidator()], ), ), ("email", wagtail.blocks.EmailBlock(required=False)), @@ -3767,9 +3759,7 @@ class Migration(migrations.Migration): help_text='An id attribute of an element on the current page. For example, "#section-1"', max_length=300, required=False, - validators=[ - networkapi.wagtailpages.pagemodels.customblocks.common.base_link_block.AnchorLinkValidator() - ], + validators=[networkapi.wagtailpages.validators.AnchorLinkValidator()], ), ), ("email", wagtail.blocks.EmailBlock(required=False)), @@ -4601,7 +4591,7 @@ class Migration(migrations.Migration): max_length=300, required=False, validators=[ - networkapi.wagtailpages.pagemodels.customblocks.common.base_link_block.AnchorLinkValidator() + networkapi.wagtailpages.validators.AnchorLinkValidator() ], ), ), @@ -5096,9 +5086,7 @@ class Migration(migrations.Migration): help_text='An id attribute of an element on the current page. For example, "#section-1"', max_length=300, required=False, - validators=[ - networkapi.wagtailpages.pagemodels.customblocks.common.base_link_block.AnchorLinkValidator() - ], + validators=[networkapi.wagtailpages.validators.AnchorLinkValidator()], ), ), ("email", wagtail.blocks.EmailBlock(required=False)), @@ -5930,7 +5918,7 @@ class Migration(migrations.Migration): max_length=300, required=False, validators=[ - networkapi.wagtailpages.pagemodels.customblocks.common.base_link_block.AnchorLinkValidator() + networkapi.wagtailpages.validators.AnchorLinkValidator() ], ), ), @@ -6164,7 +6152,7 @@ class Migration(migrations.Migration): max_length=300, required=False, validators=[ - networkapi.wagtailpages.pagemodels.customblocks.common.base_link_block.AnchorLinkValidator() + networkapi.wagtailpages.validators.AnchorLinkValidator() ], ), ), @@ -6268,7 +6256,7 @@ class Migration(migrations.Migration): max_length=300, required=False, validators=[ - networkapi.wagtailpages.pagemodels.customblocks.common.base_link_block.AnchorLinkValidator() + networkapi.wagtailpages.validators.AnchorLinkValidator() ], ), ), diff --git a/network-api/networkapi/wagtailpages/migrations/0129_allow_spacer_on_articlepage_body.py b/network-api/networkapi/wagtailpages/migrations/0129_allow_spacer_on_articlepage_body.py index 38dbd5293cb..354e6f6bbaa 100644 --- a/network-api/networkapi/wagtailpages/migrations/0129_allow_spacer_on_articlepage_body.py +++ b/network-api/networkapi/wagtailpages/migrations/0129_allow_spacer_on_articlepage_body.py @@ -478,9 +478,7 @@ class Migration(migrations.Migration): help_text='An id attribute of an element on the current page. For example, "#section-1"', max_length=300, required=False, - validators=[ - networkapi.wagtailpages.pagemodels.customblocks.common.base_link_block.AnchorLinkValidator() - ], + validators=[networkapi.wagtailpages.validators.AnchorLinkValidator()], ), ), ("email", wagtail.blocks.EmailBlock(required=False)), diff --git a/network-api/networkapi/wagtailpages/migrations/0130_make_linkto_required.py b/network-api/networkapi/wagtailpages/migrations/0130_make_linkto_required.py new file mode 100644 index 00000000000..e8c0da79b0e --- /dev/null +++ b/network-api/networkapi/wagtailpages/migrations/0130_make_linkto_required.py @@ -0,0 +1,6296 @@ +# Generated by Django 4.2.10 on 2024-03-13 14:00 + +import django.core.validators +import wagtail.blocks +import wagtail.blocks.static_block +import wagtail.contrib.table_block.blocks +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.customblocks.articles +import networkapi.wagtailpages.pagemodels.profiles +import networkapi.wagtailpages.validators + + +class Migration(migrations.Migration): + + dependencies = [ + ("wagtailpages", "0129_allow_spacer_on_articlepage_body"), + ] + + operations = [ + migrations.AlterField( + model_name="articlepage", + name="body", + field=wagtail.fields.StreamField( + [ + ( + "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.", + ), + ), + ] + ), + ), + ] + ), + ), + ] + ) + ), + ) + ] + ), + ), + ( + "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", + ), + ), + ] + ), + ), + ( + "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", + ), + ), + ( + "callout", + wagtail.blocks.RichTextBlock( + features=["bold", "italic", "link", "h2", "h3", "h4", "ul", "ol"], + template="wagtailpages/blocks/article_blockquote_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.", + ), + ) + ] + ), + ), + ( + "content", + networkapi.wagtailpages.pagemodels.customblocks.articles.ArticleRichText( + features=[ + "bold", + "italic", + "link", + "h2", + "h3", + "h4", + "h5", + "ol", + "ul", + "hr", + "document-link", + "large", + "image", + "footnotes", + ] + ), + ), + ( + "image", + wagtail.blocks.StructBlock( + [ + ("image", wagtail.images.blocks.ImageChooserBlock()), + ( + "caption", + wagtail.blocks.RichTextBlock( + features=["bold", "italic", "link"], label="Image caption", required=False + ), + ), + ("alt_text", wagtail.blocks.CharBlock(required=False)), + ( + "wide_image", + wagtail.blocks.BooleanBlock( + default=False, + help_text='Checking this will use a wider version of this image, but not full width. For an edge-to-edge image, use the "Wide Image" block.', + required=False, + ), + ), + ] + ), + ), + ( + "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, + ), + ), + ] + ) + ), + ) + ] + ), + ), + ( + "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 + ), + ), + ] + ), + ), + ( + "double_image", + wagtail.blocks.StructBlock( + [ + ("image_1", wagtail.images.blocks.ImageChooserBlock()), + ( + "image_1_caption", + wagtail.blocks.RichTextBlock( + features=["bold", "italic", "link"], label="Image caption", required=False + ), + ), + ("image_2", wagtail.images.blocks.ImageChooserBlock()), + ( + "image_2_caption", + wagtail.blocks.RichTextBlock( + features=["bold", "italic", "link"], label="Image caption", required=False + ), + ), + ] + ), + ), + ( + "full_width_image", + wagtail.blocks.StructBlock( + [ + ("image", wagtail.images.blocks.ImageChooserBlock()), + ( + "image_height", + wagtail.blocks.IntegerBlock( + default=410, + help_text="A custom height for this image. The image will be 1400px wide by this height. Note: This may cause images to look pixelated. If the browser is wider than 1400px the height will scale vertically while the width scales horizontally", + ), + ), + ( + "caption", + wagtail.blocks.RichTextBlock( + features=["bold", "italic", "link"], label="Image caption", required=False + ), + ), + ] + ), + ), + ( + "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()), + ( + "link_to", + wagtail.blocks.ChoiceBlock( + choices=[ + ("page", "Page"), + ("external_url", "External URL"), + ("relative_url", "Relative URL"), + ("email", "Email"), + ("anchor", "Anchor"), + ("file", "File"), + ("phone", "Phone"), + ], + label="Link to", + ), + ), + ("page", wagtail.blocks.PageChooserBlock(label="Page", required=False)), + ( + "external_url", + wagtail.blocks.URLBlock( + help_text="Enter a full URL including http:// or https://", + label="External URL", + max_length=300, + required=False, + ), + ), + ( + "relative_url", + wagtail.blocks.CharBlock( + help_text='A path relative to this domain. For example, "/foo/bar"', + label="Relative URL", + max_length=300, + required=False, + validators=[networkapi.wagtailpages.validators.RelativeURLValidator()], + ), + ), + ( + "anchor", + wagtail.blocks.CharBlock( + help_text='An id attribute of an element on the current page. For example, "#section-1"', + label="#", + max_length=300, + required=False, + validators=[networkapi.wagtailpages.validators.AnchorLinkValidator()], + ), + ), + ("email", wagtail.blocks.EmailBlock(required=False)), + ("file", wagtail.documents.blocks.DocumentChooserBlock(label="File", required=False)), + ("phone", wagtail.blocks.CharBlock(label="Phone", max_length=30, required=False)), + ( + "new_window", + wagtail.blocks.BooleanBlock(label="Open in new window", required=False), + ), + ( + "styling", + wagtail.blocks.ChoiceBlock( + choices=[ + ("btn-primary", "Primary button"), + ("btn-secondary", "Secondary button"), + ] + ), + ), + ] + ), + ), + ( + "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), + ), + ] + ), + ), + ( + "slider", + wagtail.blocks.StructBlock( + [ + ( + "title", + wagtail.blocks.CharBlock(help_text="Heading for the slider.", required=False), + ), + ( + "slides", + wagtail.blocks.StreamBlock( + [ + ( + "slide", + wagtail.blocks.StructBlock( + [ + ( + "title", + wagtail.blocks.CharBlock( + help_text="Heading of the card.", required=False + ), + ), + ( + "image", + wagtail.images.blocks.ImageChooserBlock( + help_text="The image associated with this event." + ), + ), + ( + "caption", + wagtail.blocks.TextBlock( + help_text="Caption for slider image", required=False + ), + ), + ( + "body", + wagtail.blocks.RichTextBlock( + blank=True, + features=["bold", "italic", "link", "large"], + required=False, + ), + ), + ( + "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, + required=False, + ), + ), + ] + ), + ) + ], + help_text="A list of slides.", + ), + ), + ] + ), + ), + ( + "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"), + ] + ), + ) + ] + ), + ), + ( + "table", + wagtail.contrib.table_block.blocks.TableBlock( + template="wagtailpages/blocks/article_table_block.html" + ), + ), + ( + "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.", + ), + ), + ] + ), + ), + ( + "advanced_table", + wagtail.blocks.StructBlock( + [ + ( + "header", + wagtail.blocks.BooleanBlock( + help_text="Display the first row as a header.", required=False + ), + ), + ( + "column", + wagtail.blocks.BooleanBlock( + help_text="Display the first column as a header.", required=False + ), + ), + ( + "caption", + wagtail.blocks.CharBlock( + help_text="A heading that identifies the overall topic of the table, and is useful for screen reader users", + required=False, + ), + ), + ( + "wide", + wagtail.blocks.BooleanBlock( + default=True, + help_text="If this is checked, the table will render wider than the other page body content.", + required=False, + ), + ), + ( + "table", + wagtail.blocks.StreamBlock( + [ + ( + "row", + wagtail.blocks.StreamBlock( + [ + ( + "cell", + wagtail.blocks.StructBlock( + [ + ( + "centered_text", + wagtail.blocks.BooleanBlock(required=False), + ), + ( + "column_width", + wagtail.blocks.IntegerBlock( + default=1, + help_text="Enter the number of extra cell columns you want to merge together. Merging a cell column will expand a cell to the right. To merge two cells together, set the column width to 2. For 3, set 3. Default is 1. Min 1. Max 20.", + validators=[ + django.core.validators.MaxValueValidator( + 20 + ), + django.core.validators.MinValueValidator( + 1 + ), + ], + ), + ), + ( + "content", + wagtail.blocks.RichTextBlock( + features=[ + "bold", + "italic", + "link", + "ul", + "ol", + ] + ), + ), + ] + ), + ) + ] + ), + ) + ] + ), + ), + ] + ), + ), + ], + use_json_field=True, + ), + ), + migrations.AlterField( + model_name="blogpage", + name="body", + field=wagtail.fields.StreamField( + [ + ( + "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.", + ), + ), + ] + ), + ), + ] + ), + ), + ] + ) + ), + ) + ] + ), + ), + ( + "paragraph", + wagtail.blocks.RichTextBlock( + features=[ + "bold", + "italic", + "link", + "h2", + "h3", + "h4", + "h5", + "ol", + "ul", + "hr", + "document-link", + ], + 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.", + ), + ) + ] + ), + ), + ( + "CTA_card", + wagtail.blocks.StructBlock( + [ + ( + "style", + wagtail.blocks.ChoiceBlock( + choices=[("pop", "Pop"), ("outline", "Outline"), ("filled", "Filled")] + ), + ), + ( + "title", + wagtail.blocks.CharBlock( + help_text="Optional title for the card.", max_length=100, required=False + ), + ), + ( + "body", + wagtail.blocks.RichTextBlock( + features=["bold", "italic", "link", "hr", "h4", "h5", "ul", "ol"], + help_text="Body text of the card.", + ), + ), + ( + "image", + wagtail.blocks.ListBlock( + wagtail.blocks.StructBlock( + [ + ("image", wagtail.images.blocks.ImageChooserBlock()), + ( + "altText", + wagtail.blocks.CharBlock( + help_text="Image description (for screen readers).", + required=True, + ), + ), + ] + ), + default=[], + max_num=1, + min_num=0, + ), + ), + ( + "button", + wagtail.blocks.ListBlock( + wagtail.blocks.StructBlock( + [ + ("label", wagtail.blocks.CharBlock()), + ( + "link_to", + wagtail.blocks.ChoiceBlock( + choices=[ + ("page", "Page"), + ("external_url", "External URL"), + ("relative_url", "Relative URL"), + ("email", "Email"), + ("anchor", "Anchor"), + ("file", "File"), + ("phone", "Phone"), + ], + label="Link to", + ), + ), + ( + "page", + wagtail.blocks.PageChooserBlock(label="Page", required=False), + ), + ( + "external_url", + wagtail.blocks.URLBlock( + help_text="Enter a full URL including http:// or https://", + label="External URL", + max_length=300, + required=False, + ), + ), + ( + "relative_url", + wagtail.blocks.CharBlock( + help_text='A path relative to this domain. For example, "/foo/bar"', + label="Relative URL", + max_length=300, + required=False, + validators=[ + networkapi.wagtailpages.validators.RelativeURLValidator() + ], + ), + ), + ( + "anchor", + wagtail.blocks.CharBlock( + help_text='An id attribute of an element on the current page. For example, "#section-1"', + label="#", + max_length=300, + required=False, + validators=[ + networkapi.wagtailpages.validators.AnchorLinkValidator() + ], + ), + ), + ("email", wagtail.blocks.EmailBlock(required=False)), + ( + "file", + wagtail.documents.blocks.DocumentChooserBlock( + label="File", required=False + ), + ), + ( + "phone", + wagtail.blocks.CharBlock( + label="Phone", max_length=30, required=False + ), + ), + ( + "new_window", + wagtail.blocks.BooleanBlock( + label="Open in new window", required=False + ), + ), + ( + "styling", + wagtail.blocks.ChoiceBlock( + choices=[ + ("btn-primary", "Primary button"), + ("btn-secondary", "Secondary button"), + ] + ), + ), + ] + ), + default=[], + max_num=1, + min_num=0, + ), + ), + ] + ), + ), + ( + "CTA_card_with_text", + wagtail.blocks.StructBlock( + [ + ( + "alignment", + wagtail.blocks.ChoiceBlock( + choices=[("right", "Right"), ("left", "Left")], + help_text="For full-width cards, please use a regular Blog CTA Card block with a separate paragraph.", + ), + ), + ( + "card", + wagtail.blocks.StructBlock( + [ + ( + "style", + wagtail.blocks.ChoiceBlock( + choices=[ + ("pop", "Pop"), + ("outline", "Outline"), + ("filled", "Filled"), + ] + ), + ), + ( + "title", + wagtail.blocks.CharBlock( + help_text="Optional title for the card.", + max_length=100, + required=False, + ), + ), + ( + "body", + wagtail.blocks.RichTextBlock( + features=["bold", "italic", "link", "hr", "h4", "h5", "ul", "ol"], + help_text="Body text of the card.", + ), + ), + ( + "image", + wagtail.blocks.ListBlock( + wagtail.blocks.StructBlock( + [ + ("image", wagtail.images.blocks.ImageChooserBlock()), + ( + "altText", + wagtail.blocks.CharBlock( + help_text="Image description (for screen readers).", + required=True, + ), + ), + ] + ), + default=[], + max_num=1, + min_num=0, + ), + ), + ( + "button", + wagtail.blocks.ListBlock( + wagtail.blocks.StructBlock( + [ + ("label", wagtail.blocks.CharBlock()), + ( + "link_to", + wagtail.blocks.ChoiceBlock( + choices=[ + ("page", "Page"), + ("external_url", "External URL"), + ("relative_url", "Relative URL"), + ("email", "Email"), + ("anchor", "Anchor"), + ("file", "File"), + ("phone", "Phone"), + ], + label="Link to", + ), + ), + ( + "page", + wagtail.blocks.PageChooserBlock( + label="Page", required=False + ), + ), + ( + "external_url", + wagtail.blocks.URLBlock( + help_text="Enter a full URL including http:// or https://", + label="External URL", + max_length=300, + required=False, + ), + ), + ( + "relative_url", + wagtail.blocks.CharBlock( + help_text='A path relative to this domain. For example, "/foo/bar"', + label="Relative URL", + max_length=300, + required=False, + validators=[ + networkapi.wagtailpages.validators.RelativeURLValidator() + ], + ), + ), + ( + "anchor", + wagtail.blocks.CharBlock( + help_text='An id attribute of an element on the current page. For example, "#section-1"', + label="#", + max_length=300, + required=False, + validators=[ + networkapi.wagtailpages.validators.AnchorLinkValidator() + ], + ), + ), + ("email", wagtail.blocks.EmailBlock(required=False)), + ( + "file", + wagtail.documents.blocks.DocumentChooserBlock( + label="File", required=False + ), + ), + ( + "phone", + wagtail.blocks.CharBlock( + label="Phone", max_length=30, required=False + ), + ), + ( + "new_window", + wagtail.blocks.BooleanBlock( + label="Open in new window", required=False + ), + ), + ( + "styling", + wagtail.blocks.ChoiceBlock( + choices=[ + ("btn-primary", "Primary button"), + ("btn-secondary", "Secondary button"), + ] + ), + ), + ] + ), + default=[], + max_num=1, + min_num=0, + ), + ), + ], + required=True, + template="wagtailpages/blocks/blog_cta_card_block_regular.html", + ), + ), + ( + "paragraph", + wagtail.blocks.RichTextBlock( + features=[ + "bold", + "italic", + "link", + "h2", + "h3", + "h4", + "h5", + "ol", + "ul", + "hr", + "document-link", + ], + help_text="Text to be displayed next to the card.", + template="wagtailpages/blocks/rich_text_block.html", + ), + ), + ] + ), + ), + ( + "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, + ), + ), + ] + ) + ), + ) + ] + ), + ), + ( + "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, + ), + ), + ] + ), + ), + ( + "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)", + ), + ), + ] + ), + ), + ( + "audio", + wagtail.blocks.StructBlock( + [ + ("audio", wagtailmedia.blocks.AudioChooserBlock()), + ("caption", wagtail.blocks.CharBlock(required=False)), + ] + ), + ), + ( + "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"])), + ] + ), + ), + ( + "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.", + ), + ), + ] + ), + ), + ( + "linkbutton", + wagtail.blocks.StructBlock( + [ + ("label", wagtail.blocks.CharBlock()), + ( + "link_to", + wagtail.blocks.ChoiceBlock( + choices=[ + ("page", "Page"), + ("external_url", "External URL"), + ("relative_url", "Relative URL"), + ("email", "Email"), + ("anchor", "Anchor"), + ("file", "File"), + ("phone", "Phone"), + ], + label="Link to", + ), + ), + ("page", wagtail.blocks.PageChooserBlock(label="Page", required=False)), + ( + "external_url", + wagtail.blocks.URLBlock( + help_text="Enter a full URL including http:// or https://", + label="External URL", + max_length=300, + required=False, + ), + ), + ( + "relative_url", + wagtail.blocks.CharBlock( + help_text='A path relative to this domain. For example, "/foo/bar"', + label="Relative URL", + max_length=300, + required=False, + validators=[networkapi.wagtailpages.validators.RelativeURLValidator()], + ), + ), + ( + "anchor", + wagtail.blocks.CharBlock( + help_text='An id attribute of an element on the current page. For example, "#section-1"', + label="#", + max_length=300, + required=False, + validators=[networkapi.wagtailpages.validators.AnchorLinkValidator()], + ), + ), + ("email", wagtail.blocks.EmailBlock(required=False)), + ("file", wagtail.documents.blocks.DocumentChooserBlock(label="File", required=False)), + ("phone", wagtail.blocks.CharBlock(label="Phone", max_length=30, required=False)), + ( + "new_window", + wagtail.blocks.BooleanBlock(label="Open in new window", required=False), + ), + ( + "styling", + wagtail.blocks.ChoiceBlock( + choices=[ + ("btn-primary", "Primary button"), + ("btn-secondary", "Secondary button"), + ] + ), + ), + ] + ), + ), + ( + "looping_video", + wagtail.blocks.StructBlock( + [ + ( + "video_url", + wagtail.blocks.CharBlock( + help_text='Log into Vimeo using 1Password and upload the desired video. Then select the video and click "Advanced", "Distribution", and "Video File Links". Copy and paste the link here.', + label="Embed URL", + ), + ) + ] + ), + ), + ( + "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, + ), + ), + ] + ), + ), + ( + "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), + ), + ] + ), + ), + ( + "slider", + wagtail.blocks.StructBlock( + [ + ( + "title", + wagtail.blocks.CharBlock(help_text="Heading for the slider.", required=False), + ), + ( + "slides", + wagtail.blocks.StreamBlock( + [ + ( + "slide", + wagtail.blocks.StructBlock( + [ + ( + "title", + wagtail.blocks.CharBlock( + help_text="Heading of the card.", required=False + ), + ), + ( + "image", + wagtail.images.blocks.ImageChooserBlock( + help_text="The image associated with this event." + ), + ), + ( + "caption", + wagtail.blocks.TextBlock( + help_text="Caption for slider image", required=False + ), + ), + ( + "body", + wagtail.blocks.RichTextBlock( + blank=True, + features=["bold", "italic", "link", "large"], + required=False, + ), + ), + ( + "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, + required=False, + ), + ), + ] + ), + ) + ], + help_text="A list of slides.", + ), + ), + ] + ), + ), + ( + "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"), + ] + ), + ) + ] + ), + ), + ( + "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", + ), + ), + ] + ), + ), + ( + "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", + ), + ), + ( + "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, + ), + ), + ] + ), + ), + ( + "newsletter_signup", + wagtail.blocks.StructBlock( + [("signup", wagtail.snippets.blocks.SnippetChooserBlock("wagtailpages.BlogSignup"))] + ), + ), + ], + use_json_field=True, + ), + ), + migrations.AlterField( + model_name="buyersguidearticlepage", + name="body", + field=wagtail.fields.StreamField( + [ + ( + "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.", + ), + ), + ] + ), + ), + ] + ), + ), + ] + ) + ), + ) + ] + ), + ), + ( + "paragraph", + wagtail.blocks.RichTextBlock( + features=[ + "bold", + "italic", + "link", + "h2", + "h3", + "h4", + "h5", + "ol", + "ul", + "hr", + "document-link", + ], + 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_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, + ), + ), + ] + ) + ), + ) + ] + ), + ), + ( + "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, + ), + ), + ] + ), + ), + ( + "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)", + ), + ), + ] + ), + ), + ( + "audio", + wagtail.blocks.StructBlock( + [ + ("audio", wagtailmedia.blocks.AudioChooserBlock()), + ("caption", wagtail.blocks.CharBlock(required=False)), + ] + ), + ), + ( + "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"])), + ] + ), + ), + ( + "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.", + ), + ), + ] + ), + ), + ( + "linkbutton", + wagtail.blocks.StructBlock( + [ + ("label", wagtail.blocks.CharBlock()), + ( + "link_to", + wagtail.blocks.ChoiceBlock( + choices=[ + ("page", "Page"), + ("external_url", "External URL"), + ("relative_url", "Relative URL"), + ("email", "Email"), + ("anchor", "Anchor"), + ("file", "File"), + ("phone", "Phone"), + ], + label="Link to", + ), + ), + ("page", wagtail.blocks.PageChooserBlock(label="Page", required=False)), + ( + "external_url", + wagtail.blocks.URLBlock( + help_text="Enter a full URL including http:// or https://", + label="External URL", + max_length=300, + required=False, + ), + ), + ( + "relative_url", + wagtail.blocks.CharBlock( + help_text='A path relative to this domain. For example, "/foo/bar"', + label="Relative URL", + max_length=300, + required=False, + validators=[networkapi.wagtailpages.validators.RelativeURLValidator()], + ), + ), + ( + "anchor", + wagtail.blocks.CharBlock( + help_text='An id attribute of an element on the current page. For example, "#section-1"', + label="#", + max_length=300, + required=False, + validators=[networkapi.wagtailpages.validators.AnchorLinkValidator()], + ), + ), + ("email", wagtail.blocks.EmailBlock(required=False)), + ("file", wagtail.documents.blocks.DocumentChooserBlock(label="File", required=False)), + ("phone", wagtail.blocks.CharBlock(label="Phone", max_length=30, required=False)), + ( + "new_window", + wagtail.blocks.BooleanBlock(label="Open in new window", required=False), + ), + ( + "styling", + wagtail.blocks.ChoiceBlock( + choices=[ + ("btn-primary", "Primary button"), + ("btn-secondary", "Secondary button"), + ] + ), + ), + ] + ), + ), + ( + "looping_video", + wagtail.blocks.StructBlock( + [ + ( + "video_url", + wagtail.blocks.CharBlock( + help_text='Log into Vimeo using 1Password and upload the desired video. Then select the video and click "Advanced", "Distribution", and "Video File Links". Copy and paste the link here.', + label="Embed URL", + ), + ) + ] + ), + ), + ( + "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, + ), + ), + ] + ), + ), + ( + "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), + ), + ] + ), + ), + ( + "slider", + wagtail.blocks.StructBlock( + [ + ( + "title", + wagtail.blocks.CharBlock(help_text="Heading for the slider.", required=False), + ), + ( + "slides", + wagtail.blocks.StreamBlock( + [ + ( + "slide", + wagtail.blocks.StructBlock( + [ + ( + "title", + wagtail.blocks.CharBlock( + help_text="Heading of the card.", required=False + ), + ), + ( + "image", + wagtail.images.blocks.ImageChooserBlock( + help_text="The image associated with this event." + ), + ), + ( + "caption", + wagtail.blocks.TextBlock( + help_text="Caption for slider image", required=False + ), + ), + ( + "body", + wagtail.blocks.RichTextBlock( + blank=True, + features=["bold", "italic", "link", "large"], + required=False, + ), + ), + ( + "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, + required=False, + ), + ), + ] + ), + ) + ], + help_text="A list of slides.", + ), + ), + ] + ), + ), + ( + "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"), + ] + ), + ) + ] + ), + ), + ( + "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", + ), + ), + ] + ), + ), + ( + "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", + ), + ), + ( + "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, + ), + ), + ] + ), + ), + ], + null=True, + use_json_field=True, + ), + ), + migrations.AlterField( + model_name="buyersguidecampaignpage", + name="body", + field=wagtail.fields.StreamField( + [ + ( + "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.", + ), + ), + ] + ), + ), + ] + ), + ), + ] + ) + ), + ) + ] + ), + ), + ( + "paragraph", + wagtail.blocks.RichTextBlock( + features=[ + "bold", + "italic", + "link", + "h2", + "h3", + "h4", + "h5", + "ol", + "ul", + "hr", + "document-link", + ], + 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_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, + ), + ), + ] + ) + ), + ) + ] + ), + ), + ( + "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, + ), + ), + ] + ), + ), + ( + "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)", + ), + ), + ] + ), + ), + ( + "audio", + wagtail.blocks.StructBlock( + [ + ("audio", wagtailmedia.blocks.AudioChooserBlock()), + ("caption", wagtail.blocks.CharBlock(required=False)), + ] + ), + ), + ( + "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"])), + ] + ), + ), + ( + "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.", + ), + ), + ] + ), + ), + ( + "linkbutton", + wagtail.blocks.StructBlock( + [ + ("label", wagtail.blocks.CharBlock()), + ( + "link_to", + wagtail.blocks.ChoiceBlock( + choices=[ + ("page", "Page"), + ("external_url", "External URL"), + ("relative_url", "Relative URL"), + ("email", "Email"), + ("anchor", "Anchor"), + ("file", "File"), + ("phone", "Phone"), + ], + label="Link to", + ), + ), + ("page", wagtail.blocks.PageChooserBlock(label="Page", required=False)), + ( + "external_url", + wagtail.blocks.URLBlock( + help_text="Enter a full URL including http:// or https://", + label="External URL", + max_length=300, + required=False, + ), + ), + ( + "relative_url", + wagtail.blocks.CharBlock( + help_text='A path relative to this domain. For example, "/foo/bar"', + label="Relative URL", + max_length=300, + required=False, + validators=[networkapi.wagtailpages.validators.RelativeURLValidator()], + ), + ), + ( + "anchor", + wagtail.blocks.CharBlock( + help_text='An id attribute of an element on the current page. For example, "#section-1"', + label="#", + max_length=300, + required=False, + validators=[networkapi.wagtailpages.validators.AnchorLinkValidator()], + ), + ), + ("email", wagtail.blocks.EmailBlock(required=False)), + ("file", wagtail.documents.blocks.DocumentChooserBlock(label="File", required=False)), + ("phone", wagtail.blocks.CharBlock(label="Phone", max_length=30, required=False)), + ( + "new_window", + wagtail.blocks.BooleanBlock(label="Open in new window", required=False), + ), + ( + "styling", + wagtail.blocks.ChoiceBlock( + choices=[ + ("btn-primary", "Primary button"), + ("btn-secondary", "Secondary button"), + ] + ), + ), + ] + ), + ), + ( + "looping_video", + wagtail.blocks.StructBlock( + [ + ( + "video_url", + wagtail.blocks.CharBlock( + help_text='Log into Vimeo using 1Password and upload the desired video. Then select the video and click "Advanced", "Distribution", and "Video File Links". Copy and paste the link here.', + label="Embed URL", + ), + ) + ] + ), + ), + ( + "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, + ), + ), + ] + ), + ), + ( + "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), + ), + ] + ), + ), + ( + "slider", + wagtail.blocks.StructBlock( + [ + ( + "title", + wagtail.blocks.CharBlock(help_text="Heading for the slider.", required=False), + ), + ( + "slides", + wagtail.blocks.StreamBlock( + [ + ( + "slide", + wagtail.blocks.StructBlock( + [ + ( + "title", + wagtail.blocks.CharBlock( + help_text="Heading of the card.", required=False + ), + ), + ( + "image", + wagtail.images.blocks.ImageChooserBlock( + help_text="The image associated with this event." + ), + ), + ( + "caption", + wagtail.blocks.TextBlock( + help_text="Caption for slider image", required=False + ), + ), + ( + "body", + wagtail.blocks.RichTextBlock( + blank=True, + features=["bold", "italic", "link", "large"], + required=False, + ), + ), + ( + "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, + required=False, + ), + ), + ] + ), + ) + ], + help_text="A list of slides.", + ), + ), + ] + ), + ), + ( + "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"), + ] + ), + ) + ] + ), + ), + ( + "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", + ), + ), + ] + ), + ), + ( + "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", + ), + ), + ( + "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, + ), + ), + ] + ), + ), + ], + use_json_field=True, + ), + ), + migrations.AlterField( + model_name="modularpage", + 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()), + ( + "link_to", + wagtail.blocks.ChoiceBlock( + choices=[ + ("page", "Page"), + ("external_url", "External URL"), + ("relative_url", "Relative URL"), + ("email", "Email"), + ("anchor", "Anchor"), + ("file", "File"), + ("phone", "Phone"), + ], + label="Link to", + ), + ), + ("page", wagtail.blocks.PageChooserBlock(label="Page", required=False)), + ( + "external_url", + wagtail.blocks.URLBlock( + help_text="Enter a full URL including http:// or https://", + label="External URL", + max_length=300, + required=False, + ), + ), + ( + "relative_url", + wagtail.blocks.CharBlock( + help_text='A path relative to this domain. For example, "/foo/bar"', + label="Relative URL", + max_length=300, + required=False, + validators=[networkapi.wagtailpages.validators.RelativeURLValidator()], + ), + ), + ( + "anchor", + wagtail.blocks.CharBlock( + help_text='An id attribute of an element on the current page. For example, "#section-1"', + label="#", + max_length=300, + required=False, + validators=[networkapi.wagtailpages.validators.AnchorLinkValidator()], + ), + ), + ("email", wagtail.blocks.EmailBlock(required=False)), + ("file", wagtail.documents.blocks.DocumentChooserBlock(label="File", required=False)), + ("phone", wagtail.blocks.CharBlock(label="Phone", max_length=30, required=False)), + ( + "new_window", + wagtail.blocks.BooleanBlock(label="Open in new window", required=False), + ), + ( + "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( + [ + ( + "heading", + wagtail.blocks.CharBlock(help_text="Heading for the block.", 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 + ), + ), + ("title", wagtail.blocks.CharBlock(help_text="Heading for the card.")), + ( + "highlighted_metadata", + wagtail.blocks.CharBlock( + help_text="Metadata to highlight on the card.", required=False + ), + ), + ( + "metadata", + wagtail.blocks.CharBlock( + help_text="Generic metadata.", required=False + ), + ), + ( + "body", + wagtail.blocks.RichTextBlock( + features=["bold"], + help_text="Body text of the card.", + required=False, + ), + ), + ( + "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, + ), + ), + ( + "cards_per_row", + wagtail.blocks.ChoiceBlock( + choices=[(2, "2"), (3, "3")], + help_text="Number of cards per row. Note: this is a max and fewer might be used if is less space is available.", + required=False, + ), + ), + ] + ), + ), + ( + "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( + [ + ( + "heading", + wagtail.blocks.CharBlock( + help_text="Heading for the block.", 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, + ), + ), + ( + "title", + wagtail.blocks.CharBlock( + help_text="Heading for the card." + ), + ), + ( + "highlighted_metadata", + wagtail.blocks.CharBlock( + help_text="Metadata to highlight on the card.", + required=False, + ), + ), + ( + "metadata", + wagtail.blocks.CharBlock( + help_text="Generic metadata.", + required=False, + ), + ), + ( + "body", + wagtail.blocks.RichTextBlock( + features=["bold"], + help_text="Body text of the card.", + required=False, + ), + ), + ( + "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, + ), + ), + ( + "cards_per_row", + wagtail.blocks.ChoiceBlock( + choices=[(2, "2"), (3, "3")], + help_text="Number of cards per row. Note: this is a max and fewer might be used if is less space is available.", + required=False, + ), + ), + ] + ), + ), + ( + "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()), + ( + "link_to", + wagtail.blocks.ChoiceBlock( + choices=[ + ("page", "Page"), + ("external_url", "External URL"), + ("relative_url", "Relative URL"), + ("email", "Email"), + ("anchor", "Anchor"), + ("file", "File"), + ("phone", "Phone"), + ], + label="Link to", + ), + ), + ( + "page", + wagtail.blocks.PageChooserBlock( + label="Page", required=False + ), + ), + ( + "external_url", + wagtail.blocks.URLBlock( + help_text="Enter a full URL including http:// or https://", + label="External URL", + max_length=300, + required=False, + ), + ), + ( + "relative_url", + wagtail.blocks.CharBlock( + help_text='A path relative to this domain. For example, "/foo/bar"', + label="Relative URL", + max_length=300, + required=False, + validators=[ + networkapi.wagtailpages.validators.RelativeURLValidator() + ], + ), + ), + ( + "anchor", + wagtail.blocks.CharBlock( + help_text='An id attribute of an element on the current page. For example, "#section-1"', + label="#", + max_length=300, + required=False, + validators=[ + networkapi.wagtailpages.validators.AnchorLinkValidator() + ], + ), + ), + ("email", wagtail.blocks.EmailBlock(required=False)), + ( + "file", + wagtail.documents.blocks.DocumentChooserBlock( + label="File", required=False + ), + ), + ( + "phone", + wagtail.blocks.CharBlock( + label="Phone", max_length=30, required=False + ), + ), + ( + "new_window", + wagtail.blocks.BooleanBlock( + label="Open in new window", required=False + ), + ), + ( + "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.", + ), + ), + ] + ), + ), + ] + ), + ), + ] + ) + ), + ) + ] + ), + ), + ], + use_json_field=True, + ), + ), + migrations.AlterField( + model_name="primarypage", + 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()), + ( + "link_to", + wagtail.blocks.ChoiceBlock( + choices=[ + ("page", "Page"), + ("external_url", "External URL"), + ("relative_url", "Relative URL"), + ("email", "Email"), + ("anchor", "Anchor"), + ("file", "File"), + ("phone", "Phone"), + ], + label="Link to", + ), + ), + ("page", wagtail.blocks.PageChooserBlock(label="Page", required=False)), + ( + "external_url", + wagtail.blocks.URLBlock( + help_text="Enter a full URL including http:// or https://", + label="External URL", + max_length=300, + required=False, + ), + ), + ( + "relative_url", + wagtail.blocks.CharBlock( + help_text='A path relative to this domain. For example, "/foo/bar"', + label="Relative URL", + max_length=300, + required=False, + validators=[networkapi.wagtailpages.validators.RelativeURLValidator()], + ), + ), + ( + "anchor", + wagtail.blocks.CharBlock( + help_text='An id attribute of an element on the current page. For example, "#section-1"', + label="#", + max_length=300, + required=False, + validators=[networkapi.wagtailpages.validators.AnchorLinkValidator()], + ), + ), + ("email", wagtail.blocks.EmailBlock(required=False)), + ("file", wagtail.documents.blocks.DocumentChooserBlock(label="File", required=False)), + ("phone", wagtail.blocks.CharBlock(label="Phone", max_length=30, required=False)), + ( + "new_window", + wagtail.blocks.BooleanBlock(label="Open in new window", required=False), + ), + ( + "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( + [ + ( + "heading", + wagtail.blocks.CharBlock(help_text="Heading for the block.", 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 + ), + ), + ("title", wagtail.blocks.CharBlock(help_text="Heading for the card.")), + ( + "highlighted_metadata", + wagtail.blocks.CharBlock( + help_text="Metadata to highlight on the card.", required=False + ), + ), + ( + "metadata", + wagtail.blocks.CharBlock( + help_text="Generic metadata.", required=False + ), + ), + ( + "body", + wagtail.blocks.RichTextBlock( + features=["bold"], + help_text="Body text of the card.", + required=False, + ), + ), + ( + "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, + ), + ), + ( + "cards_per_row", + wagtail.blocks.ChoiceBlock( + choices=[(2, "2"), (3, "3")], + help_text="Number of cards per row. Note: this is a max and fewer might be used if is less space is available.", + required=False, + ), + ), + ] + ), + ), + ( + "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( + [ + ( + "heading", + wagtail.blocks.CharBlock( + help_text="Heading for the block.", 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, + ), + ), + ( + "title", + wagtail.blocks.CharBlock( + help_text="Heading for the card." + ), + ), + ( + "highlighted_metadata", + wagtail.blocks.CharBlock( + help_text="Metadata to highlight on the card.", + required=False, + ), + ), + ( + "metadata", + wagtail.blocks.CharBlock( + help_text="Generic metadata.", + required=False, + ), + ), + ( + "body", + wagtail.blocks.RichTextBlock( + features=["bold"], + help_text="Body text of the card.", + required=False, + ), + ), + ( + "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, + ), + ), + ( + "cards_per_row", + wagtail.blocks.ChoiceBlock( + choices=[(2, "2"), (3, "3")], + help_text="Number of cards per row. Note: this is a max and fewer might be used if is less space is available.", + required=False, + ), + ), + ] + ), + ), + ( + "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()), + ( + "link_to", + wagtail.blocks.ChoiceBlock( + choices=[ + ("page", "Page"), + ("external_url", "External URL"), + ("relative_url", "Relative URL"), + ("email", "Email"), + ("anchor", "Anchor"), + ("file", "File"), + ("phone", "Phone"), + ], + label="Link to", + ), + ), + ( + "page", + wagtail.blocks.PageChooserBlock( + label="Page", required=False + ), + ), + ( + "external_url", + wagtail.blocks.URLBlock( + help_text="Enter a full URL including http:// or https://", + label="External URL", + max_length=300, + required=False, + ), + ), + ( + "relative_url", + wagtail.blocks.CharBlock( + help_text='A path relative to this domain. For example, "/foo/bar"', + label="Relative URL", + max_length=300, + required=False, + validators=[ + networkapi.wagtailpages.validators.RelativeURLValidator() + ], + ), + ), + ( + "anchor", + wagtail.blocks.CharBlock( + help_text='An id attribute of an element on the current page. For example, "#section-1"', + label="#", + max_length=300, + required=False, + validators=[ + networkapi.wagtailpages.validators.AnchorLinkValidator() + ], + ), + ), + ("email", wagtail.blocks.EmailBlock(required=False)), + ( + "file", + wagtail.documents.blocks.DocumentChooserBlock( + label="File", required=False + ), + ), + ( + "phone", + wagtail.blocks.CharBlock( + label="Phone", max_length=30, required=False + ), + ), + ( + "new_window", + wagtail.blocks.BooleanBlock( + label="Open in new window", required=False + ), + ), + ( + "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.", + ), + ), + ] + ), + ), + ] + ), + ), + ] + ) + ), + ) + ] + ), + ), + ], + use_json_field=True, + ), + ), + migrations.AlterField( + model_name="rcclandingpage", + name="aside_cta", + field=wagtail.fields.StreamField( + [ + ( + "cta", + wagtail.blocks.StructBlock( + [ + ("title", wagtail.blocks.CharBlock(help_text="Heading for the card.")), + ("body", wagtail.blocks.TextBlock(help_text="Body text of the card.")), + ( + "button", + wagtail.blocks.StructBlock( + [ + ("label", wagtail.blocks.CharBlock()), + ( + "link_to", + wagtail.blocks.ChoiceBlock( + choices=[ + ("page", "Page"), + ("external_url", "External URL"), + ("relative_url", "Relative URL"), + ("email", "Email"), + ("anchor", "Anchor"), + ("file", "File"), + ("phone", "Phone"), + ], + label="Link to", + ), + ), + ("page", wagtail.blocks.PageChooserBlock(label="Page", required=False)), + ( + "external_url", + wagtail.blocks.URLBlock( + help_text="Enter a full URL including http:// or https://", + label="External URL", + max_length=300, + required=False, + ), + ), + ( + "relative_url", + wagtail.blocks.CharBlock( + help_text='A path relative to this domain. For example, "/foo/bar"', + label="Relative URL", + max_length=300, + required=False, + validators=[ + networkapi.wagtailpages.validators.RelativeURLValidator() + ], + ), + ), + ( + "anchor", + wagtail.blocks.CharBlock( + help_text='An id attribute of an element on the current page. For example, "#section-1"', + label="#", + max_length=300, + required=False, + validators=[ + networkapi.wagtailpages.validators.AnchorLinkValidator() + ], + ), + ), + ("email", wagtail.blocks.EmailBlock(required=False)), + ( + "file", + wagtail.documents.blocks.DocumentChooserBlock( + label="File", required=False + ), + ), + ( + "phone", + wagtail.blocks.CharBlock(label="Phone", max_length=30, required=False), + ), + ( + "new_window", + wagtail.blocks.BooleanBlock( + label="Open in new window", required=False + ), + ), + ( + "styling", + wagtail.blocks.ChoiceBlock( + choices=[ + ("btn-primary", "Primary button"), + ("btn-secondary", "Secondary button"), + ] + ), + ), + ] + ), + ), + ] + ), + ) + ], + blank=True, + null=True, + use_json_field=True, + ), + ), + migrations.AlterField( + model_name="researchlandingpage", + name="aside_cta", + field=wagtail.fields.StreamField( + [ + ( + "cta", + wagtail.blocks.StructBlock( + [ + ("title", wagtail.blocks.CharBlock(help_text="Heading for the card.")), + ("body", wagtail.blocks.TextBlock(help_text="Body text of the card.")), + ( + "button", + wagtail.blocks.StructBlock( + [ + ("label", wagtail.blocks.CharBlock()), + ( + "link_to", + wagtail.blocks.ChoiceBlock( + choices=[ + ("page", "Page"), + ("external_url", "External URL"), + ("relative_url", "Relative URL"), + ("email", "Email"), + ("anchor", "Anchor"), + ("file", "File"), + ("phone", "Phone"), + ], + label="Link to", + ), + ), + ("page", wagtail.blocks.PageChooserBlock(label="Page", required=False)), + ( + "external_url", + wagtail.blocks.URLBlock( + help_text="Enter a full URL including http:// or https://", + label="External URL", + max_length=300, + required=False, + ), + ), + ( + "relative_url", + wagtail.blocks.CharBlock( + help_text='A path relative to this domain. For example, "/foo/bar"', + label="Relative URL", + max_length=300, + required=False, + validators=[ + networkapi.wagtailpages.validators.RelativeURLValidator() + ], + ), + ), + ( + "anchor", + wagtail.blocks.CharBlock( + help_text='An id attribute of an element on the current page. For example, "#section-1"', + label="#", + max_length=300, + required=False, + validators=[ + networkapi.wagtailpages.validators.AnchorLinkValidator() + ], + ), + ), + ("email", wagtail.blocks.EmailBlock(required=False)), + ( + "file", + wagtail.documents.blocks.DocumentChooserBlock( + label="File", required=False + ), + ), + ( + "phone", + wagtail.blocks.CharBlock(label="Phone", max_length=30, required=False), + ), + ( + "new_window", + wagtail.blocks.BooleanBlock( + label="Open in new window", required=False + ), + ), + ( + "styling", + wagtail.blocks.ChoiceBlock( + choices=[ + ("btn-primary", "Primary button"), + ("btn-secondary", "Secondary button"), + ] + ), + ), + ] + ), + ), + ] + ), + ) + ], + blank=True, + null=True, + use_json_field=True, + ), + ), + ] diff --git a/network-api/networkapi/wagtailpages/pagemodels/customblocks/common/base_link_block.py b/network-api/networkapi/wagtailpages/pagemodels/customblocks/common/base_link_block.py index 14242571f13..9929e6697c1 100644 --- a/network-api/networkapi/wagtailpages/pagemodels/customblocks/common/base_link_block.py +++ b/network-api/networkapi/wagtailpages/pagemodels/customblocks/common/base_link_block.py @@ -4,7 +4,7 @@ from wagtail import blocks from wagtail.blocks.struct_block import StructBlockAdapter -from networkapi.wagtailpages.validators import AnchorLinkValidator, RelativeURLValidator +from networkapi.wagtailpages.validators import RelativeURLValidator class BaseLinkValue(blocks.StructValue): @@ -22,12 +22,6 @@ def get_external_url_link(self): def get_relative_url_link(self): return self.get("relative_url") - def get_email_link(self): - return f"mailto:{self.get('email')}" - - def get_anchor_link(self): - return f"#{self.get('anchor')}" - @property def url(self): link_to = self.get("link_to") @@ -43,15 +37,14 @@ def get_link_to(self): class BaseLinkBlock(blocks.StructBlock): + label = blocks.CharBlock() + link_to = blocks.ChoiceBlock( choices=[ ("page", "Page"), ("external_url", "External URL"), ("relative_url", "Relative URL"), - ("email", "Email"), - ("anchor", "Anchor"), ], - required=False, label="Link to", ) page = blocks.PageChooserBlock(required=False, label="Page") @@ -68,14 +61,6 @@ class BaseLinkBlock(blocks.StructBlock): label="Relative URL", help_text='A path relative to this domain. For example, "/foo/bar"', ) - anchor = blocks.CharBlock( - max_length=300, - required=False, - validators=[AnchorLinkValidator()], - label="#", - help_text='An id attribute of an element on the current page. For example, "#section-1"', - ) - email = blocks.EmailBlock(required=False) class Meta: abstract = True @@ -86,8 +71,6 @@ def get_default_values(self): "page": None, "external_url": "", "relative_url": "", - "anchor": "", - "email": "", } def clean(self, value): diff --git a/network-api/networkapi/wagtailpages/pagemodels/customblocks/link_block.py b/network-api/networkapi/wagtailpages/pagemodels/customblocks/link_block.py index 0ae7decab6d..b661520635d 100644 --- a/network-api/networkapi/wagtailpages/pagemodels/customblocks/link_block.py +++ b/network-api/networkapi/wagtailpages/pagemodels/customblocks/link_block.py @@ -1,5 +1,3 @@ -from collections import OrderedDict - from wagtail import blocks from wagtail.documents.blocks import DocumentChooserBlock from wagtail.telepath import register @@ -9,9 +7,16 @@ BaseLinkBlockAdapter, BaseLinkValue, ) +from networkapi.wagtailpages.validators import AnchorLinkValidator class LinkValue(BaseLinkValue): + def get_email_link(self): + return f"mailto:{self.get('email')}" + + def get_anchor_link(self): + return f"#{self.get('anchor')}" + def get_phone_link(self): return "tel:{}".format(self.get("phone")) @@ -33,27 +38,23 @@ class LinkBlock(BaseLinkBlock): ], label="Link to", ) + anchor = blocks.CharBlock( + max_length=300, + required=False, + validators=[AnchorLinkValidator()], + label="#", + help_text='An id attribute of an element on the current page. For example, "#section-1"', + ) + email = blocks.EmailBlock(required=False) file = DocumentChooserBlock(required=False, label="File") phone = blocks.CharBlock(max_length=30, required=False, label="Phone") - label = blocks.CharBlock() - new_window = blocks.BooleanBlock(label="Open in new window", required=False) - def __init__(self, local_blocks=None, **kwargs): - super().__init__(local_blocks, **kwargs) - # Reoder child_blocks so that label is first - self.child_blocks = self.base_blocks.copy() - child_blocks = OrderedDict( - { - "label": self.child_blocks.pop("label"), - } - ) - child_blocks.update({k: v for k, v in self.child_blocks.items()}) - self.child_blocks = child_blocks - def get_default_values(self): default_values = super().get_default_values() + default_values["anchor"] = "" + default_values["email"] = "" default_values["file"] = None default_values["phone"] = "" return default_values From 95cd4c7c899dea0b3fc5642ac14e57df30274db1 Mon Sep 17 00:00:00 2001 From: Jhonatan Lopes Date: Thu, 14 Mar 2024 11:18:02 -0300 Subject: [PATCH 02/18] [Main Nav] Navigation item [TP-265](#12039) * Add description to NavLinkBlock * Set max length --- network-api/networkapi/nav/blocks.py | 3 +++ network-api/networkapi/nav/factories.py | 1 + 2 files changed, 4 insertions(+) diff --git a/network-api/networkapi/nav/blocks.py b/network-api/networkapi/nav/blocks.py index d71df666ff6..e89b216dc90 100644 --- a/network-api/networkapi/nav/blocks.py +++ b/network-api/networkapi/nav/blocks.py @@ -1,3 +1,4 @@ +from wagtail import blocks from wagtail.telepath import register from networkapi.wagtailpages.pagemodels.customblocks.common.base_link_block import ( @@ -17,6 +18,8 @@ def open_in_new_window(self) -> bool: class NavLinkBlock(BaseLinkBlock): + description = blocks.CharBlock(required=False, max_length=100) + class Meta: value_class = NavLinkValue label = "Navigation Link" diff --git a/network-api/networkapi/nav/factories.py b/network-api/networkapi/nav/factories.py index aec8757a02b..5be975fa689 100644 --- a/network-api/networkapi/nav/factories.py +++ b/network-api/networkapi/nav/factories.py @@ -41,6 +41,7 @@ class Params: relative_url_link = factory.Trait(link_to="relative_url", relative_url=f'/{factory.Faker("uri_path")}') label = factory.Faker("sentence", nb_words=3) + description = factory.Faker("sentence", nb_words=6) # Setup default link as external URL (it won't pass validation without a link type defined though # so it's still necessary to use the factory with traits) From bb7db18265fcdac10e387ff79f8ea820f04acccf Mon Sep 17 00:00:00 2001 From: Jhonatan Lopes Date: Fri, 15 Mar 2024 13:55:12 -0300 Subject: [PATCH 03/18] [Main nav] Navigation column [TP-266] (#12047) * Add description to NavLinkBlock * Rename NavLinkBlock to NavItem * Add NavButtonBlock * Fix test name * Add a NavColumn model --- network-api/networkapi/nav/blocks.py | 46 ++++++- network-api/networkapi/nav/factories.py | 67 +++++++++- .../networkapi/nav/tests/test_blocks.py | 115 +++++++++++++++--- 3 files changed, 206 insertions(+), 22 deletions(-) diff --git a/network-api/networkapi/nav/blocks.py b/network-api/networkapi/nav/blocks.py index e89b216dc90..d5cc0573a69 100644 --- a/network-api/networkapi/nav/blocks.py +++ b/network-api/networkapi/nav/blocks.py @@ -1,3 +1,5 @@ +from collections import OrderedDict + from wagtail import blocks from wagtail.telepath import register @@ -8,7 +10,7 @@ ) -class NavLinkValue(BaseLinkValue): +class NavItemValue(BaseLinkValue): @property def open_in_new_window(self) -> bool: link_to = self.get("link_to") @@ -17,14 +19,50 @@ def open_in_new_window(self) -> bool: return False -class NavLinkBlock(BaseLinkBlock): +class NavItem(BaseLinkBlock): description = blocks.CharBlock(required=False, max_length=100) + def __init__(self, local_blocks=None, **kwargs): + # Use __init__ method to change the order of the blocks when constructing + # them through inheritance + super().__init__(local_blocks, **kwargs) + self.child_blocks = self.base_blocks.copy() + child_blocks = OrderedDict( + { + "label": self.child_blocks.pop("label"), + "description": self.child_blocks.pop("description"), + } + ) + child_blocks.update({k: v for k, v in self.child_blocks.items()}) + self.child_blocks = child_blocks + class Meta: - value_class = NavLinkValue + value_class = NavItemValue label = "Navigation Link" icon = "link" template = "nav/blocks/nav_link_block.html" -register(BaseLinkBlockAdapter(), NavLinkBlock) +register(BaseLinkBlockAdapter(), NavItem) + + +class NavButton(BaseLinkBlock): + class Meta: + value_class = NavItemValue + label = "Navigation Button" + icon = "link" + template = "nav/blocks/nav_button_block.html" + + +register(BaseLinkBlockAdapter(), NavButton) + + +class NavColumn(blocks.StructBlock): + title = blocks.CharBlock(max_length=100) + links = blocks.ListBlock(NavItem, min_num=1, max_num=4) + button = blocks.ListBlock(NavButton, required=False, min_num=0, max_num=1) + + class Meta: + label = "Navigation Column" + icon = "list-ul" + template = "nav/blocks/nav_column_block.html" diff --git a/network-api/networkapi/nav/factories.py b/network-api/networkapi/nav/factories.py index 5be975fa689..935bf6f84c8 100644 --- a/network-api/networkapi/nav/factories.py +++ b/network-api/networkapi/nav/factories.py @@ -2,10 +2,10 @@ import wagtail_factories from wagtail import models as wagtail_models -from networkapi.nav.blocks import NavLinkBlock, NavLinkValue +from networkapi.nav import blocks as nav_blocks -class NavLinkBlockFactory(wagtail_factories.StructBlockFactory): +class NavItemFactory(wagtail_factories.StructBlockFactory): """Factory for NavLinkBlock. Use traits to create instances based on the type of link needed: @@ -22,12 +22,12 @@ class NavLinkBlockFactory(wagtail_factories.StructBlockFactory): """ class Meta: - model = NavLinkBlock + model = nav_blocks.NavItem @classmethod def _construct_struct_value(cls, block_class, params): """Use NavLinkValue to create the StructValue instance.""" - return NavLinkValue( + return nav_blocks.NavItemValue( block_class(), [(name, value) for name, value in params.items()], ) @@ -51,3 +51,62 @@ class Params: page = None external_url = "" relative_url = "" + + +class NavButtonFactory(wagtail_factories.StructBlockFactory): + """Factory for NavButtonBlock. + + Use traits to create instances based on the type of link needed: + - page_link: link to a page + - external_url_link: link to a custom external URL + - relative_url_link: link to a relative URL + + Example: + ``` + block = NavButtonBlockFactory(page_link=True) + block = NavButtonBlockFactory(external_url_link=True) + block = NavButtonBlockFactory(relative_url_link=True) + ``` + """ + + class Meta: + model = nav_blocks.NavButton + + @classmethod + def _construct_struct_value(cls, block_class, params): + """Use NavLinkValue to create the StructValue instance.""" + return nav_blocks.NavItemValue( + block_class(), + [(name, value) for name, value in params.items()], + ) + + class Params: + page_link = factory.Trait( + link_to="page", + page=factory.Iterator(wagtail_models.Page.objects.filter(locale_id="1")), + ) + external_url_link = factory.Trait(link_to="external_url", external_url=factory.Faker("url")) + relative_url_link = factory.Trait(link_to="relative_url", relative_url=f'/{factory.Faker("uri_path")}') + + label = factory.Faker("sentence", nb_words=3) + + # Setup default link as external URL (it won't pass validation without a link type defined though + # so it's still necessary to use the factory with traits) + link_to = "external_url" + # Set all link types to None by default. Only define the needed link type in the factory + # trait to avoid conflicts + page = None + external_url = "" + relative_url = "" + + +class NavColumnFactory(wagtail_factories.StructBlockFactory): + class Meta: + model = nav_blocks.NavColumn + + class Params: + with_button = False + + title = factory.Faker("sentence", nb_words=3) + links = factory.List([factory.SubFactory(NavItemFactory) for _ in range(4)]) + button = factory.LazyAttribute(lambda o: [NavButtonFactory()] if o.with_button else []) diff --git a/network-api/networkapi/nav/tests/test_blocks.py b/network-api/networkapi/nav/tests/test_blocks.py index b1a04c798a7..92ab08aa114 100644 --- a/network-api/networkapi/nav/tests/test_blocks.py +++ b/network-api/networkapi/nav/tests/test_blocks.py @@ -1,23 +1,23 @@ from django.test import TestCase -from wagtail.blocks import StreamBlockValidationError +from wagtail.blocks import StreamBlockValidationError, StructBlockValidationError from wagtail.models import Locale, Page -from networkapi.nav.blocks import NavLinkBlock -from networkapi.nav.factories import NavLinkBlockFactory +from networkapi.nav import blocks as nav_blocks +from networkapi.nav import factories as nav_factories -class TestLinkBlock(TestCase): +class TestNavItemBlock(TestCase): def test_default(self): - """Assert that default NavLinkBlock factory works and is an external URL.""" - block = NavLinkBlockFactory() + """Assert that default nav_blocks.NavItem factory works and is an external URL.""" + block = nav_factories.NavItemFactory() # Assert that the page link is custom URL and that it is correct url = block["external_url"] self.assertEqual(block.url, url) def test_page_link(self): - """Create a NavLinkBlock with a page link.""" - block = NavLinkBlockFactory(page_link=True) + """Create a nav_blocks.NavItem with a page link.""" + block = nav_factories.NavItemFactory(page_link=True) page = block["page"] self.assertIsNotNone(page) @@ -32,8 +32,8 @@ def test_page_link(self): self.assertEqual(block["relative_url"], "") def test_external_url_link(self): - """Create a NavLinkBlock with a custom/external URL.""" - block = NavLinkBlockFactory(external_url_link=True) + """Create a nav_blocks.NavItem with a custom/external URL.""" + block = nav_factories.NavItemFactory(external_url_link=True) # Assert that the URL is a URL url = block["external_url"] @@ -46,8 +46,8 @@ def test_external_url_link(self): self.assertEqual(block["relative_url"], "") def test_relative_url_link(self): - """Create a NavLinkBlock with a relative URL.""" - block = NavLinkBlockFactory(relative_url_link=True) + """Create a nav_blocks.NavItem with a relative URL.""" + block = nav_factories.NavItemFactory(relative_url_link=True) # Assert that the URL is a URL url = block["relative_url"] @@ -61,5 +61,92 @@ def test_relative_url_link(self): def test_needs_to_provide_at_least_one_link(self): with self.assertRaises(StreamBlockValidationError): - block = NavLinkBlockFactory() - NavLinkBlock().clean(block) + block = nav_factories.NavItemFactory() + nav_blocks.NavItem().clean(block) + + +class TestNavButton(TestCase): + def test_default(self): + """Assert that default nav_blocks.NavButton factory works and is an external URL.""" + block = nav_factories.NavButtonFactory() + + # Assert that the page link is custom URL and that it is correct + url = block["external_url"] + self.assertEqual(block.url, url) + + def test_page_link(self): + """Create a nav_blocks.NavButton with a page link.""" + block = nav_factories.NavButtonFactory(page_link=True) + + page = block["page"] + self.assertIsNotNone(page) + self.assertTrue(isinstance(page, Page)) + default_locale = Locale.get_default() + self.assertEqual(page.locale, default_locale) + + # Assert that other fields are empty + self.assertEqual(block["external_url"], "") + self.assertEqual(block["relative_url"], "") + + def test_external_url_link(self): + """Create a nav_blocks.NavButton with a custom/external URL.""" + block = nav_factories.NavButtonFactory(external_url_link=True) + + # Assert that the URL is a URL + url = block["external_url"] + self.assertIsNotNone(url) + + # Assert that other fields are empty + self.assertIsNone(block["page"]) + self.assertEqual(block["relative_url"], "") + + def test_relative_url_link(self): + """Create a nav_blocks.NavButton with a relative URL.""" + block = nav_factories.NavButtonFactory(relative_url_link=True) + + # Assert that the URL is a URL + url = block["relative_url"] + self.assertIsNotNone(url) + + # Assert that other fields are empty + self.assertIsNone(block["page"]) + self.assertEqual(block["external_url"], "") + + def test_needs_to_provide_at_least_one_link(self): + with self.assertRaises(StreamBlockValidationError): + block = nav_factories.NavItemFactory() + nav_blocks.NavItem().clean(block) + + +class TestNavColumnBlock(TestCase): + def test_default(self): + """Assert that default nav_blocks.NavColumn factory works and is an external URL.""" + block = nav_factories.NavColumnFactory() + + self.assertEqual(len(block["links"]), 4) + for link in block["links"]: + self.assertIsInstance(link.block, nav_blocks.NavItem) + self.assertIsInstance(link, nav_blocks.NavItemValue) + self.assertCountEqual(block["button"], []) + + def test_with_button(self): + """Create a nav_blocks.NavColumn with a button.""" + block = nav_factories.NavColumnFactory(with_button=True) + + self.assertEqual(len(block["button"]), 1) + self.assertIsInstance(block["button"][0].block, nav_blocks.NavButton) + + def test_needs_to_provide_at_least_one_link(self): + with self.assertRaises(StructBlockValidationError): + block = nav_factories.NavColumnFactory(links=[]) + nav_blocks.NavColumn().clean(block) + + def test_needs_to_provide_at_most_four_links(self): + with self.assertRaises(StructBlockValidationError): + block = nav_factories.NavColumnFactory(links=[nav_factories.NavItemFactory() for _ in range(5)]) + nav_blocks.NavColumn().clean(block) + + def test_cannot_have_more_than_one_button(self): + with self.assertRaises(StructBlockValidationError): + block = nav_factories.NavColumnFactory(button=[nav_factories.NavButtonFactory() for _ in range(2)]) + nav_blocks.NavColumn().clean(block) From 20335ea0d5660bc6c02300d1435fd1027326ddcf Mon Sep 17 00:00:00 2001 From: Jhonatan Lopes Date: Thu, 28 Mar 2024 13:28:08 -0300 Subject: [PATCH 04/18] Delete duplicated migrations (#12116) --- .../migrations/0009_make_linkto_required.py | 1348 ---- .../migrations/0049_make_linkto_required.py | 1814 ----- .../migrations/0130_make_linkto_required.py | 6296 ----------------- 3 files changed, 9458 deletions(-) delete mode 100644 network-api/networkapi/donate/migrations/0009_make_linkto_required.py delete mode 100644 network-api/networkapi/mozfest/migrations/0049_make_linkto_required.py delete mode 100644 network-api/networkapi/wagtailpages/migrations/0130_make_linkto_required.py diff --git a/network-api/networkapi/donate/migrations/0009_make_linkto_required.py b/network-api/networkapi/donate/migrations/0009_make_linkto_required.py deleted file mode 100644 index cfbbb7f14e9..00000000000 --- a/network-api/networkapi/donate/migrations/0009_make_linkto_required.py +++ /dev/null @@ -1,1348 +0,0 @@ -# Generated by Django 4.2.10 on 2024-03-13 14:00 - -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 -from django.db import migrations - -import networkapi.wagtailpages.pagemodels.blog.blog_topic -import networkapi.wagtailpages.pagemodels.profiles -import networkapi.wagtailpages.validators - - -class Migration(migrations.Migration): - - dependencies = [ - ("donate", "0008_remove_old_url_fields_from_linkblock"), - ] - - operations = [ - migrations.AlterField( - model_name="donatehelppage", - 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()), - ( - "link_to", - wagtail.blocks.ChoiceBlock( - choices=[ - ("page", "Page"), - ("external_url", "External URL"), - ("relative_url", "Relative URL"), - ("email", "Email"), - ("anchor", "Anchor"), - ("file", "File"), - ("phone", "Phone"), - ], - label="Link to", - ), - ), - ("page", wagtail.blocks.PageChooserBlock(label="Page", required=False)), - ( - "external_url", - wagtail.blocks.URLBlock( - help_text="Enter a full URL including http:// or https://", - label="External URL", - max_length=300, - required=False, - ), - ), - ( - "relative_url", - wagtail.blocks.CharBlock( - help_text='A path relative to this domain. For example, "/foo/bar"', - label="Relative URL", - max_length=300, - required=False, - validators=[networkapi.wagtailpages.validators.RelativeURLValidator()], - ), - ), - ( - "anchor", - wagtail.blocks.CharBlock( - help_text='An id attribute of an element on the current page. For example, "#section-1"', - label="#", - max_length=300, - required=False, - validators=[networkapi.wagtailpages.validators.AnchorLinkValidator()], - ), - ), - ("email", wagtail.blocks.EmailBlock(required=False)), - ("file", wagtail.documents.blocks.DocumentChooserBlock(label="File", required=False)), - ("phone", wagtail.blocks.CharBlock(label="Phone", max_length=30, required=False)), - ( - "new_window", - wagtail.blocks.BooleanBlock(label="Open in new window", required=False), - ), - ( - "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( - [ - ( - "heading", - wagtail.blocks.CharBlock(help_text="Heading for the block.", 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 - ), - ), - ("title", wagtail.blocks.CharBlock(help_text="Heading for the card.")), - ( - "highlighted_metadata", - wagtail.blocks.CharBlock( - help_text="Metadata to highlight on the card.", required=False - ), - ), - ( - "metadata", - wagtail.blocks.CharBlock( - help_text="Generic metadata.", required=False - ), - ), - ( - "body", - wagtail.blocks.RichTextBlock( - features=["bold"], - help_text="Body text of the card.", - required=False, - ), - ), - ( - "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, - ), - ), - ( - "cards_per_row", - wagtail.blocks.ChoiceBlock( - choices=[(2, "2"), (3, "3")], - help_text="Number of cards per row. Note: this is a max and fewer might be used if is less space is available.", - required=False, - ), - ), - ] - ), - ), - ( - "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( - [ - ( - "heading", - wagtail.blocks.CharBlock( - help_text="Heading for the block.", 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, - ), - ), - ( - "title", - wagtail.blocks.CharBlock( - help_text="Heading for the card." - ), - ), - ( - "highlighted_metadata", - wagtail.blocks.CharBlock( - help_text="Metadata to highlight on the card.", - required=False, - ), - ), - ( - "metadata", - wagtail.blocks.CharBlock( - help_text="Generic metadata.", - required=False, - ), - ), - ( - "body", - wagtail.blocks.RichTextBlock( - features=["bold"], - help_text="Body text of the card.", - required=False, - ), - ), - ( - "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, - ), - ), - ( - "cards_per_row", - wagtail.blocks.ChoiceBlock( - choices=[(2, "2"), (3, "3")], - help_text="Number of cards per row. Note: this is a max and fewer might be used if is less space is available.", - required=False, - ), - ), - ] - ), - ), - ( - "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()), - ( - "link_to", - wagtail.blocks.ChoiceBlock( - choices=[ - ("page", "Page"), - ("external_url", "External URL"), - ("relative_url", "Relative URL"), - ("email", "Email"), - ("anchor", "Anchor"), - ("file", "File"), - ("phone", "Phone"), - ], - label="Link to", - ), - ), - ( - "page", - wagtail.blocks.PageChooserBlock( - label="Page", required=False - ), - ), - ( - "external_url", - wagtail.blocks.URLBlock( - help_text="Enter a full URL including http:// or https://", - label="External URL", - max_length=300, - required=False, - ), - ), - ( - "relative_url", - wagtail.blocks.CharBlock( - help_text='A path relative to this domain. For example, "/foo/bar"', - label="Relative URL", - max_length=300, - required=False, - validators=[ - networkapi.wagtailpages.validators.RelativeURLValidator() - ], - ), - ), - ( - "anchor", - wagtail.blocks.CharBlock( - help_text='An id attribute of an element on the current page. For example, "#section-1"', - label="#", - max_length=300, - required=False, - validators=[ - networkapi.wagtailpages.validators.AnchorLinkValidator() - ], - ), - ), - ("email", wagtail.blocks.EmailBlock(required=False)), - ( - "file", - wagtail.documents.blocks.DocumentChooserBlock( - label="File", required=False - ), - ), - ( - "phone", - wagtail.blocks.CharBlock( - label="Phone", max_length=30, required=False - ), - ), - ( - "new_window", - wagtail.blocks.BooleanBlock( - label="Open in new window", required=False - ), - ), - ( - "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.", - ), - ), - ] - ), - ), - ] - ), - ), - ] - ) - ), - ) - ] - ), - ), - ], - blank=True, - use_json_field=True, - ), - ), - ] diff --git a/network-api/networkapi/mozfest/migrations/0049_make_linkto_required.py b/network-api/networkapi/mozfest/migrations/0049_make_linkto_required.py deleted file mode 100644 index 18ad728a166..00000000000 --- a/network-api/networkapi/mozfest/migrations/0049_make_linkto_required.py +++ /dev/null @@ -1,1814 +0,0 @@ -# Generated by Django 4.2.10 on 2024-03-13 14:00 - -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 -import networkapi.wagtailpages.validators - - -class Migration(migrations.Migration): - - dependencies = [ - ("mozfest", "0048_remove_old_url_fields_from_linkblock"), - ] - - 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()), - ( - "link_to", - wagtail.blocks.ChoiceBlock( - choices=[ - ("page", "Page"), - ("external_url", "External URL"), - ("relative_url", "Relative URL"), - ("email", "Email"), - ("anchor", "Anchor"), - ("file", "File"), - ("phone", "Phone"), - ], - label="Link to", - ), - ), - ("page", wagtail.blocks.PageChooserBlock(label="Page", required=False)), - ( - "external_url", - wagtail.blocks.URLBlock( - help_text="Enter a full URL including http:// or https://", - label="External URL", - max_length=300, - required=False, - ), - ), - ( - "relative_url", - wagtail.blocks.CharBlock( - help_text='A path relative to this domain. For example, "/foo/bar"', - label="Relative URL", - max_length=300, - required=False, - validators=[networkapi.wagtailpages.validators.RelativeURLValidator()], - ), - ), - ( - "anchor", - wagtail.blocks.CharBlock( - help_text='An id attribute of an element on the current page. For example, "#section-1"', - label="#", - max_length=300, - required=False, - validators=[networkapi.wagtailpages.validators.AnchorLinkValidator()], - ), - ), - ("email", wagtail.blocks.EmailBlock(required=False)), - ("file", wagtail.documents.blocks.DocumentChooserBlock(label="File", required=False)), - ("phone", wagtail.blocks.CharBlock(label="Phone", max_length=30, required=False)), - ( - "new_window", - wagtail.blocks.BooleanBlock(label="Open in new window", required=False), - ), - ( - "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), - ), - ] - ), - ), - ( - "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"]) - ), - ), - ] - ), - ), - ( - "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( - [ - ( - "heading", - wagtail.blocks.CharBlock(help_text="Heading for the block.", 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 - ), - ), - ("title", wagtail.blocks.CharBlock(help_text="Heading for the card.")), - ( - "highlighted_metadata", - wagtail.blocks.CharBlock( - help_text="Metadata to highlight on the card.", required=False - ), - ), - ( - "metadata", - wagtail.blocks.CharBlock( - help_text="Generic metadata.", required=False - ), - ), - ( - "body", - wagtail.blocks.RichTextBlock( - features=["bold"], - help_text="Body text of the card.", - required=False, - ), - ), - ( - "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, - ), - ), - ( - "cards_per_row", - wagtail.blocks.ChoiceBlock( - choices=[(2, "2"), (3, "3")], - help_text="Number of cards per row. Note: this is a max and fewer might be used if is less space is available.", - required=False, - ), - ), - ] - ), - ), - ( - "profiles", - wagtail.blocks.StructBlock( - [ - ( - "profiles", - wagtail.blocks.ListBlock( - wagtail.blocks.StructBlock( - [ - ( - "profile", - wagtail.snippets.blocks.SnippetChooserBlock( - networkapi.wagtailpages.pagemodels.profiles.Profile - ), - ) - ] - ), - min_num=1, - ), - ) - ] - ), - ), - ( - "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( - [ - ( - "heading", - wagtail.blocks.CharBlock( - help_text="Heading for the block.", 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, - ), - ), - ( - "title", - wagtail.blocks.CharBlock( - help_text="Heading for the card." - ), - ), - ( - "highlighted_metadata", - wagtail.blocks.CharBlock( - help_text="Metadata to highlight on the card.", - required=False, - ), - ), - ( - "metadata", - wagtail.blocks.CharBlock( - help_text="Generic metadata.", - required=False, - ), - ), - ( - "body", - wagtail.blocks.RichTextBlock( - features=["bold"], - help_text="Body text of the card.", - required=False, - ), - ), - ( - "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, - ), - ), - ( - "cards_per_row", - wagtail.blocks.ChoiceBlock( - choices=[(2, "2"), (3, "3")], - help_text="Number of cards per row. Note: this is a max and fewer might be used if is less space is available.", - required=False, - ), - ), - ] - ), - ), - ( - "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()), - ( - "link_to", - wagtail.blocks.ChoiceBlock( - choices=[ - ("page", "Page"), - ("external_url", "External URL"), - ("relative_url", "Relative URL"), - ("email", "Email"), - ("anchor", "Anchor"), - ("file", "File"), - ("phone", "Phone"), - ], - label="Link to", - ), - ), - ( - "page", - wagtail.blocks.PageChooserBlock( - label="Page", required=False - ), - ), - ( - "external_url", - wagtail.blocks.URLBlock( - help_text="Enter a full URL including http:// or https://", - label="External URL", - max_length=300, - required=False, - ), - ), - ( - "relative_url", - wagtail.blocks.CharBlock( - help_text='A path relative to this domain. For example, "/foo/bar"', - label="Relative URL", - max_length=300, - required=False, - validators=[ - networkapi.wagtailpages.validators.RelativeURLValidator() - ], - ), - ), - ( - "anchor", - wagtail.blocks.CharBlock( - help_text='An id attribute of an element on the current page. For example, "#section-1"', - label="#", - max_length=300, - required=False, - validators=[ - networkapi.wagtailpages.validators.AnchorLinkValidator() - ], - ), - ), - ("email", wagtail.blocks.EmailBlock(required=False)), - ( - "file", - wagtail.documents.blocks.DocumentChooserBlock( - label="File", required=False - ), - ), - ( - "phone", - wagtail.blocks.CharBlock( - label="Phone", max_length=30, required=False - ), - ), - ( - "new_window", - wagtail.blocks.BooleanBlock( - label="Open in new window", required=False - ), - ), - ( - "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, - ), - ), - ] - ), - ), - ( - "newsletter_signup", - wagtail.blocks.StructBlock( - [ - ( - "snippet", - wagtail.snippets.blocks.SnippetChooserBlock( - "mozfest.NewsletterSignupWithBackground" - ), - ) - ] - ), - ), - ( - "statistics", - wagtail.blocks.StructBlock( - [ - ( - "statistics", - wagtail.blocks.ListBlock( - wagtail.blocks.StructBlock( - [ - ( - "title", - wagtail.blocks.CharBlock( - help_text="The statistic figure, e.g '1000+' or '10%'" - ), - ), - ( - "description", - wagtail.blocks.CharBlock( - help_text="Context or description for the statistic" - ), - ), - ] - ), - help_text="Please use a minimum of 2 cards.", - max_num=4, - min_num=2, - ), - ) - ] - ), - ), - ( - "carousel_and_text", - wagtail.blocks.StructBlock( - [ - ( - "heading", - wagtail.blocks.CharBlock(help_text="Heading for the block.", required=False), - ), - ("text", wagtail.blocks.RichTextBlock(features=["bold", "italic", "link"])), - ( - "link_url", - wagtail.blocks.URLBlock( - help_text="A CTA URL for a link displayed", required=False - ), - ), - ( - "link_label", - wagtail.blocks.CharBlock(help_text="Label for the CTA link.", required=False), - ), - ( - "carousel_images", - wagtail.blocks.ListBlock( - wagtail.blocks.StructBlock( - [ - ("image", wagtail.images.blocks.ImageChooserBlock()), - ( - "altText", - wagtail.blocks.CharBlock( - help_text="Image description (for screen readers).", - required=True, - ), - ), - ] - ), - max_num=4, - ), - ), - ] - ), - ), - ( - "tickets", - wagtail.blocks.StructBlock( - [ - ( - "heading", - wagtail.blocks.CharBlock(help_text="Heading for the block.", required=False), - ), - ( - "tickets", - wagtail.blocks.ListBlock( - wagtail.snippets.blocks.SnippetChooserBlock("mozfest.Ticket"), max_num=3 - ), - ), - ] - ), - ), - ( - "dark_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), - ), - ] - ), - ), - ( - "cta", - wagtail.blocks.StructBlock( - [ - ("label", wagtail.blocks.CharBlock()), - ( - "link_to", - wagtail.blocks.ChoiceBlock( - choices=[ - ("page", "Page"), - ("external_url", "External URL"), - ("relative_url", "Relative URL"), - ("email", "Email"), - ("anchor", "Anchor"), - ("file", "File"), - ("phone", "Phone"), - ], - label="Link to", - ), - ), - ("page", wagtail.blocks.PageChooserBlock(label="Page", required=False)), - ( - "external_url", - wagtail.blocks.URLBlock( - help_text="Enter a full URL including http:// or https://", - label="External URL", - max_length=300, - required=False, - ), - ), - ( - "relative_url", - wagtail.blocks.CharBlock( - help_text='A path relative to this domain. For example, "/foo/bar"', - label="Relative URL", - max_length=300, - required=False, - validators=[networkapi.wagtailpages.validators.RelativeURLValidator()], - ), - ), - ( - "anchor", - wagtail.blocks.CharBlock( - help_text='An id attribute of an element on the current page. For example, "#section-1"', - label="#", - max_length=300, - required=False, - validators=[networkapi.wagtailpages.validators.AnchorLinkValidator()], - ), - ), - ("email", wagtail.blocks.EmailBlock(required=False)), - ("file", wagtail.documents.blocks.DocumentChooserBlock(label="File", required=False)), - ("phone", wagtail.blocks.CharBlock(label="Phone", max_length=30, required=False)), - ( - "new_window", - wagtail.blocks.BooleanBlock(label="Open in new window", required=False), - ), - ("heading", wagtail.blocks.CharBlock(required=False)), - ("text", wagtail.blocks.CharBlock(required=False)), - ("dark_background", wagtail.blocks.BooleanBlock(required=False)), - ] - ), - ), - ( - "mixed_content", - wagtail.blocks.StructBlock( - [ - ( - "video", - wagtail.blocks.StructBlock( - [ - ( - "url", - wagtail.blocks.URLBlock( - 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( - help_text="Optional caption for the video, displayed next to the play button.", - max_length=25, - required=False, - ), - ), - ( - "title", - wagtail.blocks.CharBlock( - help_text="Optional title for the video.", - max_length=50, - required=False, - ), - ), - ( - "text", - wagtail.blocks.CharBlock( - help_text="Text content to display with the video title.", - required=False, - ), - ), - ( - "thumbnail", - wagtail.images.blocks.ImageChooserBlock( - help_text="The image to show before the video is played." - ), - ), - ] - ), - ), - ( - "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.")), - ( - "highlighted_metadata", - wagtail.blocks.CharBlock( - help_text="Metadata to highlight on the card.", required=False - ), - ), - ( - "metadata", - wagtail.blocks.CharBlock( - help_text="Generic metadata.", required=False - ), - ), - ( - "body", - wagtail.blocks.RichTextBlock( - features=["bold"], - help_text="Body text of the card.", - required=False, - ), - ), - ( - "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, - ), - ), - ] - ), - max_num=4, - min_num=1, - ), - ), - ("link_url", wagtail.blocks.URLBlock(required=False)), - ("link_text", wagtail.blocks.CharBlock(max_length=50, required=False)), - ] - ), - ), - ], - use_json_field=True, - ), - ), - ] diff --git a/network-api/networkapi/wagtailpages/migrations/0130_make_linkto_required.py b/network-api/networkapi/wagtailpages/migrations/0130_make_linkto_required.py deleted file mode 100644 index e8c0da79b0e..00000000000 --- a/network-api/networkapi/wagtailpages/migrations/0130_make_linkto_required.py +++ /dev/null @@ -1,6296 +0,0 @@ -# Generated by Django 4.2.10 on 2024-03-13 14:00 - -import django.core.validators -import wagtail.blocks -import wagtail.blocks.static_block -import wagtail.contrib.table_block.blocks -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.customblocks.articles -import networkapi.wagtailpages.pagemodels.profiles -import networkapi.wagtailpages.validators - - -class Migration(migrations.Migration): - - dependencies = [ - ("wagtailpages", "0129_allow_spacer_on_articlepage_body"), - ] - - operations = [ - migrations.AlterField( - model_name="articlepage", - name="body", - field=wagtail.fields.StreamField( - [ - ( - "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.", - ), - ), - ] - ), - ), - ] - ), - ), - ] - ) - ), - ) - ] - ), - ), - ( - "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", - ), - ), - ] - ), - ), - ( - "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", - ), - ), - ( - "callout", - wagtail.blocks.RichTextBlock( - features=["bold", "italic", "link", "h2", "h3", "h4", "ul", "ol"], - template="wagtailpages/blocks/article_blockquote_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.", - ), - ) - ] - ), - ), - ( - "content", - networkapi.wagtailpages.pagemodels.customblocks.articles.ArticleRichText( - features=[ - "bold", - "italic", - "link", - "h2", - "h3", - "h4", - "h5", - "ol", - "ul", - "hr", - "document-link", - "large", - "image", - "footnotes", - ] - ), - ), - ( - "image", - wagtail.blocks.StructBlock( - [ - ("image", wagtail.images.blocks.ImageChooserBlock()), - ( - "caption", - wagtail.blocks.RichTextBlock( - features=["bold", "italic", "link"], label="Image caption", required=False - ), - ), - ("alt_text", wagtail.blocks.CharBlock(required=False)), - ( - "wide_image", - wagtail.blocks.BooleanBlock( - default=False, - help_text='Checking this will use a wider version of this image, but not full width. For an edge-to-edge image, use the "Wide Image" block.', - required=False, - ), - ), - ] - ), - ), - ( - "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, - ), - ), - ] - ) - ), - ) - ] - ), - ), - ( - "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 - ), - ), - ] - ), - ), - ( - "double_image", - wagtail.blocks.StructBlock( - [ - ("image_1", wagtail.images.blocks.ImageChooserBlock()), - ( - "image_1_caption", - wagtail.blocks.RichTextBlock( - features=["bold", "italic", "link"], label="Image caption", required=False - ), - ), - ("image_2", wagtail.images.blocks.ImageChooserBlock()), - ( - "image_2_caption", - wagtail.blocks.RichTextBlock( - features=["bold", "italic", "link"], label="Image caption", required=False - ), - ), - ] - ), - ), - ( - "full_width_image", - wagtail.blocks.StructBlock( - [ - ("image", wagtail.images.blocks.ImageChooserBlock()), - ( - "image_height", - wagtail.blocks.IntegerBlock( - default=410, - help_text="A custom height for this image. The image will be 1400px wide by this height. Note: This may cause images to look pixelated. If the browser is wider than 1400px the height will scale vertically while the width scales horizontally", - ), - ), - ( - "caption", - wagtail.blocks.RichTextBlock( - features=["bold", "italic", "link"], label="Image caption", required=False - ), - ), - ] - ), - ), - ( - "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()), - ( - "link_to", - wagtail.blocks.ChoiceBlock( - choices=[ - ("page", "Page"), - ("external_url", "External URL"), - ("relative_url", "Relative URL"), - ("email", "Email"), - ("anchor", "Anchor"), - ("file", "File"), - ("phone", "Phone"), - ], - label="Link to", - ), - ), - ("page", wagtail.blocks.PageChooserBlock(label="Page", required=False)), - ( - "external_url", - wagtail.blocks.URLBlock( - help_text="Enter a full URL including http:// or https://", - label="External URL", - max_length=300, - required=False, - ), - ), - ( - "relative_url", - wagtail.blocks.CharBlock( - help_text='A path relative to this domain. For example, "/foo/bar"', - label="Relative URL", - max_length=300, - required=False, - validators=[networkapi.wagtailpages.validators.RelativeURLValidator()], - ), - ), - ( - "anchor", - wagtail.blocks.CharBlock( - help_text='An id attribute of an element on the current page. For example, "#section-1"', - label="#", - max_length=300, - required=False, - validators=[networkapi.wagtailpages.validators.AnchorLinkValidator()], - ), - ), - ("email", wagtail.blocks.EmailBlock(required=False)), - ("file", wagtail.documents.blocks.DocumentChooserBlock(label="File", required=False)), - ("phone", wagtail.blocks.CharBlock(label="Phone", max_length=30, required=False)), - ( - "new_window", - wagtail.blocks.BooleanBlock(label="Open in new window", required=False), - ), - ( - "styling", - wagtail.blocks.ChoiceBlock( - choices=[ - ("btn-primary", "Primary button"), - ("btn-secondary", "Secondary button"), - ] - ), - ), - ] - ), - ), - ( - "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), - ), - ] - ), - ), - ( - "slider", - wagtail.blocks.StructBlock( - [ - ( - "title", - wagtail.blocks.CharBlock(help_text="Heading for the slider.", required=False), - ), - ( - "slides", - wagtail.blocks.StreamBlock( - [ - ( - "slide", - wagtail.blocks.StructBlock( - [ - ( - "title", - wagtail.blocks.CharBlock( - help_text="Heading of the card.", required=False - ), - ), - ( - "image", - wagtail.images.blocks.ImageChooserBlock( - help_text="The image associated with this event." - ), - ), - ( - "caption", - wagtail.blocks.TextBlock( - help_text="Caption for slider image", required=False - ), - ), - ( - "body", - wagtail.blocks.RichTextBlock( - blank=True, - features=["bold", "italic", "link", "large"], - required=False, - ), - ), - ( - "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, - required=False, - ), - ), - ] - ), - ) - ], - help_text="A list of slides.", - ), - ), - ] - ), - ), - ( - "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"), - ] - ), - ) - ] - ), - ), - ( - "table", - wagtail.contrib.table_block.blocks.TableBlock( - template="wagtailpages/blocks/article_table_block.html" - ), - ), - ( - "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.", - ), - ), - ] - ), - ), - ( - "advanced_table", - wagtail.blocks.StructBlock( - [ - ( - "header", - wagtail.blocks.BooleanBlock( - help_text="Display the first row as a header.", required=False - ), - ), - ( - "column", - wagtail.blocks.BooleanBlock( - help_text="Display the first column as a header.", required=False - ), - ), - ( - "caption", - wagtail.blocks.CharBlock( - help_text="A heading that identifies the overall topic of the table, and is useful for screen reader users", - required=False, - ), - ), - ( - "wide", - wagtail.blocks.BooleanBlock( - default=True, - help_text="If this is checked, the table will render wider than the other page body content.", - required=False, - ), - ), - ( - "table", - wagtail.blocks.StreamBlock( - [ - ( - "row", - wagtail.blocks.StreamBlock( - [ - ( - "cell", - wagtail.blocks.StructBlock( - [ - ( - "centered_text", - wagtail.blocks.BooleanBlock(required=False), - ), - ( - "column_width", - wagtail.blocks.IntegerBlock( - default=1, - help_text="Enter the number of extra cell columns you want to merge together. Merging a cell column will expand a cell to the right. To merge two cells together, set the column width to 2. For 3, set 3. Default is 1. Min 1. Max 20.", - validators=[ - django.core.validators.MaxValueValidator( - 20 - ), - django.core.validators.MinValueValidator( - 1 - ), - ], - ), - ), - ( - "content", - wagtail.blocks.RichTextBlock( - features=[ - "bold", - "italic", - "link", - "ul", - "ol", - ] - ), - ), - ] - ), - ) - ] - ), - ) - ] - ), - ), - ] - ), - ), - ], - use_json_field=True, - ), - ), - migrations.AlterField( - model_name="blogpage", - name="body", - field=wagtail.fields.StreamField( - [ - ( - "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.", - ), - ), - ] - ), - ), - ] - ), - ), - ] - ) - ), - ) - ] - ), - ), - ( - "paragraph", - wagtail.blocks.RichTextBlock( - features=[ - "bold", - "italic", - "link", - "h2", - "h3", - "h4", - "h5", - "ol", - "ul", - "hr", - "document-link", - ], - 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.", - ), - ) - ] - ), - ), - ( - "CTA_card", - wagtail.blocks.StructBlock( - [ - ( - "style", - wagtail.blocks.ChoiceBlock( - choices=[("pop", "Pop"), ("outline", "Outline"), ("filled", "Filled")] - ), - ), - ( - "title", - wagtail.blocks.CharBlock( - help_text="Optional title for the card.", max_length=100, required=False - ), - ), - ( - "body", - wagtail.blocks.RichTextBlock( - features=["bold", "italic", "link", "hr", "h4", "h5", "ul", "ol"], - help_text="Body text of the card.", - ), - ), - ( - "image", - wagtail.blocks.ListBlock( - wagtail.blocks.StructBlock( - [ - ("image", wagtail.images.blocks.ImageChooserBlock()), - ( - "altText", - wagtail.blocks.CharBlock( - help_text="Image description (for screen readers).", - required=True, - ), - ), - ] - ), - default=[], - max_num=1, - min_num=0, - ), - ), - ( - "button", - wagtail.blocks.ListBlock( - wagtail.blocks.StructBlock( - [ - ("label", wagtail.blocks.CharBlock()), - ( - "link_to", - wagtail.blocks.ChoiceBlock( - choices=[ - ("page", "Page"), - ("external_url", "External URL"), - ("relative_url", "Relative URL"), - ("email", "Email"), - ("anchor", "Anchor"), - ("file", "File"), - ("phone", "Phone"), - ], - label="Link to", - ), - ), - ( - "page", - wagtail.blocks.PageChooserBlock(label="Page", required=False), - ), - ( - "external_url", - wagtail.blocks.URLBlock( - help_text="Enter a full URL including http:// or https://", - label="External URL", - max_length=300, - required=False, - ), - ), - ( - "relative_url", - wagtail.blocks.CharBlock( - help_text='A path relative to this domain. For example, "/foo/bar"', - label="Relative URL", - max_length=300, - required=False, - validators=[ - networkapi.wagtailpages.validators.RelativeURLValidator() - ], - ), - ), - ( - "anchor", - wagtail.blocks.CharBlock( - help_text='An id attribute of an element on the current page. For example, "#section-1"', - label="#", - max_length=300, - required=False, - validators=[ - networkapi.wagtailpages.validators.AnchorLinkValidator() - ], - ), - ), - ("email", wagtail.blocks.EmailBlock(required=False)), - ( - "file", - wagtail.documents.blocks.DocumentChooserBlock( - label="File", required=False - ), - ), - ( - "phone", - wagtail.blocks.CharBlock( - label="Phone", max_length=30, required=False - ), - ), - ( - "new_window", - wagtail.blocks.BooleanBlock( - label="Open in new window", required=False - ), - ), - ( - "styling", - wagtail.blocks.ChoiceBlock( - choices=[ - ("btn-primary", "Primary button"), - ("btn-secondary", "Secondary button"), - ] - ), - ), - ] - ), - default=[], - max_num=1, - min_num=0, - ), - ), - ] - ), - ), - ( - "CTA_card_with_text", - wagtail.blocks.StructBlock( - [ - ( - "alignment", - wagtail.blocks.ChoiceBlock( - choices=[("right", "Right"), ("left", "Left")], - help_text="For full-width cards, please use a regular Blog CTA Card block with a separate paragraph.", - ), - ), - ( - "card", - wagtail.blocks.StructBlock( - [ - ( - "style", - wagtail.blocks.ChoiceBlock( - choices=[ - ("pop", "Pop"), - ("outline", "Outline"), - ("filled", "Filled"), - ] - ), - ), - ( - "title", - wagtail.blocks.CharBlock( - help_text="Optional title for the card.", - max_length=100, - required=False, - ), - ), - ( - "body", - wagtail.blocks.RichTextBlock( - features=["bold", "italic", "link", "hr", "h4", "h5", "ul", "ol"], - help_text="Body text of the card.", - ), - ), - ( - "image", - wagtail.blocks.ListBlock( - wagtail.blocks.StructBlock( - [ - ("image", wagtail.images.blocks.ImageChooserBlock()), - ( - "altText", - wagtail.blocks.CharBlock( - help_text="Image description (for screen readers).", - required=True, - ), - ), - ] - ), - default=[], - max_num=1, - min_num=0, - ), - ), - ( - "button", - wagtail.blocks.ListBlock( - wagtail.blocks.StructBlock( - [ - ("label", wagtail.blocks.CharBlock()), - ( - "link_to", - wagtail.blocks.ChoiceBlock( - choices=[ - ("page", "Page"), - ("external_url", "External URL"), - ("relative_url", "Relative URL"), - ("email", "Email"), - ("anchor", "Anchor"), - ("file", "File"), - ("phone", "Phone"), - ], - label="Link to", - ), - ), - ( - "page", - wagtail.blocks.PageChooserBlock( - label="Page", required=False - ), - ), - ( - "external_url", - wagtail.blocks.URLBlock( - help_text="Enter a full URL including http:// or https://", - label="External URL", - max_length=300, - required=False, - ), - ), - ( - "relative_url", - wagtail.blocks.CharBlock( - help_text='A path relative to this domain. For example, "/foo/bar"', - label="Relative URL", - max_length=300, - required=False, - validators=[ - networkapi.wagtailpages.validators.RelativeURLValidator() - ], - ), - ), - ( - "anchor", - wagtail.blocks.CharBlock( - help_text='An id attribute of an element on the current page. For example, "#section-1"', - label="#", - max_length=300, - required=False, - validators=[ - networkapi.wagtailpages.validators.AnchorLinkValidator() - ], - ), - ), - ("email", wagtail.blocks.EmailBlock(required=False)), - ( - "file", - wagtail.documents.blocks.DocumentChooserBlock( - label="File", required=False - ), - ), - ( - "phone", - wagtail.blocks.CharBlock( - label="Phone", max_length=30, required=False - ), - ), - ( - "new_window", - wagtail.blocks.BooleanBlock( - label="Open in new window", required=False - ), - ), - ( - "styling", - wagtail.blocks.ChoiceBlock( - choices=[ - ("btn-primary", "Primary button"), - ("btn-secondary", "Secondary button"), - ] - ), - ), - ] - ), - default=[], - max_num=1, - min_num=0, - ), - ), - ], - required=True, - template="wagtailpages/blocks/blog_cta_card_block_regular.html", - ), - ), - ( - "paragraph", - wagtail.blocks.RichTextBlock( - features=[ - "bold", - "italic", - "link", - "h2", - "h3", - "h4", - "h5", - "ol", - "ul", - "hr", - "document-link", - ], - help_text="Text to be displayed next to the card.", - template="wagtailpages/blocks/rich_text_block.html", - ), - ), - ] - ), - ), - ( - "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, - ), - ), - ] - ) - ), - ) - ] - ), - ), - ( - "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, - ), - ), - ] - ), - ), - ( - "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)", - ), - ), - ] - ), - ), - ( - "audio", - wagtail.blocks.StructBlock( - [ - ("audio", wagtailmedia.blocks.AudioChooserBlock()), - ("caption", wagtail.blocks.CharBlock(required=False)), - ] - ), - ), - ( - "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"])), - ] - ), - ), - ( - "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.", - ), - ), - ] - ), - ), - ( - "linkbutton", - wagtail.blocks.StructBlock( - [ - ("label", wagtail.blocks.CharBlock()), - ( - "link_to", - wagtail.blocks.ChoiceBlock( - choices=[ - ("page", "Page"), - ("external_url", "External URL"), - ("relative_url", "Relative URL"), - ("email", "Email"), - ("anchor", "Anchor"), - ("file", "File"), - ("phone", "Phone"), - ], - label="Link to", - ), - ), - ("page", wagtail.blocks.PageChooserBlock(label="Page", required=False)), - ( - "external_url", - wagtail.blocks.URLBlock( - help_text="Enter a full URL including http:// or https://", - label="External URL", - max_length=300, - required=False, - ), - ), - ( - "relative_url", - wagtail.blocks.CharBlock( - help_text='A path relative to this domain. For example, "/foo/bar"', - label="Relative URL", - max_length=300, - required=False, - validators=[networkapi.wagtailpages.validators.RelativeURLValidator()], - ), - ), - ( - "anchor", - wagtail.blocks.CharBlock( - help_text='An id attribute of an element on the current page. For example, "#section-1"', - label="#", - max_length=300, - required=False, - validators=[networkapi.wagtailpages.validators.AnchorLinkValidator()], - ), - ), - ("email", wagtail.blocks.EmailBlock(required=False)), - ("file", wagtail.documents.blocks.DocumentChooserBlock(label="File", required=False)), - ("phone", wagtail.blocks.CharBlock(label="Phone", max_length=30, required=False)), - ( - "new_window", - wagtail.blocks.BooleanBlock(label="Open in new window", required=False), - ), - ( - "styling", - wagtail.blocks.ChoiceBlock( - choices=[ - ("btn-primary", "Primary button"), - ("btn-secondary", "Secondary button"), - ] - ), - ), - ] - ), - ), - ( - "looping_video", - wagtail.blocks.StructBlock( - [ - ( - "video_url", - wagtail.blocks.CharBlock( - help_text='Log into Vimeo using 1Password and upload the desired video. Then select the video and click "Advanced", "Distribution", and "Video File Links". Copy and paste the link here.', - label="Embed URL", - ), - ) - ] - ), - ), - ( - "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, - ), - ), - ] - ), - ), - ( - "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), - ), - ] - ), - ), - ( - "slider", - wagtail.blocks.StructBlock( - [ - ( - "title", - wagtail.blocks.CharBlock(help_text="Heading for the slider.", required=False), - ), - ( - "slides", - wagtail.blocks.StreamBlock( - [ - ( - "slide", - wagtail.blocks.StructBlock( - [ - ( - "title", - wagtail.blocks.CharBlock( - help_text="Heading of the card.", required=False - ), - ), - ( - "image", - wagtail.images.blocks.ImageChooserBlock( - help_text="The image associated with this event." - ), - ), - ( - "caption", - wagtail.blocks.TextBlock( - help_text="Caption for slider image", required=False - ), - ), - ( - "body", - wagtail.blocks.RichTextBlock( - blank=True, - features=["bold", "italic", "link", "large"], - required=False, - ), - ), - ( - "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, - required=False, - ), - ), - ] - ), - ) - ], - help_text="A list of slides.", - ), - ), - ] - ), - ), - ( - "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"), - ] - ), - ) - ] - ), - ), - ( - "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", - ), - ), - ] - ), - ), - ( - "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", - ), - ), - ( - "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, - ), - ), - ] - ), - ), - ( - "newsletter_signup", - wagtail.blocks.StructBlock( - [("signup", wagtail.snippets.blocks.SnippetChooserBlock("wagtailpages.BlogSignup"))] - ), - ), - ], - use_json_field=True, - ), - ), - migrations.AlterField( - model_name="buyersguidearticlepage", - name="body", - field=wagtail.fields.StreamField( - [ - ( - "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.", - ), - ), - ] - ), - ), - ] - ), - ), - ] - ) - ), - ) - ] - ), - ), - ( - "paragraph", - wagtail.blocks.RichTextBlock( - features=[ - "bold", - "italic", - "link", - "h2", - "h3", - "h4", - "h5", - "ol", - "ul", - "hr", - "document-link", - ], - 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_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, - ), - ), - ] - ) - ), - ) - ] - ), - ), - ( - "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, - ), - ), - ] - ), - ), - ( - "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)", - ), - ), - ] - ), - ), - ( - "audio", - wagtail.blocks.StructBlock( - [ - ("audio", wagtailmedia.blocks.AudioChooserBlock()), - ("caption", wagtail.blocks.CharBlock(required=False)), - ] - ), - ), - ( - "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"])), - ] - ), - ), - ( - "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.", - ), - ), - ] - ), - ), - ( - "linkbutton", - wagtail.blocks.StructBlock( - [ - ("label", wagtail.blocks.CharBlock()), - ( - "link_to", - wagtail.blocks.ChoiceBlock( - choices=[ - ("page", "Page"), - ("external_url", "External URL"), - ("relative_url", "Relative URL"), - ("email", "Email"), - ("anchor", "Anchor"), - ("file", "File"), - ("phone", "Phone"), - ], - label="Link to", - ), - ), - ("page", wagtail.blocks.PageChooserBlock(label="Page", required=False)), - ( - "external_url", - wagtail.blocks.URLBlock( - help_text="Enter a full URL including http:// or https://", - label="External URL", - max_length=300, - required=False, - ), - ), - ( - "relative_url", - wagtail.blocks.CharBlock( - help_text='A path relative to this domain. For example, "/foo/bar"', - label="Relative URL", - max_length=300, - required=False, - validators=[networkapi.wagtailpages.validators.RelativeURLValidator()], - ), - ), - ( - "anchor", - wagtail.blocks.CharBlock( - help_text='An id attribute of an element on the current page. For example, "#section-1"', - label="#", - max_length=300, - required=False, - validators=[networkapi.wagtailpages.validators.AnchorLinkValidator()], - ), - ), - ("email", wagtail.blocks.EmailBlock(required=False)), - ("file", wagtail.documents.blocks.DocumentChooserBlock(label="File", required=False)), - ("phone", wagtail.blocks.CharBlock(label="Phone", max_length=30, required=False)), - ( - "new_window", - wagtail.blocks.BooleanBlock(label="Open in new window", required=False), - ), - ( - "styling", - wagtail.blocks.ChoiceBlock( - choices=[ - ("btn-primary", "Primary button"), - ("btn-secondary", "Secondary button"), - ] - ), - ), - ] - ), - ), - ( - "looping_video", - wagtail.blocks.StructBlock( - [ - ( - "video_url", - wagtail.blocks.CharBlock( - help_text='Log into Vimeo using 1Password and upload the desired video. Then select the video and click "Advanced", "Distribution", and "Video File Links". Copy and paste the link here.', - label="Embed URL", - ), - ) - ] - ), - ), - ( - "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, - ), - ), - ] - ), - ), - ( - "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), - ), - ] - ), - ), - ( - "slider", - wagtail.blocks.StructBlock( - [ - ( - "title", - wagtail.blocks.CharBlock(help_text="Heading for the slider.", required=False), - ), - ( - "slides", - wagtail.blocks.StreamBlock( - [ - ( - "slide", - wagtail.blocks.StructBlock( - [ - ( - "title", - wagtail.blocks.CharBlock( - help_text="Heading of the card.", required=False - ), - ), - ( - "image", - wagtail.images.blocks.ImageChooserBlock( - help_text="The image associated with this event." - ), - ), - ( - "caption", - wagtail.blocks.TextBlock( - help_text="Caption for slider image", required=False - ), - ), - ( - "body", - wagtail.blocks.RichTextBlock( - blank=True, - features=["bold", "italic", "link", "large"], - required=False, - ), - ), - ( - "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, - required=False, - ), - ), - ] - ), - ) - ], - help_text="A list of slides.", - ), - ), - ] - ), - ), - ( - "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"), - ] - ), - ) - ] - ), - ), - ( - "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", - ), - ), - ] - ), - ), - ( - "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", - ), - ), - ( - "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, - ), - ), - ] - ), - ), - ], - null=True, - use_json_field=True, - ), - ), - migrations.AlterField( - model_name="buyersguidecampaignpage", - name="body", - field=wagtail.fields.StreamField( - [ - ( - "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.", - ), - ), - ] - ), - ), - ] - ), - ), - ] - ) - ), - ) - ] - ), - ), - ( - "paragraph", - wagtail.blocks.RichTextBlock( - features=[ - "bold", - "italic", - "link", - "h2", - "h3", - "h4", - "h5", - "ol", - "ul", - "hr", - "document-link", - ], - 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_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, - ), - ), - ] - ) - ), - ) - ] - ), - ), - ( - "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, - ), - ), - ] - ), - ), - ( - "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)", - ), - ), - ] - ), - ), - ( - "audio", - wagtail.blocks.StructBlock( - [ - ("audio", wagtailmedia.blocks.AudioChooserBlock()), - ("caption", wagtail.blocks.CharBlock(required=False)), - ] - ), - ), - ( - "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"])), - ] - ), - ), - ( - "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.", - ), - ), - ] - ), - ), - ( - "linkbutton", - wagtail.blocks.StructBlock( - [ - ("label", wagtail.blocks.CharBlock()), - ( - "link_to", - wagtail.blocks.ChoiceBlock( - choices=[ - ("page", "Page"), - ("external_url", "External URL"), - ("relative_url", "Relative URL"), - ("email", "Email"), - ("anchor", "Anchor"), - ("file", "File"), - ("phone", "Phone"), - ], - label="Link to", - ), - ), - ("page", wagtail.blocks.PageChooserBlock(label="Page", required=False)), - ( - "external_url", - wagtail.blocks.URLBlock( - help_text="Enter a full URL including http:// or https://", - label="External URL", - max_length=300, - required=False, - ), - ), - ( - "relative_url", - wagtail.blocks.CharBlock( - help_text='A path relative to this domain. For example, "/foo/bar"', - label="Relative URL", - max_length=300, - required=False, - validators=[networkapi.wagtailpages.validators.RelativeURLValidator()], - ), - ), - ( - "anchor", - wagtail.blocks.CharBlock( - help_text='An id attribute of an element on the current page. For example, "#section-1"', - label="#", - max_length=300, - required=False, - validators=[networkapi.wagtailpages.validators.AnchorLinkValidator()], - ), - ), - ("email", wagtail.blocks.EmailBlock(required=False)), - ("file", wagtail.documents.blocks.DocumentChooserBlock(label="File", required=False)), - ("phone", wagtail.blocks.CharBlock(label="Phone", max_length=30, required=False)), - ( - "new_window", - wagtail.blocks.BooleanBlock(label="Open in new window", required=False), - ), - ( - "styling", - wagtail.blocks.ChoiceBlock( - choices=[ - ("btn-primary", "Primary button"), - ("btn-secondary", "Secondary button"), - ] - ), - ), - ] - ), - ), - ( - "looping_video", - wagtail.blocks.StructBlock( - [ - ( - "video_url", - wagtail.blocks.CharBlock( - help_text='Log into Vimeo using 1Password and upload the desired video. Then select the video and click "Advanced", "Distribution", and "Video File Links". Copy and paste the link here.', - label="Embed URL", - ), - ) - ] - ), - ), - ( - "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, - ), - ), - ] - ), - ), - ( - "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), - ), - ] - ), - ), - ( - "slider", - wagtail.blocks.StructBlock( - [ - ( - "title", - wagtail.blocks.CharBlock(help_text="Heading for the slider.", required=False), - ), - ( - "slides", - wagtail.blocks.StreamBlock( - [ - ( - "slide", - wagtail.blocks.StructBlock( - [ - ( - "title", - wagtail.blocks.CharBlock( - help_text="Heading of the card.", required=False - ), - ), - ( - "image", - wagtail.images.blocks.ImageChooserBlock( - help_text="The image associated with this event." - ), - ), - ( - "caption", - wagtail.blocks.TextBlock( - help_text="Caption for slider image", required=False - ), - ), - ( - "body", - wagtail.blocks.RichTextBlock( - blank=True, - features=["bold", "italic", "link", "large"], - required=False, - ), - ), - ( - "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, - required=False, - ), - ), - ] - ), - ) - ], - help_text="A list of slides.", - ), - ), - ] - ), - ), - ( - "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"), - ] - ), - ) - ] - ), - ), - ( - "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", - ), - ), - ] - ), - ), - ( - "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", - ), - ), - ( - "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, - ), - ), - ] - ), - ), - ], - use_json_field=True, - ), - ), - migrations.AlterField( - model_name="modularpage", - 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()), - ( - "link_to", - wagtail.blocks.ChoiceBlock( - choices=[ - ("page", "Page"), - ("external_url", "External URL"), - ("relative_url", "Relative URL"), - ("email", "Email"), - ("anchor", "Anchor"), - ("file", "File"), - ("phone", "Phone"), - ], - label="Link to", - ), - ), - ("page", wagtail.blocks.PageChooserBlock(label="Page", required=False)), - ( - "external_url", - wagtail.blocks.URLBlock( - help_text="Enter a full URL including http:// or https://", - label="External URL", - max_length=300, - required=False, - ), - ), - ( - "relative_url", - wagtail.blocks.CharBlock( - help_text='A path relative to this domain. For example, "/foo/bar"', - label="Relative URL", - max_length=300, - required=False, - validators=[networkapi.wagtailpages.validators.RelativeURLValidator()], - ), - ), - ( - "anchor", - wagtail.blocks.CharBlock( - help_text='An id attribute of an element on the current page. For example, "#section-1"', - label="#", - max_length=300, - required=False, - validators=[networkapi.wagtailpages.validators.AnchorLinkValidator()], - ), - ), - ("email", wagtail.blocks.EmailBlock(required=False)), - ("file", wagtail.documents.blocks.DocumentChooserBlock(label="File", required=False)), - ("phone", wagtail.blocks.CharBlock(label="Phone", max_length=30, required=False)), - ( - "new_window", - wagtail.blocks.BooleanBlock(label="Open in new window", required=False), - ), - ( - "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( - [ - ( - "heading", - wagtail.blocks.CharBlock(help_text="Heading for the block.", 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 - ), - ), - ("title", wagtail.blocks.CharBlock(help_text="Heading for the card.")), - ( - "highlighted_metadata", - wagtail.blocks.CharBlock( - help_text="Metadata to highlight on the card.", required=False - ), - ), - ( - "metadata", - wagtail.blocks.CharBlock( - help_text="Generic metadata.", required=False - ), - ), - ( - "body", - wagtail.blocks.RichTextBlock( - features=["bold"], - help_text="Body text of the card.", - required=False, - ), - ), - ( - "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, - ), - ), - ( - "cards_per_row", - wagtail.blocks.ChoiceBlock( - choices=[(2, "2"), (3, "3")], - help_text="Number of cards per row. Note: this is a max and fewer might be used if is less space is available.", - required=False, - ), - ), - ] - ), - ), - ( - "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( - [ - ( - "heading", - wagtail.blocks.CharBlock( - help_text="Heading for the block.", 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, - ), - ), - ( - "title", - wagtail.blocks.CharBlock( - help_text="Heading for the card." - ), - ), - ( - "highlighted_metadata", - wagtail.blocks.CharBlock( - help_text="Metadata to highlight on the card.", - required=False, - ), - ), - ( - "metadata", - wagtail.blocks.CharBlock( - help_text="Generic metadata.", - required=False, - ), - ), - ( - "body", - wagtail.blocks.RichTextBlock( - features=["bold"], - help_text="Body text of the card.", - required=False, - ), - ), - ( - "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, - ), - ), - ( - "cards_per_row", - wagtail.blocks.ChoiceBlock( - choices=[(2, "2"), (3, "3")], - help_text="Number of cards per row. Note: this is a max and fewer might be used if is less space is available.", - required=False, - ), - ), - ] - ), - ), - ( - "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()), - ( - "link_to", - wagtail.blocks.ChoiceBlock( - choices=[ - ("page", "Page"), - ("external_url", "External URL"), - ("relative_url", "Relative URL"), - ("email", "Email"), - ("anchor", "Anchor"), - ("file", "File"), - ("phone", "Phone"), - ], - label="Link to", - ), - ), - ( - "page", - wagtail.blocks.PageChooserBlock( - label="Page", required=False - ), - ), - ( - "external_url", - wagtail.blocks.URLBlock( - help_text="Enter a full URL including http:// or https://", - label="External URL", - max_length=300, - required=False, - ), - ), - ( - "relative_url", - wagtail.blocks.CharBlock( - help_text='A path relative to this domain. For example, "/foo/bar"', - label="Relative URL", - max_length=300, - required=False, - validators=[ - networkapi.wagtailpages.validators.RelativeURLValidator() - ], - ), - ), - ( - "anchor", - wagtail.blocks.CharBlock( - help_text='An id attribute of an element on the current page. For example, "#section-1"', - label="#", - max_length=300, - required=False, - validators=[ - networkapi.wagtailpages.validators.AnchorLinkValidator() - ], - ), - ), - ("email", wagtail.blocks.EmailBlock(required=False)), - ( - "file", - wagtail.documents.blocks.DocumentChooserBlock( - label="File", required=False - ), - ), - ( - "phone", - wagtail.blocks.CharBlock( - label="Phone", max_length=30, required=False - ), - ), - ( - "new_window", - wagtail.blocks.BooleanBlock( - label="Open in new window", required=False - ), - ), - ( - "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.", - ), - ), - ] - ), - ), - ] - ), - ), - ] - ) - ), - ) - ] - ), - ), - ], - use_json_field=True, - ), - ), - migrations.AlterField( - model_name="primarypage", - 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()), - ( - "link_to", - wagtail.blocks.ChoiceBlock( - choices=[ - ("page", "Page"), - ("external_url", "External URL"), - ("relative_url", "Relative URL"), - ("email", "Email"), - ("anchor", "Anchor"), - ("file", "File"), - ("phone", "Phone"), - ], - label="Link to", - ), - ), - ("page", wagtail.blocks.PageChooserBlock(label="Page", required=False)), - ( - "external_url", - wagtail.blocks.URLBlock( - help_text="Enter a full URL including http:// or https://", - label="External URL", - max_length=300, - required=False, - ), - ), - ( - "relative_url", - wagtail.blocks.CharBlock( - help_text='A path relative to this domain. For example, "/foo/bar"', - label="Relative URL", - max_length=300, - required=False, - validators=[networkapi.wagtailpages.validators.RelativeURLValidator()], - ), - ), - ( - "anchor", - wagtail.blocks.CharBlock( - help_text='An id attribute of an element on the current page. For example, "#section-1"', - label="#", - max_length=300, - required=False, - validators=[networkapi.wagtailpages.validators.AnchorLinkValidator()], - ), - ), - ("email", wagtail.blocks.EmailBlock(required=False)), - ("file", wagtail.documents.blocks.DocumentChooserBlock(label="File", required=False)), - ("phone", wagtail.blocks.CharBlock(label="Phone", max_length=30, required=False)), - ( - "new_window", - wagtail.blocks.BooleanBlock(label="Open in new window", required=False), - ), - ( - "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( - [ - ( - "heading", - wagtail.blocks.CharBlock(help_text="Heading for the block.", 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 - ), - ), - ("title", wagtail.blocks.CharBlock(help_text="Heading for the card.")), - ( - "highlighted_metadata", - wagtail.blocks.CharBlock( - help_text="Metadata to highlight on the card.", required=False - ), - ), - ( - "metadata", - wagtail.blocks.CharBlock( - help_text="Generic metadata.", required=False - ), - ), - ( - "body", - wagtail.blocks.RichTextBlock( - features=["bold"], - help_text="Body text of the card.", - required=False, - ), - ), - ( - "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, - ), - ), - ( - "cards_per_row", - wagtail.blocks.ChoiceBlock( - choices=[(2, "2"), (3, "3")], - help_text="Number of cards per row. Note: this is a max and fewer might be used if is less space is available.", - required=False, - ), - ), - ] - ), - ), - ( - "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( - [ - ( - "heading", - wagtail.blocks.CharBlock( - help_text="Heading for the block.", 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, - ), - ), - ( - "title", - wagtail.blocks.CharBlock( - help_text="Heading for the card." - ), - ), - ( - "highlighted_metadata", - wagtail.blocks.CharBlock( - help_text="Metadata to highlight on the card.", - required=False, - ), - ), - ( - "metadata", - wagtail.blocks.CharBlock( - help_text="Generic metadata.", - required=False, - ), - ), - ( - "body", - wagtail.blocks.RichTextBlock( - features=["bold"], - help_text="Body text of the card.", - required=False, - ), - ), - ( - "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, - ), - ), - ( - "cards_per_row", - wagtail.blocks.ChoiceBlock( - choices=[(2, "2"), (3, "3")], - help_text="Number of cards per row. Note: this is a max and fewer might be used if is less space is available.", - required=False, - ), - ), - ] - ), - ), - ( - "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()), - ( - "link_to", - wagtail.blocks.ChoiceBlock( - choices=[ - ("page", "Page"), - ("external_url", "External URL"), - ("relative_url", "Relative URL"), - ("email", "Email"), - ("anchor", "Anchor"), - ("file", "File"), - ("phone", "Phone"), - ], - label="Link to", - ), - ), - ( - "page", - wagtail.blocks.PageChooserBlock( - label="Page", required=False - ), - ), - ( - "external_url", - wagtail.blocks.URLBlock( - help_text="Enter a full URL including http:// or https://", - label="External URL", - max_length=300, - required=False, - ), - ), - ( - "relative_url", - wagtail.blocks.CharBlock( - help_text='A path relative to this domain. For example, "/foo/bar"', - label="Relative URL", - max_length=300, - required=False, - validators=[ - networkapi.wagtailpages.validators.RelativeURLValidator() - ], - ), - ), - ( - "anchor", - wagtail.blocks.CharBlock( - help_text='An id attribute of an element on the current page. For example, "#section-1"', - label="#", - max_length=300, - required=False, - validators=[ - networkapi.wagtailpages.validators.AnchorLinkValidator() - ], - ), - ), - ("email", wagtail.blocks.EmailBlock(required=False)), - ( - "file", - wagtail.documents.blocks.DocumentChooserBlock( - label="File", required=False - ), - ), - ( - "phone", - wagtail.blocks.CharBlock( - label="Phone", max_length=30, required=False - ), - ), - ( - "new_window", - wagtail.blocks.BooleanBlock( - label="Open in new window", required=False - ), - ), - ( - "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.", - ), - ), - ] - ), - ), - ] - ), - ), - ] - ) - ), - ) - ] - ), - ), - ], - use_json_field=True, - ), - ), - migrations.AlterField( - model_name="rcclandingpage", - name="aside_cta", - field=wagtail.fields.StreamField( - [ - ( - "cta", - wagtail.blocks.StructBlock( - [ - ("title", wagtail.blocks.CharBlock(help_text="Heading for the card.")), - ("body", wagtail.blocks.TextBlock(help_text="Body text of the card.")), - ( - "button", - wagtail.blocks.StructBlock( - [ - ("label", wagtail.blocks.CharBlock()), - ( - "link_to", - wagtail.blocks.ChoiceBlock( - choices=[ - ("page", "Page"), - ("external_url", "External URL"), - ("relative_url", "Relative URL"), - ("email", "Email"), - ("anchor", "Anchor"), - ("file", "File"), - ("phone", "Phone"), - ], - label="Link to", - ), - ), - ("page", wagtail.blocks.PageChooserBlock(label="Page", required=False)), - ( - "external_url", - wagtail.blocks.URLBlock( - help_text="Enter a full URL including http:// or https://", - label="External URL", - max_length=300, - required=False, - ), - ), - ( - "relative_url", - wagtail.blocks.CharBlock( - help_text='A path relative to this domain. For example, "/foo/bar"', - label="Relative URL", - max_length=300, - required=False, - validators=[ - networkapi.wagtailpages.validators.RelativeURLValidator() - ], - ), - ), - ( - "anchor", - wagtail.blocks.CharBlock( - help_text='An id attribute of an element on the current page. For example, "#section-1"', - label="#", - max_length=300, - required=False, - validators=[ - networkapi.wagtailpages.validators.AnchorLinkValidator() - ], - ), - ), - ("email", wagtail.blocks.EmailBlock(required=False)), - ( - "file", - wagtail.documents.blocks.DocumentChooserBlock( - label="File", required=False - ), - ), - ( - "phone", - wagtail.blocks.CharBlock(label="Phone", max_length=30, required=False), - ), - ( - "new_window", - wagtail.blocks.BooleanBlock( - label="Open in new window", required=False - ), - ), - ( - "styling", - wagtail.blocks.ChoiceBlock( - choices=[ - ("btn-primary", "Primary button"), - ("btn-secondary", "Secondary button"), - ] - ), - ), - ] - ), - ), - ] - ), - ) - ], - blank=True, - null=True, - use_json_field=True, - ), - ), - migrations.AlterField( - model_name="researchlandingpage", - name="aside_cta", - field=wagtail.fields.StreamField( - [ - ( - "cta", - wagtail.blocks.StructBlock( - [ - ("title", wagtail.blocks.CharBlock(help_text="Heading for the card.")), - ("body", wagtail.blocks.TextBlock(help_text="Body text of the card.")), - ( - "button", - wagtail.blocks.StructBlock( - [ - ("label", wagtail.blocks.CharBlock()), - ( - "link_to", - wagtail.blocks.ChoiceBlock( - choices=[ - ("page", "Page"), - ("external_url", "External URL"), - ("relative_url", "Relative URL"), - ("email", "Email"), - ("anchor", "Anchor"), - ("file", "File"), - ("phone", "Phone"), - ], - label="Link to", - ), - ), - ("page", wagtail.blocks.PageChooserBlock(label="Page", required=False)), - ( - "external_url", - wagtail.blocks.URLBlock( - help_text="Enter a full URL including http:// or https://", - label="External URL", - max_length=300, - required=False, - ), - ), - ( - "relative_url", - wagtail.blocks.CharBlock( - help_text='A path relative to this domain. For example, "/foo/bar"', - label="Relative URL", - max_length=300, - required=False, - validators=[ - networkapi.wagtailpages.validators.RelativeURLValidator() - ], - ), - ), - ( - "anchor", - wagtail.blocks.CharBlock( - help_text='An id attribute of an element on the current page. For example, "#section-1"', - label="#", - max_length=300, - required=False, - validators=[ - networkapi.wagtailpages.validators.AnchorLinkValidator() - ], - ), - ), - ("email", wagtail.blocks.EmailBlock(required=False)), - ( - "file", - wagtail.documents.blocks.DocumentChooserBlock( - label="File", required=False - ), - ), - ( - "phone", - wagtail.blocks.CharBlock(label="Phone", max_length=30, required=False), - ), - ( - "new_window", - wagtail.blocks.BooleanBlock( - label="Open in new window", required=False - ), - ), - ( - "styling", - wagtail.blocks.ChoiceBlock( - choices=[ - ("btn-primary", "Primary button"), - ("btn-secondary", "Secondary button"), - ] - ), - ), - ] - ), - ), - ] - ), - ) - ], - blank=True, - null=True, - use_json_field=True, - ), - ), - ] From 6fefc37b51513df89fc0465343fd593dffc9a4e4 Mon Sep 17 00:00:00 2001 From: Jhonatan Lopes Date: Thu, 28 Mar 2024 15:18:17 -0300 Subject: [PATCH 05/18] [TP-268][Main Nav] Nav dropdown (#12114) * Better NavColumn block with default values * Add NavOverview block * Setup rich text features on overview description * Define an ExtendedStructBlockFactory * Use ExtendedStructBlockFactory * Add NavDropdown block * Lint * Fix types --- network-api/networkapi/nav/blocks.py | 128 +++++++++++++++- network-api/networkapi/nav/factories.py | 80 +++++++--- .../networkapi/nav/tests/test_blocks.py | 143 ++++++++++++++++-- .../wagtailcustomization/factories/blocks.py | 12 ++ 4 files changed, 327 insertions(+), 36 deletions(-) create mode 100644 network-api/networkapi/wagtailcustomization/factories/blocks.py diff --git a/network-api/networkapi/nav/blocks.py b/network-api/networkapi/nav/blocks.py index d5cc0573a69..549c1a39cdb 100644 --- a/network-api/networkapi/nav/blocks.py +++ b/network-api/networkapi/nav/blocks.py @@ -1,5 +1,7 @@ from collections import OrderedDict +from django.core.exceptions import ValidationError +from django.forms.utils import ErrorList from wagtail import blocks from wagtail.telepath import register @@ -57,12 +59,134 @@ class Meta: register(BaseLinkBlockAdapter(), NavButton) +class NavColumnValue(blocks.StructValue): + @property + def has_button(self) -> bool: + return bool(self.get("button")) + + @property + def button(self) -> NavButton | None: + button = self.get("button") + if button: + return button[0] + return None + + class NavColumn(blocks.StructBlock): title = blocks.CharBlock(max_length=100) - links = blocks.ListBlock(NavItem, min_num=1, max_num=4) - button = blocks.ListBlock(NavButton, required=False, min_num=0, max_num=1) + # Empty default so that it starts collapsed: + nav_items = blocks.ListBlock(NavItem, min_num=1, max_num=4, label="Items", default=[]) + button = blocks.ListBlock( + NavButton, + required=False, + min_num=0, + max_num=1, + default=[], + label="Column Button", + help_text="Adds a CTA button to the bottom of the nav column.", + ) class Meta: label = "Navigation Column" icon = "list-ul" template = "nav/blocks/nav_column_block.html" + value_class = NavColumnValue + + +class NavOverview(blocks.StructBlock): + title = blocks.CharBlock(max_length=100) + description = blocks.RichTextBlock(features=["bold", "italic"], max_length=200) + + class Meta: + label = "Navigation Overview" + icon = "pilcrow" + template = "nav/blocks/overview_block.html" + + +class NavDropdownValue(blocks.StructValue): + @property + def has_overview(self) -> bool: + return bool(self.get("overview")) + + @property + def overview(self) -> NavOverview | None: + overview = self.get("overview") + if overview: + return overview[0] + return None + + @property + def has_button(self) -> bool: + return bool(self.get("button")) + + @property + def button(self) -> NavButton | None: + button = self.get("button") + if button: + return button[0] + return None + + +class NavDropdown(blocks.StructBlock): + title = blocks.CharBlock(max_length=100, help_text="How the dropdown menu will be labelled in the nav bar") + overview = blocks.ListBlock( + NavOverview(label="Overview"), + min_num=0, + max_num=1, + label="Overview", + help_text="If added, the overview will take the place of the first column", + default=[], + ) + columns = blocks.ListBlock( + NavColumn(label="Column"), + min_num=1, + max_num=4, + label="Columns", + help_text="Add up to 4 columns of navigation links", + ) + button = blocks.ListBlock( + NavButton, + required=False, + min_num=0, + max_num=1, + default=[], + label="Dropdown Button", + help_text="Use it to add a CTA to link to the contents of the dropdown menu", + ) + + def clean(self, value): + result = super().clean(value) + errors = {} + + if result["overview"] and len(result["columns"]) > 3: + errors["overview"] = ErrorList( + [ + blocks.ListBlockValidationError( + block_errors={}, + non_block_errors=ErrorList( + [ValidationError("Overview cannot be used with more than 3 nav columns.")] + ), + ) + ] + ) + errors["columns"] = ErrorList( + [ + blocks.ListBlockValidationError( + block_errors={}, + non_block_errors=ErrorList( + [ValidationError('A maximum of 3 columns can be added together with an "overview".')] + ), + ) + ] + ) + + if errors: + raise blocks.StructBlockValidationError(block_errors=errors) + + return result + + class Meta: + label = "Navigation Dropdown" + icon = "bars" + template = "nav/blocks/nav_dropdown_block.html" + value_class = NavDropdownValue diff --git a/network-api/networkapi/nav/factories.py b/network-api/networkapi/nav/factories.py index 935bf6f84c8..7ef5cdb5007 100644 --- a/network-api/networkapi/nav/factories.py +++ b/network-api/networkapi/nav/factories.py @@ -1,11 +1,13 @@ import factory import wagtail_factories from wagtail import models as wagtail_models +from wagtail.rich_text import RichText from networkapi.nav import blocks as nav_blocks +from networkapi.wagtailcustomization.factories.blocks import ExtendedStructBlockFactory -class NavItemFactory(wagtail_factories.StructBlockFactory): +class NavItemFactory(ExtendedStructBlockFactory): """Factory for NavLinkBlock. Use traits to create instances based on the type of link needed: @@ -24,14 +26,6 @@ class NavItemFactory(wagtail_factories.StructBlockFactory): class Meta: model = nav_blocks.NavItem - @classmethod - def _construct_struct_value(cls, block_class, params): - """Use NavLinkValue to create the StructValue instance.""" - return nav_blocks.NavItemValue( - block_class(), - [(name, value) for name, value in params.items()], - ) - class Params: page_link = factory.Trait( link_to="page", @@ -53,7 +47,7 @@ class Params: relative_url = "" -class NavButtonFactory(wagtail_factories.StructBlockFactory): +class NavButtonFactory(ExtendedStructBlockFactory): """Factory for NavButtonBlock. Use traits to create instances based on the type of link needed: @@ -72,14 +66,6 @@ class NavButtonFactory(wagtail_factories.StructBlockFactory): class Meta: model = nav_blocks.NavButton - @classmethod - def _construct_struct_value(cls, block_class, params): - """Use NavLinkValue to create the StructValue instance.""" - return nav_blocks.NavItemValue( - block_class(), - [(name, value) for name, value in params.items()], - ) - class Params: page_link = factory.Trait( link_to="page", @@ -100,13 +86,63 @@ class Params: relative_url = "" -class NavColumnFactory(wagtail_factories.StructBlockFactory): +class NavColumnFactory(ExtendedStructBlockFactory): class Meta: model = nav_blocks.NavColumn class Params: - with_button = False + no_button = factory.Trait(button=[]) + + title = factory.Faker("sentence", nb_words=3) + nav_items = wagtail_factories.ListBlockFactory( + NavItemFactory, + **{ + "0__external_url_link": True, + "1__external_url_link": True, + "2__external_url_link": True, + "3__external_url_link": True, + }, + ) + button = wagtail_factories.ListBlockFactory(NavButtonFactory, **{"0__external_url_link": True}) + + +class NavOverviewFactory(wagtail_factories.StructBlockFactory): + class Meta: + model = nav_blocks.NavOverview title = factory.Faker("sentence", nb_words=3) - links = factory.List([factory.SubFactory(NavItemFactory) for _ in range(4)]) - button = factory.LazyAttribute(lambda o: [NavButtonFactory()] if o.with_button else []) + description = RichText(str(factory.Faker("sentence", nb_words=6))) + + +class NavDropdownFactory(ExtendedStructBlockFactory): + class Meta: + model = nav_blocks.NavDropdown + + class Params: + no_overview = factory.Trait(overview=[]) + all_columns = factory.Trait( + overview=[], + columns=wagtail_factories.ListBlockFactory( + NavColumnFactory, + **{ + "0__title": factory.Faker("sentence", nb_words=3), + "1__title": factory.Faker("sentence", nb_words=3), + "2__title": factory.Faker("sentence", nb_words=3), + "3__title": factory.Faker("sentence", nb_words=3), + }, + ), + ) + no_button = factory.Trait(button=[]) + + overview = wagtail_factories.ListBlockFactory( + NavOverviewFactory, **{"0__title": factory.Faker("sentence", nb_words=3)} + ) + columns = wagtail_factories.ListBlockFactory( + NavColumnFactory, + **{ + "0__title": factory.Faker("sentence", nb_words=3), + "1__title": factory.Faker("sentence", nb_words=3), + "2__title": factory.Faker("sentence", nb_words=3), + }, + ) + button = wagtail_factories.ListBlockFactory(NavButtonFactory, **{"0__external_url_link": True}) diff --git a/network-api/networkapi/nav/tests/test_blocks.py b/network-api/networkapi/nav/tests/test_blocks.py index 92ab08aa114..4fd92376f96 100644 --- a/network-api/networkapi/nav/tests/test_blocks.py +++ b/network-api/networkapi/nav/tests/test_blocks.py @@ -123,30 +123,149 @@ def test_default(self): """Assert that default nav_blocks.NavColumn factory works and is an external URL.""" block = nav_factories.NavColumnFactory() - self.assertEqual(len(block["links"]), 4) - for link in block["links"]: + self.assertEqual(len(block["nav_items"]), 4) + for link in block["nav_items"]: self.assertIsInstance(link.block, nav_blocks.NavItem) self.assertIsInstance(link, nav_blocks.NavItemValue) - self.assertCountEqual(block["button"], []) - - def test_with_button(self): - """Create a nav_blocks.NavColumn with a button.""" - block = nav_factories.NavColumnFactory(with_button=True) - + self.assertIsNotNone(block.button) self.assertEqual(len(block["button"]), 1) - self.assertIsInstance(block["button"][0].block, nav_blocks.NavButton) + self.assertEqual(block.button, block["button"][0]) + self.assertTrue(block.has_button) + + def test_without_button(self): + """Create a nav_blocks.NavColumn without a button.""" + block = nav_factories.NavColumnFactory(no_button=True) + + self.assertEqual(len(block["button"]), 0) + self.assertFalse(block.has_button) + self.assertIsNone(block.button) + + def test_with_variable_number_of_links(self): + """Create a nav_blocks.NavColumn with links.""" + block = nav_factories.NavColumnFactory( + **{ + "nav_items__0__page_url_link": True, + "nav_items__1__relative_url_link": True, + "nav_items__2__external_url_link": True, + } + ) + + self.assertEqual(len(block["nav_items"]), 4) + for link in block["nav_items"]: + self.assertIsInstance(link.block, nav_blocks.NavItem) + self.assertIsInstance(link, nav_blocks.NavItemValue) def test_needs_to_provide_at_least_one_link(self): with self.assertRaises(StructBlockValidationError): - block = nav_factories.NavColumnFactory(links=[]) + block = nav_factories.NavColumnFactory(nav_items=[]) nav_blocks.NavColumn().clean(block) def test_needs_to_provide_at_most_four_links(self): with self.assertRaises(StructBlockValidationError): - block = nav_factories.NavColumnFactory(links=[nav_factories.NavItemFactory() for _ in range(5)]) + block = nav_factories.NavColumnFactory( + **{ + "nav_items__0__external_url_link": True, + "nav_items__1__external_url_link": True, + "nav_items__2__external_url_link": True, + "nav_items__3__external_url_link": True, + "nav_items__4__external_url_link": True, + } + ) nav_blocks.NavColumn().clean(block) def test_cannot_have_more_than_one_button(self): with self.assertRaises(StructBlockValidationError): - block = nav_factories.NavColumnFactory(button=[nav_factories.NavButtonFactory() for _ in range(2)]) + block = nav_factories.NavColumnFactory( + **{ + "button__0__external_url_link": True, + "button__1__external_url_link": True, + } + ) nav_blocks.NavColumn().clean(block) + + +class TestNavDropdownBlock(TestCase): + def test_default_block_factory(self): + """Default factory creates a block with an overview and 3 columns.""" + block = nav_factories.NavDropdownFactory() + nav_blocks.NavDropdown().clean(block) + + self.assertEqual(len(block["overview"]), 1) + self.assertIsInstance(block["overview"][0].block, nav_blocks.NavOverview) + self.assertTrue(block.has_overview) + self.assertEqual(block.overview, block["overview"][0]) + + self.assertEqual(len(block["columns"]), 3) + for column in block["columns"]: + self.assertIsInstance(column.block, nav_blocks.NavColumn) + self.assertIsInstance(column, nav_blocks.NavColumnValue) + + self.assertEqual(len(block["button"]), 1) + self.assertIsInstance(block["button"][0].block, nav_blocks.NavButton) + self.assertTrue(block.has_button) + self.assertEqual(block.button, block["button"][0]) + + def test_block_with_four_columns(self): + """Create a nav_blocks.NavDropdown with four columns.""" + block = nav_factories.NavDropdownFactory(all_columns=True) + nav_blocks.NavDropdown().clean(block) + + self.assertEqual(len(block["overview"]), 0) + self.assertFalse(block.has_overview) + self.assertIsNone(block.overview) + + self.assertEqual(len(block["columns"]), 4) + for column in block["columns"]: + self.assertIsInstance(column.block, nav_blocks.NavColumn) + self.assertIsInstance(column, nav_blocks.NavColumnValue) + + def test_block_without_overview(self): + """Create a nav_blocks.NavDropdown without an overview.""" + block = nav_factories.NavDropdownFactory(no_overview=True) + nav_blocks.NavDropdown().clean(block) + + self.assertEqual(len(block["overview"]), 0) + self.assertFalse(block.has_overview) + self.assertIsNone(block.overview) + + self.assertEqual(len(block["columns"]), 3) + + def test_block_without_button(self): + """Create a nav_blocks.NavDropdown without a button.""" + block = nav_factories.NavDropdownFactory(no_button=True) + nav_blocks.NavDropdown().clean(block) + + self.assertEqual(len(block["button"]), 0) + self.assertFalse(block.has_button) + self.assertIsNone(block.button) + + def test_needs_at_least_one_column(self): + with self.assertRaises(StructBlockValidationError): + block = nav_factories.NavDropdownFactory(columns=[]) + nav_blocks.NavDropdown().clean(block) + + def test_cannot_have_more_than_four_columns(self): + with self.assertRaises(StructBlockValidationError): + block = nav_factories.NavDropdownFactory( + **{ + "columns__0__title": "Column 1", + "columns__1__title": "Column 2", + "columns__2__title": "Column 3", + "columns__3__title": "Column 4", + "columns__4__title": "Column 5", + } + ) + nav_blocks.NavDropdown().clean(block) + + def test_block_with_overview_cannot_have_more_than_three_columns(self): + with self.assertRaises(StructBlockValidationError): + block = nav_factories.NavDropdownFactory( + **{ + "overview__0__title": "Overview", + "columns__0__title": "Column 1", + "columns__1__title": "Column 2", + "columns__2__title": "Column 3", + "columns__3__title": "Column 4", + } + ) + nav_blocks.NavDropdown().clean(block) diff --git a/network-api/networkapi/wagtailcustomization/factories/blocks.py b/network-api/networkapi/wagtailcustomization/factories/blocks.py new file mode 100644 index 00000000000..1299e644904 --- /dev/null +++ b/network-api/networkapi/wagtailcustomization/factories/blocks.py @@ -0,0 +1,12 @@ +from wagtail_factories.blocks import StructBlockFactory + + +class ExtendedStructBlockFactory(StructBlockFactory): + @classmethod + def _construct_struct_value(cls, block_class, params): + """Use value_class defined on model's Meta to create the StructValue instance.""" + struct_value_class = cls._meta.model().meta.value_class + return struct_value_class( + block_class(), + [(name, value) for name, value in params.items()], + ) From d268780d186e507cef857f4bf5d2b5de8c5b4f5f Mon Sep 17 00:00:00 2001 From: Jhonatan Lopes Date: Mon, 1 Apr 2024 10:53:49 -0300 Subject: [PATCH 06/18] [Main Nav] Nav menu [TP1-51](#12120) * Add `NavMenu` model * Add factory for `NavMenu` * Add initial migration for `NavMenu` * Add tests for `NavMenu` * Register nav on wagtail side bar * Update migration file --- network-api/networkapi/nav/factories.py | 18 ++ .../networkapi/nav/migrations/0001_initial.py | 182 ++++++++++++++++++ .../networkapi/nav/migrations/__init__.py | 0 network-api/networkapi/nav/models.py | 38 ++++ .../networkapi/nav/tests/test_models.py | 29 +++ network-api/networkapi/nav/wagtail_hooks.py | 25 +++ 6 files changed, 292 insertions(+) create mode 100644 network-api/networkapi/nav/migrations/0001_initial.py create mode 100644 network-api/networkapi/nav/migrations/__init__.py create mode 100644 network-api/networkapi/nav/models.py create mode 100644 network-api/networkapi/nav/tests/test_models.py create mode 100644 network-api/networkapi/nav/wagtail_hooks.py diff --git a/network-api/networkapi/nav/factories.py b/network-api/networkapi/nav/factories.py index 7ef5cdb5007..03c6cd50f23 100644 --- a/network-api/networkapi/nav/factories.py +++ b/network-api/networkapi/nav/factories.py @@ -1,9 +1,11 @@ import factory import wagtail_factories +from factory.django import DjangoModelFactory from wagtail import models as wagtail_models from wagtail.rich_text import RichText from networkapi.nav import blocks as nav_blocks +from networkapi.nav import models as nav_models from networkapi.wagtailcustomization.factories.blocks import ExtendedStructBlockFactory @@ -146,3 +148,19 @@ class Params: }, ) button = wagtail_factories.ListBlockFactory(NavButtonFactory, **{"0__external_url_link": True}) + + +class NavMenuFactory(DjangoModelFactory): + class Meta: + model = nav_models.NavMenu + + title = factory.Faker("sentence", nb_words=3) + dropdowns = wagtail_factories.StreamFieldFactory( + {"dropdown": factory.SubFactory(NavDropdownFactory)}, + **{ + "0": "dropdown", + "1": "dropdown", + "2": "dropdown", + "3": "dropdown", + }, + ) diff --git a/network-api/networkapi/nav/migrations/0001_initial.py b/network-api/networkapi/nav/migrations/0001_initial.py new file mode 100644 index 00000000000..726955e21e3 --- /dev/null +++ b/network-api/networkapi/nav/migrations/0001_initial.py @@ -0,0 +1,182 @@ +# Generated by Django 4.2.10 on 2024-03-28 18:22 + +import uuid + +import django.db.models.deletion +import wagtail.blocks +import wagtail.fields +import wagtail.models +from django.db import migrations, models + +import networkapi.nav.blocks + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ("wagtailcore", "0089_log_entry_data_json_null_to_object"), + ] + + operations = [ + migrations.CreateModel( + name="NavMenu", + fields=[ + ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("translation_key", models.UUIDField(default=uuid.uuid4, editable=False)), + ("live", models.BooleanField(default=True, editable=False, verbose_name="live")), + ( + "has_unpublished_changes", + models.BooleanField(default=False, editable=False, verbose_name="has unpublished changes"), + ), + ( + "first_published_at", + models.DateTimeField(blank=True, db_index=True, null=True, verbose_name="first published at"), + ), + ( + "last_published_at", + models.DateTimeField(editable=False, null=True, verbose_name="last published at"), + ), + ("go_live_at", models.DateTimeField(blank=True, null=True, verbose_name="go live date/time")), + ("expire_at", models.DateTimeField(blank=True, null=True, verbose_name="expiry date/time")), + ("expired", models.BooleanField(default=False, editable=False, verbose_name="expired")), + ("title", models.CharField(help_text="For internal identification only", max_length=100)), + ( + "dropdowns", + wagtail.fields.StreamField( + [ + ( + "dropdown", + wagtail.blocks.StructBlock( + [ + ( + "title", + wagtail.blocks.CharBlock( + help_text="How the dropdown menu will be labelled in the nav bar", + max_length=100, + ), + ), + ( + "overview", + wagtail.blocks.ListBlock( + wagtail.blocks.StructBlock( + [ + ("title", wagtail.blocks.CharBlock(max_length=100)), + ( + "description", + wagtail.blocks.RichTextBlock( + features=["bold", "italic"], max_length=200 + ), + ), + ], + label="Overview", + ), + default=[], + help_text="If added, the overview will take the place of the first column", + label="Overview", + max_num=1, + min_num=0, + ), + ), + ( + "columns", + wagtail.blocks.ListBlock( + wagtail.blocks.StructBlock( + [ + ("title", wagtail.blocks.CharBlock(max_length=100)), + ( + "nav_items", + wagtail.blocks.ListBlock( + networkapi.nav.blocks.NavItem, + default=[], + label="Items", + max_num=4, + min_num=1, + ), + ), + ( + "button", + wagtail.blocks.ListBlock( + networkapi.nav.blocks.NavButton, + default=[], + help_text="Adds a CTA button to the bottom of the nav column.", + label="Column Button", + max_num=1, + min_num=0, + required=False, + ), + ), + ], + label="Column", + ), + help_text="Add up to 4 columns of navigation links", + label="Columns", + max_num=4, + min_num=1, + ), + ), + ( + "button", + wagtail.blocks.ListBlock( + networkapi.nav.blocks.NavButton, + default=[], + help_text="Use it to add a CTA to link to the contents of the dropdown menu", + label="Dropdown Button", + max_num=1, + min_num=0, + required=False, + ), + ), + ], + label="Dropdown", + ), + ) + ], + help_text="Add up to 5 dropdown menus", + use_json_field=True, + ), + ), + ( + "latest_revision", + models.ForeignKey( + blank=True, + editable=False, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="+", + to="wagtailcore.revision", + verbose_name="latest revision", + ), + ), + ( + "live_revision", + models.ForeignKey( + blank=True, + editable=False, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="+", + to="wagtailcore.revision", + verbose_name="live revision", + ), + ), + ( + "locale", + models.ForeignKey( + editable=False, + on_delete=django.db.models.deletion.PROTECT, + related_name="+", + to="wagtailcore.locale", + ), + ), + ], + options={ + "verbose_name": "Navigation Menu", + "verbose_name_plural": "Navigation Menus", + "abstract": False, + "unique_together": {("translation_key", "locale")}, + }, + bases=(wagtail.models.PreviewableMixin, models.Model), + ), + ] diff --git a/network-api/networkapi/nav/migrations/__init__.py b/network-api/networkapi/nav/migrations/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/network-api/networkapi/nav/models.py b/network-api/networkapi/nav/models.py new file mode 100644 index 00000000000..f6efd1a8917 --- /dev/null +++ b/network-api/networkapi/nav/models.py @@ -0,0 +1,38 @@ +from django.db import models +from wagtail import models as wagtail_models +from wagtail.admin import panels +from wagtail.fields import StreamField + +from networkapi.nav import blocks as nav_blocks + + +class NavMenu( + wagtail_models.PreviewableMixin, + wagtail_models.DraftStateMixin, + wagtail_models.RevisionMixin, + wagtail_models.TranslatableMixin, + models.Model, +): + title = models.CharField(max_length=100, help_text="For internal identification only") + + dropdowns = StreamField( + [ + ("dropdown", nav_blocks.NavDropdown(label="Dropdown")), + ], + use_json_field=True, + min_num=1, + max_num=5, + help_text="Add up to 5 dropdown menus", + ) + + panels = [ + panels.FieldPanel("title"), + panels.FieldPanel("dropdowns"), + ] + + class Meta(wagtail_models.TranslatableMixin.Meta): + verbose_name = "Navigation Menu" + verbose_name_plural = "Navigation Menus" + + def __str__(self) -> str: + return self.title diff --git a/network-api/networkapi/nav/tests/test_models.py b/network-api/networkapi/nav/tests/test_models.py new file mode 100644 index 00000000000..6595bc2da37 --- /dev/null +++ b/network-api/networkapi/nav/tests/test_models.py @@ -0,0 +1,29 @@ +from django.test import TestCase +from wagtail.blocks import StreamBlockValidationError + +from networkapi.nav import factories as nav_factories +from networkapi.nav import models as nav_models + + +class NavMenuTests(TestCase): + def test_default_factory(self): + """Test that the default factory creates a NavMenu with 4 dropdowns.""" + menu = nav_factories.NavMenuFactory() + self.assertIsInstance(menu, nav_models.NavMenu) + self.assertEqual(len(menu.dropdowns), 4) + + def test_cannot_create_nav_menu_without_dropdowns(self): + with self.assertRaises(StreamBlockValidationError): + menu = nav_factories.NavMenuFactory(dropdowns={}) + menu.dropdowns.stream_block.clean([]) + + def test_cannot_create_nav_menu_with_more_than_five_dropdowns(self): + with self.assertRaises(StreamBlockValidationError): + menu = nav_factories.NavMenuFactory( + dropdowns__0="dropdown", + dropdowns__1="dropdown", + dropdowns__2="dropdown", + dropdowns__3="dropdown", + dropdowns__4="dropdown", + ) + menu.dropdowns.stream_block.clean([]) diff --git a/network-api/networkapi/nav/wagtail_hooks.py b/network-api/networkapi/nav/wagtail_hooks.py new file mode 100644 index 00000000000..eba9b1212f2 --- /dev/null +++ b/network-api/networkapi/nav/wagtail_hooks.py @@ -0,0 +1,25 @@ +from wagtail.snippets.models import register_snippet +from wagtail.snippets.views.snippets import SnippetViewSet, SnippetViewSetGroup + +from networkapi.nav.models import NavMenu + + +class NavMenuViewSet(SnippetViewSet): + model = NavMenu + icon = "list-ul" + menu_order = 100 + menu_label = "Navigation Menus" + list_display = ("title",) + search_fields = ("title",) + ordering = ("title",) + + +class NavDropdownViewSetGroup(SnippetViewSetGroup): + items = (NavMenuViewSet,) + menu_label = "Main Navigation" + menu_name = "Main Navigation" + add_to_admin_menu = True + menu_order = 1600 + + +register_snippet(NavDropdownViewSetGroup) From 13aad08a8b307223ec8749f3b0cf3d68f86fb366 Mon Sep 17 00:00:00 2001 From: Jhonatan Lopes Date: Wed, 3 Apr 2024 08:26:13 -0300 Subject: [PATCH 07/18] [Main Nav] Featured columns [TP1-53](#12134) * Add NavFeaturedItem block * Add NavFeaturedColumn block * Add featured column to dropdown * Adjust factories to featured column * Adjust tests to new factories and add tests for featured column * Fix migration --- network-api/networkapi/nav/blocks.py | 125 ++++++++++++++-- network-api/networkapi/nav/factories.py | 95 +++++++++++- .../networkapi/nav/migrations/0001_initial.py | 28 +++- .../networkapi/nav/tests/test_blocks.py | 140 +++++++++++++++--- 4 files changed, 345 insertions(+), 43 deletions(-) diff --git a/network-api/networkapi/nav/blocks.py b/network-api/networkapi/nav/blocks.py index 549c1a39cdb..be38da5337c 100644 --- a/network-api/networkapi/nav/blocks.py +++ b/network-api/networkapi/nav/blocks.py @@ -3,6 +3,7 @@ from django.core.exceptions import ValidationError from django.forms.utils import ErrorList from wagtail import blocks +from wagtail.images.blocks import ImageChooserBlock from wagtail.telepath import register from networkapi.wagtailpages.pagemodels.customblocks.common.base_link_block import ( @@ -48,6 +49,33 @@ class Meta: register(BaseLinkBlockAdapter(), NavItem) +class NavFeaturedItem(BaseLinkBlock): + icon = ImageChooserBlock() + + def __init__(self, local_blocks=None, **kwargs): + # Use __init__ method to change the order of the blocks when constructing + # them through inheritance + super().__init__(local_blocks, **kwargs) + self.child_blocks = self.base_blocks.copy() + child_blocks = OrderedDict( + { + "label": self.child_blocks.pop("label"), + "icon": self.child_blocks.pop("icon"), + } + ) + child_blocks.update({k: v for k, v in self.child_blocks.items()}) + self.child_blocks = child_blocks + + class Meta: + value_class = NavItemValue + label = "Featured Navigation Link" + icon = "link" + template = "nav/blocks/featured_item_block.html" + + +register(BaseLinkBlockAdapter(), NavFeaturedItem) + + class NavButton(BaseLinkBlock): class Meta: value_class = NavItemValue @@ -93,6 +121,18 @@ class Meta: value_class = NavColumnValue +class NavFeaturedColumn(blocks.StructBlock): + title = blocks.CharBlock(max_length=100) + # Empty default so that it starts collapsed: + nav_items = blocks.ListBlock(NavFeaturedItem, min_num=1, max_num=4, label="Items", default=[]) + + class Meta: + label = "Featured Navigation Column" + icon = "list-ul" + template = "nav/blocks/featured_column_block.html" + value_class = NavColumnValue + + class NavOverview(blocks.StructBlock): title = blocks.CharBlock(max_length=100) description = blocks.RichTextBlock(features=["bold", "italic"], max_length=200) @@ -126,6 +166,17 @@ def button(self) -> NavButton | None: return button[0] return None + @property + def has_featured_column(self) -> bool: + return bool(self.get("featured_column")) + + @property + def featured_column(self) -> NavFeaturedColumn | None: + featured_column = self.get("featured_column") + if featured_column: + return featured_column[0] + return None + class NavDropdown(blocks.StructBlock): title = blocks.CharBlock(max_length=100, help_text="How the dropdown menu will be labelled in the nav bar") @@ -144,6 +195,14 @@ class NavDropdown(blocks.StructBlock): label="Columns", help_text="Add up to 4 columns of navigation links", ) + featured_column = blocks.ListBlock( + NavFeaturedColumn(label="Featured Column"), + min_num=0, + max_num=1, + label="Featured Column", + help_text="A column made of items and icons. If added, it will take the place of the last column", + default=[], + ) button = blocks.ListBlock( NavButton, required=False, @@ -158,24 +217,62 @@ def clean(self, value): result = super().clean(value) errors = {} - if result["overview"] and len(result["columns"]) > 3: - errors["overview"] = ErrorList( - [ - blocks.ListBlockValidationError( - block_errors={}, - non_block_errors=ErrorList( - [ValidationError("Overview cannot be used with more than 3 nav columns.")] - ), - ) - ] - ) + has_overview = bool(result["overview"]) + has_featured_column = bool(result["featured_column"]) + + allowed_number_of_columns = 4 + if has_overview: + allowed_number_of_columns -= 1 + if has_featured_column: + allowed_number_of_columns -= 1 + + current_number_of_columns = len(result["columns"]) + + if current_number_of_columns > allowed_number_of_columns: + if has_overview and not has_featured_column: + err_msg_overview = 'A maximum of 3 columns can be added together with an "overview".' + err_msg_columns = 'A maximum of 3 columns can be added together with an "overview".' + err_msg_featured_column = "" + elif has_featured_column and not has_overview: + err_msg_overview = "" + err_msg_columns = 'A maximum of 3 columns can be added together with a "featured column".' + err_msg_featured_column = "Featured column cannot be used with more than 3 nav columns." + elif has_overview and has_featured_column: + err_msg_overview = ( + 'A maximum of 2 columns can be added together with an "overview" and a "featured column".' + ) + err_msg_columns = ( + 'A maximum of 2 columns can be added together with an "overview" and a "featured column".' + ) + err_msg_featured_column = ( + 'A maximum of 2 columns can be added together with an "overview" and a "featured column".' + ) + + if err_msg_overview: + errors["overview"] = ErrorList( + [ + blocks.ListBlockValidationError( + block_errors={}, + non_block_errors=ErrorList([ValidationError(err_msg_overview)]), + ) + ] + ) + + if err_msg_featured_column: + errors["featured_column"] = ErrorList( + [ + blocks.ListBlockValidationError( + block_errors={}, + non_block_errors=ErrorList([ValidationError(err_msg_featured_column)]), + ) + ] + ) + errors["columns"] = ErrorList( [ blocks.ListBlockValidationError( block_errors={}, - non_block_errors=ErrorList( - [ValidationError('A maximum of 3 columns can be added together with an "overview".')] - ), + non_block_errors=ErrorList([ValidationError(err_msg_columns)]), ) ] ) diff --git a/network-api/networkapi/nav/factories.py b/network-api/networkapi/nav/factories.py index 03c6cd50f23..41f4fd94a27 100644 --- a/network-api/networkapi/nav/factories.py +++ b/network-api/networkapi/nav/factories.py @@ -49,6 +49,31 @@ class Params: relative_url = "" +class NavFeaturedItemFactory(ExtendedStructBlockFactory): + class Meta: + model = nav_blocks.NavFeaturedItem + + class Params: + page_link = factory.Trait( + link_to="page", + page=factory.Iterator(wagtail_models.Page.objects.filter(locale_id="1")), + ) + external_url_link = factory.Trait(link_to="external_url", external_url=factory.Faker("url")) + relative_url_link = factory.Trait(link_to="relative_url", relative_url=f'/{factory.Faker("uri_path")}') + + label = factory.Faker("sentence", nb_words=3) + icon = factory.SubFactory(wagtail_factories.ImageChooserBlockFactory) + + # Setup default link as external URL (it won't pass validation without a link type defined though + # so it's still necessary to use the factory with traits) + link_to = "external_url" + # Set all link types to None by default. Only define the needed link type in the factory + # trait to avoid conflicts + page = None + external_url = "" + relative_url = "" + + class NavButtonFactory(ExtendedStructBlockFactory): """Factory for NavButtonBlock. @@ -108,6 +133,22 @@ class Params: button = wagtail_factories.ListBlockFactory(NavButtonFactory, **{"0__external_url_link": True}) +class NavFeaturedColumnFactory(ExtendedStructBlockFactory): + class Meta: + model = nav_blocks.NavFeaturedColumn + + title = factory.Faker("sentence", nb_words=3) + nav_items = wagtail_factories.ListBlockFactory( + NavFeaturedItemFactory, + **{ + "0__external_url_link": True, + "1__external_url_link": True, + "2__external_url_link": True, + "3__external_url_link": True, + }, + ) + + class NavOverviewFactory(wagtail_factories.StructBlockFactory): class Meta: model = nav_blocks.NavOverview @@ -121,8 +162,24 @@ class Meta: model = nav_blocks.NavDropdown class Params: - no_overview = factory.Trait(overview=[]) - all_columns = factory.Trait( + with_overview = factory.Trait( + overview=wagtail_factories.ListBlockFactory( + NavOverviewFactory, + **{ + "0__title": factory.Faker("sentence", nb_words=3), + }, + ), + columns=wagtail_factories.ListBlockFactory( + NavColumnFactory, + **{ + "0__title": factory.Faker("sentence", nb_words=3), + "1__title": factory.Faker("sentence", nb_words=3), + "2__title": factory.Faker("sentence", nb_words=3), + }, + ), + featured_column=[], + ) + with_featured_column = factory.Trait( overview=[], columns=wagtail_factories.ListBlockFactory( NavColumnFactory, @@ -130,23 +187,49 @@ class Params: "0__title": factory.Faker("sentence", nb_words=3), "1__title": factory.Faker("sentence", nb_words=3), "2__title": factory.Faker("sentence", nb_words=3), - "3__title": factory.Faker("sentence", nb_words=3), + }, + ), + featured_column=wagtail_factories.ListBlockFactory( + NavFeaturedColumnFactory, + **{ + "0__title": factory.Faker("sentence", nb_words=3), + }, + ), + ) + with_overview_and_featured_column = factory.Trait( + overview=wagtail_factories.ListBlockFactory( + NavOverviewFactory, + **{ + "0__title": factory.Faker("sentence", nb_words=3), + }, + ), + columns=wagtail_factories.ListBlockFactory( + NavColumnFactory, + **{ + "0__title": factory.Faker("sentence", nb_words=3), + "1__title": factory.Faker("sentence", nb_words=3), + }, + ), + featured_column=wagtail_factories.ListBlockFactory( + NavFeaturedColumnFactory, + **{ + "0__title": factory.Faker("sentence", nb_words=3), }, ), ) no_button = factory.Trait(button=[]) - overview = wagtail_factories.ListBlockFactory( - NavOverviewFactory, **{"0__title": factory.Faker("sentence", nb_words=3)} - ) + overview = wagtail_factories.ListBlockFactory(NavOverviewFactory) columns = wagtail_factories.ListBlockFactory( NavColumnFactory, **{ "0__title": factory.Faker("sentence", nb_words=3), "1__title": factory.Faker("sentence", nb_words=3), "2__title": factory.Faker("sentence", nb_words=3), + "3__title": factory.Faker("sentence", nb_words=3), }, ) + featured_column = wagtail_factories.ListBlockFactory(NavFeaturedColumnFactory) button = wagtail_factories.ListBlockFactory(NavButtonFactory, **{"0__external_url_link": True}) diff --git a/network-api/networkapi/nav/migrations/0001_initial.py b/network-api/networkapi/nav/migrations/0001_initial.py index 726955e21e3..36c37b042ca 100644 --- a/network-api/networkapi/nav/migrations/0001_initial.py +++ b/network-api/networkapi/nav/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.10 on 2024-03-28 18:22 +# Generated by Django 4.2.11 on 2024-04-01 16:39 import uuid @@ -116,6 +116,32 @@ class Migration(migrations.Migration): min_num=1, ), ), + ( + "featured_column", + wagtail.blocks.ListBlock( + wagtail.blocks.StructBlock( + [ + ("title", wagtail.blocks.CharBlock(max_length=100)), + ( + "nav_items", + wagtail.blocks.ListBlock( + networkapi.nav.blocks.NavFeaturedItem, + default=[], + label="Items", + max_num=4, + min_num=1, + ), + ), + ], + label="Featured Column", + ), + default=[], + help_text="A column made of items and icons. If added, it will take the place of the last column", + label="Featured Column", + max_num=1, + min_num=0, + ), + ), ( "button", wagtail.blocks.ListBlock( diff --git a/network-api/networkapi/nav/tests/test_blocks.py b/network-api/networkapi/nav/tests/test_blocks.py index 4fd92376f96..06cfb11a56b 100644 --- a/network-api/networkapi/nav/tests/test_blocks.py +++ b/network-api/networkapi/nav/tests/test_blocks.py @@ -65,6 +65,17 @@ def test_needs_to_provide_at_least_one_link(self): nav_blocks.NavItem().clean(block) +class TestNavFeaturedItemBlock(TestCase): + def test_default(self): + """Assert that default nav_blocks.NavFeaturedItem factory works and is an external URL.""" + block = nav_factories.NavFeaturedItemFactory() + + # Assert that the page link is custom URL and that it is correct + url = block["external_url"] + self.assertEqual(block.url, url) + self.assertTrue(block.open_in_new_window) + + class TestNavButton(TestCase): def test_default(self): """Assert that default nav_blocks.NavButton factory works and is an external URL.""" @@ -184,52 +195,111 @@ def test_cannot_have_more_than_one_button(self): nav_blocks.NavColumn().clean(block) +class TestNavFeaturedColumnBlock(TestCase): + def test_default(self): + """Assert that default nav_blocks.NavFeaturedColumn factory works and is an external URL.""" + block = nav_factories.NavFeaturedColumnFactory() + + self.assertEqual(len(block["nav_items"]), 4) + for link in block["nav_items"]: + self.assertIsInstance(link.block, nav_blocks.NavFeaturedItem) + self.assertIsInstance(link, nav_blocks.NavItemValue) + + def test_with_variable_number_of_links(self): + """Create a nav_blocks.NavFeaturedColumn with links.""" + block = nav_factories.NavFeaturedColumnFactory( + **{ + "nav_items__0__page_url_link": True, + "nav_items__1__relative_url_link": True, + "nav_items__2__external_url_link": True, + } + ) + + self.assertEqual(len(block["nav_items"]), 4) + for link in block["nav_items"]: + self.assertIsInstance(link.block, nav_blocks.NavFeaturedItem) + self.assertIsInstance(link, nav_blocks.NavItemValue) + + def test_needs_to_provide_at_least_one_link(self): + with self.assertRaises(StructBlockValidationError): + block = nav_factories.NavFeaturedColumnFactory(nav_items=[]) + nav_blocks.NavFeaturedColumn().clean(block) + + def test_needs_to_provide_at_most_four_links(self): + with self.assertRaises(StructBlockValidationError): + block = nav_factories.NavFeaturedColumnFactory( + **{ + "nav_items__0__external_url_link": True, + "nav_items__1__external_url_link": True, + "nav_items__2__external_url_link": True, + "nav_items__3__external_url_link": True, + "nav_items__4__external_url_link": True, + } + ) + nav_blocks.NavFeaturedColumn().clean(block) + + class TestNavDropdownBlock(TestCase): def test_default_block_factory(self): - """Default factory creates a block with an overview and 3 columns.""" + """Default factory creates a block with 4 columns and a button""" block = nav_factories.NavDropdownFactory() nav_blocks.NavDropdown().clean(block) - self.assertEqual(len(block["overview"]), 1) - self.assertIsInstance(block["overview"][0].block, nav_blocks.NavOverview) - self.assertTrue(block.has_overview) - self.assertEqual(block.overview, block["overview"][0]) + self.assertEqual(len(block["overview"]), 0) + self.assertFalse(block.has_overview) + self.assertIsNone(block.overview) - self.assertEqual(len(block["columns"]), 3) + self.assertEqual(len(block["columns"]), 4) for column in block["columns"]: self.assertIsInstance(column.block, nav_blocks.NavColumn) self.assertIsInstance(column, nav_blocks.NavColumnValue) + self.assertEqual(len(block["featured_column"]), 0) + self.assertFalse(block.has_featured_column) + self.assertIsNone(block.featured_column) + self.assertEqual(len(block["button"]), 1) self.assertIsInstance(block["button"][0].block, nav_blocks.NavButton) self.assertTrue(block.has_button) self.assertEqual(block.button, block["button"][0]) - def test_block_with_four_columns(self): - """Create a nav_blocks.NavDropdown with four columns.""" - block = nav_factories.NavDropdownFactory(all_columns=True) + def test_block_with_overview(self): + """Create a nav_blocks.NavDropdown with an overview.""" + block = nav_factories.NavDropdownFactory(with_overview=True) nav_blocks.NavDropdown().clean(block) - self.assertEqual(len(block["overview"]), 0) - self.assertFalse(block.has_overview) - self.assertIsNone(block.overview) + self.assertEqual(len(block["overview"]), 1) + self.assertTrue(block.has_overview) + self.assertEqual(block.overview, block["overview"][0]) - self.assertEqual(len(block["columns"]), 4) - for column in block["columns"]: - self.assertIsInstance(column.block, nav_blocks.NavColumn) - self.assertIsInstance(column, nav_blocks.NavColumnValue) + self.assertEqual(len(block["columns"]), 3) - def test_block_without_overview(self): - """Create a nav_blocks.NavDropdown without an overview.""" - block = nav_factories.NavDropdownFactory(no_overview=True) + def test_block_with_featured_column(self): + """Create a nav_blocks.NavDropdown with a featured column.""" + block = nav_factories.NavDropdownFactory(with_featured_column=True) nav_blocks.NavDropdown().clean(block) - self.assertEqual(len(block["overview"]), 0) - self.assertFalse(block.has_overview) - self.assertIsNone(block.overview) + self.assertEqual(len(block["featured_column"]), 1) + self.assertTrue(block.has_featured_column) + self.assertEqual(block.featured_column, block["featured_column"][0]) self.assertEqual(len(block["columns"]), 3) + def test_block_with_overview_and_featured_column(self): + """Create a nav_blocks.NavDropdown with an overview and a featured column.""" + block = nav_factories.NavDropdownFactory(with_overview_and_featured_column=True) + nav_blocks.NavDropdown().clean(block) + + self.assertEqual(len(block["overview"]), 1) + self.assertTrue(block.has_overview) + self.assertEqual(block.overview, block["overview"][0]) + + self.assertEqual(len(block["featured_column"]), 1) + self.assertTrue(block.has_featured_column) + self.assertEqual(block.featured_column, block["featured_column"][0]) + + self.assertEqual(len(block["columns"]), 2) + def test_block_without_button(self): """Create a nav_blocks.NavDropdown without a button.""" block = nav_factories.NavDropdownFactory(no_button=True) @@ -269,3 +339,29 @@ def test_block_with_overview_cannot_have_more_than_three_columns(self): } ) nav_blocks.NavDropdown().clean(block) + + def test_block_with_featured_column_cannot_have_more_than_three_columns(self): + with self.assertRaises(StructBlockValidationError): + block = nav_factories.NavDropdownFactory( + **{ + "columns__0__title": "Column 1", + "columns__1__title": "Column 2", + "columns__2__title": "Column 3", + "columns__3__title": "Column 4", + "featured_column__0__title": "Featured column", + } + ) + nav_blocks.NavDropdown().clean(block) + + def test_block_with_featured_column_and_overview_cannot_have_more_than_two_columns(self): + with self.assertRaises(StructBlockValidationError): + block = nav_factories.NavDropdownFactory( + **{ + "overview__0__title": "Overview", + "columns__0__title": "Column 1", + "columns__1__title": "Column 2", + "columns__2__title": "Column 3", + "featured_column__0__title": "Featured column", + } + ) + nav_blocks.NavDropdown().clean(block) From 8eafe531744d3167549ba29cedd6467e31dae74b Mon Sep 17 00:00:00 2001 From: Jhonatan Lopes Date: Thu, 4 Apr 2024 11:48:48 -0300 Subject: [PATCH 08/18] [Main Nav] Nav Settings [TP1-38](#12137) * Better panels on NavMenu * Add SiteNavMenu setting * Show only default locale nav menus on settings * Fix donate banner test * Add title and locale to NavDropdownFactory * Remove print statements --- network-api/networkapi/donate_banner/tests.py | 5 +- network-api/networkapi/nav/factories.py | 2 + .../nav/migrations/0002_sitenavmenu.py | 42 ++++++++++++++ network-api/networkapi/nav/models.py | 33 +++++++++++ .../networkapi/nav/tests/test_views.py | 58 +++++++++++++++++++ network-api/networkapi/nav/wagtail_hooks.py | 16 +++++ 6 files changed, 154 insertions(+), 2 deletions(-) create mode 100644 network-api/networkapi/nav/migrations/0002_sitenavmenu.py create mode 100644 network-api/networkapi/nav/tests/test_views.py diff --git a/network-api/networkapi/donate_banner/tests.py b/network-api/networkapi/donate_banner/tests.py index aa20d27a884..56a73578132 100644 --- a/network-api/networkapi/donate_banner/tests.py +++ b/network-api/networkapi/donate_banner/tests.py @@ -1,7 +1,7 @@ from django.contrib.auth import get_user_model from django.test import TestCase from django.urls import reverse -from wagtail.admin.viewsets import viewsets +from wagtail.admin.viewsets import viewsets as wagtail_admin_viewsets from wagtail.admin.viewsets.chooser import ChooserViewSet from wagtail.models import Locale from wagtail.test.utils import WagtailTestUtils @@ -17,7 +17,8 @@ def is_donate_banner_chooser_viewset(self, viewset): def get_chooser_viewset(self): # Get the last registered ChooserViewSet for the DonateBanner model. # Note: There can be multiple ChooserViewSets registered for a model. - model_viewsets = [x for x in viewsets.viewsets if self.is_donate_banner_chooser_viewset(x)] + wagtail_admin_viewsets.populate() + model_viewsets = [x for x in wagtail_admin_viewsets.viewsets if self.is_donate_banner_chooser_viewset(x)] return model_viewsets[-1] def setUp(self): diff --git a/network-api/networkapi/nav/factories.py b/network-api/networkapi/nav/factories.py index 41f4fd94a27..5c286c3115e 100644 --- a/network-api/networkapi/nav/factories.py +++ b/network-api/networkapi/nav/factories.py @@ -219,6 +219,7 @@ class Params: ) no_button = factory.Trait(button=[]) + title = factory.Faker("sentence", nb_words=3) overview = wagtail_factories.ListBlockFactory(NavOverviewFactory) columns = wagtail_factories.ListBlockFactory( NavColumnFactory, @@ -247,3 +248,4 @@ class Meta: "3": "dropdown", }, ) + locale = factory.LazyFunction(lambda: wagtail_models.Locale.get_default()) diff --git a/network-api/networkapi/nav/migrations/0002_sitenavmenu.py b/network-api/networkapi/nav/migrations/0002_sitenavmenu.py new file mode 100644 index 00000000000..3f35e37b12a --- /dev/null +++ b/network-api/networkapi/nav/migrations/0002_sitenavmenu.py @@ -0,0 +1,42 @@ +# Generated by Django 4.2.11 on 2024-04-02 12:26 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("wagtailcore", "0089_log_entry_data_json_null_to_object"), + ("nav", "0001_initial"), + ] + + operations = [ + migrations.CreateModel( + name="SiteNavMenu", + fields=[ + ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ( + "active_nav_menu", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="site_nav_menu", + verbose_name="Active Navigation Menu", + to="nav.navmenu", + ), + ), + ( + "site", + models.OneToOneField( + editable=False, on_delete=django.db.models.deletion.CASCADE, to="wagtailcore.site" + ), + ), + ], + options={ + "verbose_name": "Navigation Menu", + "verbose_name_plural": "Navigation Menus", + }, + ), + ] diff --git a/network-api/networkapi/nav/models.py b/network-api/networkapi/nav/models.py index f6efd1a8917..6e0bb10ef11 100644 --- a/network-api/networkapi/nav/models.py +++ b/network-api/networkapi/nav/models.py @@ -1,7 +1,10 @@ from django.db import models from wagtail import models as wagtail_models from wagtail.admin import panels +from wagtail.contrib.settings.models import BaseSiteSetting, register_setting from wagtail.fields import StreamField +from wagtail.search import index +from wagtail_localize.fields import TranslatableField from networkapi.nav import blocks as nav_blocks @@ -26,13 +29,43 @@ class NavMenu( ) panels = [ + panels.HelpPanel(content="To enable a navigation menu on a site, go to Settings > Navigation Menus."), panels.FieldPanel("title"), panels.FieldPanel("dropdowns"), ] + translatable_fields = [ + TranslatableField("title"), + TranslatableField("dropdowns"), + ] + + search_fields = [ + index.SearchField("title"), + ] + class Meta(wagtail_models.TranslatableMixin.Meta): verbose_name = "Navigation Menu" verbose_name_plural = "Navigation Menus" def __str__(self) -> str: return self.title + + +@register_setting +class SiteNavMenu(BaseSiteSetting): + active_nav_menu = models.ForeignKey( + "nav.NavMenu", + null=True, + blank=True, + on_delete=models.SET_NULL, + related_name="site_nav_menu", + verbose_name="Active Navigation Menu", + ) + + content_panels = [ + panels.FieldPanel("active_nav_menu"), + ] + + class Meta: + verbose_name = "Navigation Menu" + verbose_name_plural = "Navigation Menus" diff --git a/network-api/networkapi/nav/tests/test_views.py b/network-api/networkapi/nav/tests/test_views.py new file mode 100644 index 00000000000..5ce457f264b --- /dev/null +++ b/network-api/networkapi/nav/tests/test_views.py @@ -0,0 +1,58 @@ +from django.contrib.auth import get_user_model +from django.test import TestCase +from django.urls import reverse +from wagtail.admin.viewsets import viewsets as wagtail_admin_viewsets +from wagtail.admin.viewsets.chooser import ChooserViewSet +from wagtail.models import Locale +from wagtail.test.utils import WagtailTestUtils + +from networkapi.nav import factories as nav_factories +from networkapi.nav import models as nav_models + + +class TestNavMenuSnippetChooser(WagtailTestUtils, TestCase): + def is_nav_menu_chooser_viewset(self, viewset): + return viewset.model == nav_models.NavMenu and issubclass(viewset.__class__, ChooserViewSet) + + def get_chooser_viewset(self): + # Get the last registered ChooserViewSet for the NavMenu model. + # Note: There can be multiple ChooserViewSets registered for a model. + wagtail_admin_viewsets.populate() + model_viewsets = [x for x in wagtail_admin_viewsets.viewsets if self.is_nav_menu_chooser_viewset(x)] + return model_viewsets[-1] + + def setUp(self): + # Get the chooser url from the viewsets registry. + # Would have been easier to get from model.snippet_viewset, but + # that does not work somehow. + chooser_viewset = self.get_chooser_viewset() + self.chooser_url = reverse(f"{chooser_viewset.name}:choose") + + User = get_user_model() + self.user = User.objects.create_superuser("admin-user", "admin@example.com", "password") + self.client.force_login(self.user) + + self.default_locale = Locale.get_default() + self.fr_locale = Locale.objects.create(language_code="fr") + self.menus = [ + nav_factories.NavMenuFactory.create(title="Generic"), + nav_factories.NavMenuFactory.create(title="Foundation"), + nav_factories.NavMenuFactory.create(title="Mozfest"), + ] + + def test_menu_chooser_exclude_locale(self): + translated_menu = self.menus[0].copy_for_translation(self.fr_locale) + translated_menu.save() + all_menus = nav_models.NavMenu.objects.all() + default_menus = nav_models.NavMenu.objects.filter(locale=self.default_locale) + response = self.client.get(self.chooser_url) + results = response.context["results"] + + # Chooser does not include every menu, but only the default language ones + self.assertNotEqual(len(results), all_menus.count()) + self.assertEqual(len(results), default_menus.count()) + self.assertNotIn(translated_menu, results) + self.assertIn(self.menus[0], results) + + # Menu chooser should not contain locale filter + self.assertNotContains(response, "id_locale") diff --git a/network-api/networkapi/nav/wagtail_hooks.py b/network-api/networkapi/nav/wagtail_hooks.py index eba9b1212f2..efe0ce37397 100644 --- a/network-api/networkapi/nav/wagtail_hooks.py +++ b/network-api/networkapi/nav/wagtail_hooks.py @@ -1,7 +1,23 @@ +from wagtail import hooks from wagtail.snippets.models import register_snippet from wagtail.snippets.views.snippets import SnippetViewSet, SnippetViewSetGroup from networkapi.nav.models import NavMenu +from networkapi.wagtailcustomization.views.snippet_chooser import ( + DefaultLocaleSnippetChooserViewSet, +) + + +# Customise chooser to only show the default language nav menus as options. +# We do not want editors to select the translations as +# localisation will be handled on the template instead. +@hooks.register("register_admin_viewset") +def register_nav_menu_chooser_viewset(): + return DefaultLocaleSnippetChooserViewSet( + "wagtailsnippetchoosers_custom_navmenu", + model=NavMenu, + url_prefix="nav/chooser", + ) class NavMenuViewSet(SnippetViewSet): From 881098aa748d12a1454d419f6f58acd67eb5e9dd Mon Sep 17 00:00:00 2001 From: Jhonatan Lopes Date: Mon, 8 Apr 2024 17:37:04 -0300 Subject: [PATCH 09/18] [Main Nav][Frontend] NavItem template (#12146) * Add template tag to render SVG from static dir * Add arrow glyphs for nav * Nav item block template * Update template path on NavItem block * Lint * Add new colours * Remove `include_svg` template tag * Use font glyph instead of SVG * Only display arrow if link is external * Rename `open_in_new_window` prop to `is_external` * Rename item template * Add template docs * Rename item template * Use display:block on description --- network-api/networkapi/nav/blocks.py | 4 ++-- network-api/networkapi/nav/tests/test_blocks.py | 8 ++++---- .../templates/fragments/blocks/nav/item.html | 17 +++++++++++++++++ .../templates/fragments/blocks/nav/item.md | 5 +++++ .../templates/fragments/blocks/nav/item.yml | 7 +++++++ source/images/glyphs/arrow-right-dark-theme.svg | 1 + source/images/glyphs/arrow-right-hover.svg | 1 + source/images/glyphs/arrow-right.svg | 1 + .../images/glyphs/arrow-up-right-dark-theme.svg | 1 + source/images/glyphs/arrow-up-right-hover.svg | 1 + source/images/glyphs/arrow-up-right.svg | 1 + tailwind.config.js | 2 ++ 12 files changed, 43 insertions(+), 6 deletions(-) create mode 100644 network-api/networkapi/templates/fragments/blocks/nav/item.html create mode 100644 network-api/networkapi/templates/fragments/blocks/nav/item.md create mode 100644 network-api/networkapi/templates/fragments/blocks/nav/item.yml create mode 100644 source/images/glyphs/arrow-right-dark-theme.svg create mode 100644 source/images/glyphs/arrow-right-hover.svg create mode 100644 source/images/glyphs/arrow-right.svg create mode 100644 source/images/glyphs/arrow-up-right-dark-theme.svg create mode 100644 source/images/glyphs/arrow-up-right-hover.svg create mode 100644 source/images/glyphs/arrow-up-right.svg diff --git a/network-api/networkapi/nav/blocks.py b/network-api/networkapi/nav/blocks.py index be38da5337c..e33cd92ffef 100644 --- a/network-api/networkapi/nav/blocks.py +++ b/network-api/networkapi/nav/blocks.py @@ -15,7 +15,7 @@ class NavItemValue(BaseLinkValue): @property - def open_in_new_window(self) -> bool: + def is_external(self) -> bool: link_to = self.get("link_to") if link_to == "external_url": return True @@ -43,7 +43,7 @@ class Meta: value_class = NavItemValue label = "Navigation Link" icon = "link" - template = "nav/blocks/nav_link_block.html" + template = "fragments/blocks/nav/item.html" register(BaseLinkBlockAdapter(), NavItem) diff --git a/network-api/networkapi/nav/tests/test_blocks.py b/network-api/networkapi/nav/tests/test_blocks.py index 06cfb11a56b..724f3afa30a 100644 --- a/network-api/networkapi/nav/tests/test_blocks.py +++ b/network-api/networkapi/nav/tests/test_blocks.py @@ -25,7 +25,7 @@ def test_page_link(self): default_locale = Locale.get_default() self.assertEqual(page.locale, default_locale) - self.assertFalse(block.open_in_new_window) + self.assertFalse(block.is_external) # Assert that other fields are empty self.assertEqual(block["external_url"], "") @@ -39,7 +39,7 @@ def test_external_url_link(self): url = block["external_url"] self.assertIsNotNone(url) - self.assertTrue(block.open_in_new_window) + self.assertTrue(block.is_external) # Assert that other fields are empty self.assertIsNone(block["page"]) @@ -53,7 +53,7 @@ def test_relative_url_link(self): url = block["relative_url"] self.assertIsNotNone(url) - self.assertFalse(block.open_in_new_window) + self.assertFalse(block.is_external) # Assert that other fields are empty self.assertIsNone(block["page"]) @@ -73,7 +73,7 @@ def test_default(self): # Assert that the page link is custom URL and that it is correct url = block["external_url"] self.assertEqual(block.url, url) - self.assertTrue(block.open_in_new_window) + self.assertTrue(block.is_external) class TestNavButton(TestCase): diff --git a/network-api/networkapi/templates/fragments/blocks/nav/item.html b/network-api/networkapi/templates/fragments/blocks/nav/item.html new file mode 100644 index 00000000000..36b93c94e84 --- /dev/null +++ b/network-api/networkapi/templates/fragments/blocks/nav/item.html @@ -0,0 +1,17 @@ +{% load static i18n wagtailadmin_tags %} + +{% fragment as font_styles %}tw-text-xl tw-font-semibold tw-capitalize tw-text-black{% endfragment %} +{% fragment as hover_styles %}group-hover:tw-text-blue-80 group-hover:tw-underline{% endfragment %} +{% fragment as after_arrow_styles %}after:tw-content-['_↗'] after:tw-font-['Nunito_Sans'] after:tw-text-gray-20 group-hover:after:tw-text-blue-80{% endfragment %} + +
+ + {{ value.label }} + + {% if value.description %} +

+ {{ value.description }} +

+ {% endif %} +
diff --git a/network-api/networkapi/templates/fragments/blocks/nav/item.md b/network-api/networkapi/templates/fragments/blocks/nav/item.md new file mode 100644 index 00000000000..43c24b9cd0a --- /dev/null +++ b/network-api/networkapi/templates/fragments/blocks/nav/item.md @@ -0,0 +1,5 @@ +# Navigation Item + +An item in the navigation menu, holding a title and a link to a resource. + +If the resource is external to MoFo or it's a MoFo page with their own navigation menu (like PNI), the item should display an arrow (↗) indicating that this is an external resource and open it in a new tab. \ No newline at end of file diff --git a/network-api/networkapi/templates/fragments/blocks/nav/item.yml b/network-api/networkapi/templates/fragments/blocks/nav/item.yml new file mode 100644 index 00000000000..ae2325a073d --- /dev/null +++ b/network-api/networkapi/templates/fragments/blocks/nav/item.yml @@ -0,0 +1,7 @@ +name: Navigation Item +context: + value: + label: "*Privacy not included" + description: Scrutinizing the privacy & security of consumer tech + url: /privacynotincluded + is_external: True \ No newline at end of file diff --git a/source/images/glyphs/arrow-right-dark-theme.svg b/source/images/glyphs/arrow-right-dark-theme.svg new file mode 100644 index 00000000000..d924ebd89af --- /dev/null +++ b/source/images/glyphs/arrow-right-dark-theme.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/source/images/glyphs/arrow-right-hover.svg b/source/images/glyphs/arrow-right-hover.svg new file mode 100644 index 00000000000..4f7e1a1283e --- /dev/null +++ b/source/images/glyphs/arrow-right-hover.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/source/images/glyphs/arrow-right.svg b/source/images/glyphs/arrow-right.svg new file mode 100644 index 00000000000..7d13e111819 --- /dev/null +++ b/source/images/glyphs/arrow-right.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/source/images/glyphs/arrow-up-right-dark-theme.svg b/source/images/glyphs/arrow-up-right-dark-theme.svg new file mode 100644 index 00000000000..d2a64bce77c --- /dev/null +++ b/source/images/glyphs/arrow-up-right-dark-theme.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/source/images/glyphs/arrow-up-right-hover.svg b/source/images/glyphs/arrow-up-right-hover.svg new file mode 100644 index 00000000000..8be42df3415 --- /dev/null +++ b/source/images/glyphs/arrow-up-right-hover.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/source/images/glyphs/arrow-up-right.svg b/source/images/glyphs/arrow-up-right.svg new file mode 100644 index 00000000000..6df17389a4c --- /dev/null +++ b/source/images/glyphs/arrow-up-right.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/tailwind.config.js b/tailwind.config.js index 8eab0861c53..e13cb6a5e05 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -143,6 +143,7 @@ module.exports = { white: colors.white, table: "#dee2e6", gray: { + "02": "#fafafa", "05": "#f2f2f2", 20: "#cccccc", 40: "#999999", @@ -159,6 +160,7 @@ module.exports = { 100: "#9a000e", }, blue: { + "03": "#f5f5fd", "05": "#e7e7fc", 10: "#d3d5fc", 20: "#b7b9fa", From 09068d23c249f3901168c64b084d0abdbf56704e Mon Sep 17 00:00:00 2001 From: Jhonatan Lopes Date: Thu, 18 Apr 2024 16:17:54 -0300 Subject: [PATCH 10/18] [Main Nav] Frontend work (#12220) --- maintenance/static/css/main.css | 2 +- network-api/networkapi/nav/blocks.py | 29 ++++--- network-api/networkapi/nav/models.py | 2 + .../networkapi/nav/tests/test_blocks.py | 77 ++++++++++++++++--- .../templatetags/wagtailImages_tags.py | 4 + .../templatetags/wagtailcore_tags.py | 6 ++ network-api/networkapi/settings.py | 2 + .../fragments/blocks/nav/button.html | 13 ++++ .../templates/fragments/blocks/nav/button.md | 8 ++ .../templates/fragments/blocks/nav/button.yml | 7 ++ .../fragments/blocks/nav/column.html | 16 ++++ .../templates/fragments/blocks/nav/column.yml | 34 ++++++++ .../fragments/blocks/nav/dropdown.html | 46 +++++++++++ .../fragments/blocks/nav/dropdown.md | 3 + .../fragments/blocks/nav/dropdown.yml | 29 +++++++ .../fragments/blocks/nav/featured-column.html | 13 ++++ .../fragments/blocks/nav/featured-column.yml | 18 +++++ .../fragments/blocks/nav/featured-item.html | 12 +++ .../fragments/blocks/nav/featured-item.md | 3 + .../fragments/blocks/nav/featured-item.yml | 13 ++++ .../templates/fragments/blocks/nav/item.html | 9 +-- .../fragments/blocks/nav/overview.html | 15 ++++ .../fragments/blocks/nav/overview.md | 5 ++ .../fragments/blocks/nav/overview.yml | 11 +++ .../blocks/nav/variants/button--cta.html | 1 + .../variants/button--primary-full-width.html | 1 + .../templates/fragments/nav/menu.html | 29 +++++++ .../templates/fragments/nav/menu.yml | 12 +++ .../templates/fragments/primary_nav.html | 34 ++++---- .../networkapi/templates/pages/base.html | 8 +- .../templates/pages/styleguide.html | 4 + source/js/components/accordion/accordion.js | 54 +++++++++++++ source/js/components/nav/desktop-dropdown.js | 65 ++++++++++++++++ source/js/components/nav/mobile-dropdown.js | 39 ++++++++++ source/js/main.js | 12 +++ source/js/primary-nav.js | 12 +++ source/sass/components/primary-nav.scss | 15 ++-- tailwind.config.js | 10 +++ 38 files changed, 618 insertions(+), 55 deletions(-) create mode 100644 network-api/networkapi/project_styleguide/templatetags/wagtailImages_tags.py create mode 100644 network-api/networkapi/project_styleguide/templatetags/wagtailcore_tags.py create mode 100644 network-api/networkapi/templates/fragments/blocks/nav/button.html create mode 100644 network-api/networkapi/templates/fragments/blocks/nav/button.md create mode 100644 network-api/networkapi/templates/fragments/blocks/nav/button.yml create mode 100644 network-api/networkapi/templates/fragments/blocks/nav/column.html create mode 100644 network-api/networkapi/templates/fragments/blocks/nav/column.yml create mode 100644 network-api/networkapi/templates/fragments/blocks/nav/dropdown.html create mode 100644 network-api/networkapi/templates/fragments/blocks/nav/dropdown.md create mode 100644 network-api/networkapi/templates/fragments/blocks/nav/dropdown.yml create mode 100644 network-api/networkapi/templates/fragments/blocks/nav/featured-column.html create mode 100644 network-api/networkapi/templates/fragments/blocks/nav/featured-column.yml create mode 100644 network-api/networkapi/templates/fragments/blocks/nav/featured-item.html create mode 100644 network-api/networkapi/templates/fragments/blocks/nav/featured-item.md create mode 100644 network-api/networkapi/templates/fragments/blocks/nav/featured-item.yml create mode 100644 network-api/networkapi/templates/fragments/blocks/nav/overview.html create mode 100644 network-api/networkapi/templates/fragments/blocks/nav/overview.md create mode 100644 network-api/networkapi/templates/fragments/blocks/nav/overview.yml create mode 100644 network-api/networkapi/templates/fragments/blocks/nav/variants/button--cta.html create mode 100644 network-api/networkapi/templates/fragments/blocks/nav/variants/button--primary-full-width.html create mode 100644 network-api/networkapi/templates/fragments/nav/menu.html create mode 100644 network-api/networkapi/templates/fragments/nav/menu.yml create mode 100644 source/js/components/accordion/accordion.js create mode 100644 source/js/components/nav/desktop-dropdown.js create mode 100644 source/js/components/nav/mobile-dropdown.js diff --git a/maintenance/static/css/main.css b/maintenance/static/css/main.css index 8051dfc622c..7b0563efdc6 100644 --- a/maintenance/static/css/main.css +++ b/maintenance/static/css/main.css @@ -8294,7 +8294,7 @@ a.text-gray-dark:focus, a.text-gray-dark:hover { padding-bottom: calc( + 22px - 6px / 2); } } @media (min-width: 768px) { #primary-nav-container .nav-links a { - padding-bottom: calc( 16px + 9px - 6px / 2); } } + padding-bottom: 1rem; } } #primary-nav-container .nav-links a:focus, #primary-nav-container .nav-links a:active, #primary-nav-container .nav-links a:hover { text-decoration: none; border-bottom-color: #cccccc; } diff --git a/network-api/networkapi/nav/blocks.py b/network-api/networkapi/nav/blocks.py index e33cd92ffef..e1145f9f138 100644 --- a/network-api/networkapi/nav/blocks.py +++ b/network-api/networkapi/nav/blocks.py @@ -70,7 +70,7 @@ class Meta: value_class = NavItemValue label = "Featured Navigation Link" icon = "link" - template = "nav/blocks/featured_item_block.html" + template = "fragments/blocks/nav/featured-item.html" register(BaseLinkBlockAdapter(), NavFeaturedItem) @@ -81,7 +81,7 @@ class Meta: value_class = NavItemValue label = "Navigation Button" icon = "link" - template = "nav/blocks/nav_button_block.html" + template = "fragments/blocks/nav/button.html" register(BaseLinkBlockAdapter(), NavButton) @@ -93,7 +93,7 @@ def has_button(self) -> bool: return bool(self.get("button")) @property - def button(self) -> NavButton | None: + def button_value(self) -> NavButton | None: button = self.get("button") if button: return button[0] @@ -117,7 +117,7 @@ class NavColumn(blocks.StructBlock): class Meta: label = "Navigation Column" icon = "list-ul" - template = "nav/blocks/nav_column_block.html" + template = "fragments/blocks/nav/column.html" value_class = NavColumnValue @@ -129,7 +129,7 @@ class NavFeaturedColumn(blocks.StructBlock): class Meta: label = "Featured Navigation Column" icon = "list-ul" - template = "nav/blocks/featured_column_block.html" + template = "fragments/blocks/nav/featured-column.html" value_class = NavColumnValue @@ -140,7 +140,7 @@ class NavOverview(blocks.StructBlock): class Meta: label = "Navigation Overview" icon = "pilcrow" - template = "nav/blocks/overview_block.html" + template = "fragments/blocks/nav/overview.html" class NavDropdownValue(blocks.StructValue): @@ -149,7 +149,7 @@ def has_overview(self) -> bool: return bool(self.get("overview")) @property - def overview(self) -> NavOverview | None: + def overview_value(self) -> NavOverview | None: overview = self.get("overview") if overview: return overview[0] @@ -160,7 +160,7 @@ def has_button(self) -> bool: return bool(self.get("button")) @property - def button(self) -> NavButton | None: + def button_value(self) -> NavButton | None: button = self.get("button") if button: return button[0] @@ -171,12 +171,21 @@ def has_featured_column(self) -> bool: return bool(self.get("featured_column")) @property - def featured_column(self) -> NavFeaturedColumn | None: + def featured_column_value(self) -> NavFeaturedColumn | None: featured_column = self.get("featured_column") if featured_column: return featured_column[0] return None + @property + def ncols(self) -> int: + ncols = len(self.get("columns")) + if self.has_overview: + ncols += 1 + if self.has_featured_column: + ncols += 1 + return ncols + class NavDropdown(blocks.StructBlock): title = blocks.CharBlock(max_length=100, help_text="How the dropdown menu will be labelled in the nav bar") @@ -285,5 +294,5 @@ def clean(self, value): class Meta: label = "Navigation Dropdown" icon = "bars" - template = "nav/blocks/nav_dropdown_block.html" + template = "fragments/blocks/nav/dropdown.html" value_class = NavDropdownValue diff --git a/network-api/networkapi/nav/models.py b/network-api/networkapi/nav/models.py index 6e0bb10ef11..461d2cd2a70 100644 --- a/network-api/networkapi/nav/models.py +++ b/network-api/networkapi/nav/models.py @@ -53,6 +53,8 @@ def __str__(self) -> str: @register_setting class SiteNavMenu(BaseSiteSetting): + select_related = ["active_nav_menu"] + active_nav_menu = models.ForeignKey( "nav.NavMenu", null=True, diff --git a/network-api/networkapi/nav/tests/test_blocks.py b/network-api/networkapi/nav/tests/test_blocks.py index 724f3afa30a..5fc5951ebc9 100644 --- a/network-api/networkapi/nav/tests/test_blocks.py +++ b/network-api/networkapi/nav/tests/test_blocks.py @@ -1,3 +1,4 @@ +import wagtail_factories from django.test import TestCase from wagtail.blocks import StreamBlockValidationError, StructBlockValidationError from wagtail.models import Locale, Page @@ -138,9 +139,9 @@ def test_default(self): for link in block["nav_items"]: self.assertIsInstance(link.block, nav_blocks.NavItem) self.assertIsInstance(link, nav_blocks.NavItemValue) - self.assertIsNotNone(block.button) + self.assertIsNotNone(block.button_value) self.assertEqual(len(block["button"]), 1) - self.assertEqual(block.button, block["button"][0]) + self.assertEqual(block.button_value, block["button"][0]) self.assertTrue(block.has_button) def test_without_button(self): @@ -149,7 +150,7 @@ def test_without_button(self): self.assertEqual(len(block["button"]), 0) self.assertFalse(block.has_button) - self.assertIsNone(block.button) + self.assertIsNone(block.button_value) def test_with_variable_number_of_links(self): """Create a nav_blocks.NavColumn with links.""" @@ -247,7 +248,7 @@ def test_default_block_factory(self): self.assertEqual(len(block["overview"]), 0) self.assertFalse(block.has_overview) - self.assertIsNone(block.overview) + self.assertIsNone(block.overview_value) self.assertEqual(len(block["columns"]), 4) for column in block["columns"]: @@ -256,12 +257,14 @@ def test_default_block_factory(self): self.assertEqual(len(block["featured_column"]), 0) self.assertFalse(block.has_featured_column) - self.assertIsNone(block.featured_column) + self.assertIsNone(block.featured_column_value) self.assertEqual(len(block["button"]), 1) self.assertIsInstance(block["button"][0].block, nav_blocks.NavButton) self.assertTrue(block.has_button) - self.assertEqual(block.button, block["button"][0]) + self.assertEqual(block.button_value, block["button"][0]) + + self.assertEqual(block.ncols, 4) def test_block_with_overview(self): """Create a nav_blocks.NavDropdown with an overview.""" @@ -270,10 +273,12 @@ def test_block_with_overview(self): self.assertEqual(len(block["overview"]), 1) self.assertTrue(block.has_overview) - self.assertEqual(block.overview, block["overview"][0]) + self.assertEqual(block.overview_value, block["overview"][0]) self.assertEqual(len(block["columns"]), 3) + self.assertEqual(block.ncols, 4) + def test_block_with_featured_column(self): """Create a nav_blocks.NavDropdown with a featured column.""" block = nav_factories.NavDropdownFactory(with_featured_column=True) @@ -281,10 +286,12 @@ def test_block_with_featured_column(self): self.assertEqual(len(block["featured_column"]), 1) self.assertTrue(block.has_featured_column) - self.assertEqual(block.featured_column, block["featured_column"][0]) + self.assertEqual(block.featured_column_value, block["featured_column"][0]) self.assertEqual(len(block["columns"]), 3) + self.assertEqual(block.ncols, 4) + def test_block_with_overview_and_featured_column(self): """Create a nav_blocks.NavDropdown with an overview and a featured column.""" block = nav_factories.NavDropdownFactory(with_overview_and_featured_column=True) @@ -292,14 +299,62 @@ def test_block_with_overview_and_featured_column(self): self.assertEqual(len(block["overview"]), 1) self.assertTrue(block.has_overview) - self.assertEqual(block.overview, block["overview"][0]) + self.assertEqual(block.overview_value, block["overview"][0]) self.assertEqual(len(block["featured_column"]), 1) self.assertTrue(block.has_featured_column) - self.assertEqual(block.featured_column, block["featured_column"][0]) + self.assertEqual(block.featured_column_value, block["featured_column"][0]) self.assertEqual(len(block["columns"]), 2) + self.assertEqual(block.ncols, 4) + + def test_number_of_cols_prop(self): + """Test the number of columns property.""" + block = nav_factories.NavDropdownFactory() + nav_blocks.NavDropdown().clean(block) + + self.assertEqual(block.ncols, 4) + + block = nav_factories.NavDropdownFactory( + columns=wagtail_factories.ListBlockFactory( + nav_factories.NavColumnFactory, + **{ + "0__title": "Column 1", + "1__title": "Column 2", + }, + ), + ) + nav_blocks.NavDropdown().clean(block) + self.assertEqual(block.ncols, 2) + + block = nav_factories.NavDropdownFactory( + columns=wagtail_factories.ListBlockFactory( + nav_factories.NavColumnFactory, + **{ + "0__title": "Column 1", + "1__title": "Column 2", + "2__title": "Column 3", + }, + ), + ) + nav_blocks.NavDropdown().clean(block) + self.assertEqual(block.ncols, 3) + + block = nav_factories.NavDropdownFactory( + columns=wagtail_factories.ListBlockFactory( + nav_factories.NavColumnFactory, + **{ + "0__title": "Column 1", + "1__title": "Column 2", + "2__title": "Column 3", + "3__title": "Column 4", + }, + ), + ) + nav_blocks.NavDropdown().clean(block) + self.assertEqual(block.ncols, 4) + def test_block_without_button(self): """Create a nav_blocks.NavDropdown without a button.""" block = nav_factories.NavDropdownFactory(no_button=True) @@ -307,7 +362,7 @@ def test_block_without_button(self): self.assertEqual(len(block["button"]), 0) self.assertFalse(block.has_button) - self.assertIsNone(block.button) + self.assertIsNone(block.button_value) def test_needs_at_least_one_column(self): with self.assertRaises(StructBlockValidationError): diff --git a/network-api/networkapi/project_styleguide/templatetags/wagtailImages_tags.py b/network-api/networkapi/project_styleguide/templatetags/wagtailImages_tags.py new file mode 100644 index 00000000000..0ec9a7298fa --- /dev/null +++ b/network-api/networkapi/project_styleguide/templatetags/wagtailImages_tags.py @@ -0,0 +1,4 @@ +from pattern_library.monkey_utils import override_tag +from wagtail.images.templatetags.wagtailimages_tags import register + +override_tag(register, name="image", default_html="") diff --git a/network-api/networkapi/project_styleguide/templatetags/wagtailcore_tags.py b/network-api/networkapi/project_styleguide/templatetags/wagtailcore_tags.py new file mode 100644 index 00000000000..558f0ec9c22 --- /dev/null +++ b/network-api/networkapi/project_styleguide/templatetags/wagtailcore_tags.py @@ -0,0 +1,6 @@ +from pattern_library.monkey_utils import override_tag +from wagtail.templatetags.wagtailcore_tags import register + +override_tag(register, name="include_block", default_html="") +override_tag(register, name="pageurl", default_html="/") +override_tag(register, name="slugurl", default_html="") diff --git a/network-api/networkapi/settings.py b/network-api/networkapi/settings.py index 89d76140c4b..9bb33245e6b 100644 --- a/network-api/networkapi/settings.py +++ b/network-api/networkapi/settings.py @@ -520,6 +520,8 @@ class DatabasesDict(TypedDict): WAGTAIL_USAGE_COUNT_ENABLED = True WAGTAIL_I18N_ENABLED = True +WAGTAILIMAGES_EXTENSIONS = ["avif", "gif", "jpg", "jpeg", "png", "webp", "svg"] + # Wagtail Frontend Cache Invalidator Settings if env("FRONTEND_CACHE_CLOUDFLARE_BEARER_TOKEN"): diff --git a/network-api/networkapi/templates/fragments/blocks/nav/button.html b/network-api/networkapi/templates/fragments/blocks/nav/button.html new file mode 100644 index 00000000000..49210c0098a --- /dev/null +++ b/network-api/networkapi/templates/fragments/blocks/nav/button.html @@ -0,0 +1,13 @@ +{% load static wagtailadmin_tags %} + +{% if style == "primary" %} + {% fragment as nav_button_style %}tw-btn-primary tw-py-4 tw-px-8{% endfragment %} +{% elif style == "primary-full-width" %} + {% fragment as nav_button_style %}tw-btn-primary tw-min-w-full tw-py-4 tw-px-8{% endfragment %} +{% elif style == "cta" %} + {% fragment as nav_button_style %}tw-leading-[130%] tw-text-blue-80 tw-p-2 hover:tw-underline{% endfragment %} +{% endif %} + +{% fragment as base_styles %}link-button tw-font-bold tw-text-lg{% endfragment %} + +{{ value.label }} diff --git a/network-api/networkapi/templates/fragments/blocks/nav/button.md b/network-api/networkapi/templates/fragments/blocks/nav/button.md new file mode 100644 index 00000000000..bc07cc6cdd2 --- /dev/null +++ b/network-api/networkapi/templates/fragments/blocks/nav/button.md @@ -0,0 +1,8 @@ +# Navigation Button + +A call-to-action to a page, relative URL or external URL. + +Takes one of the following `style` options: +* `primary` (for a solid primary button) +* `primary-full-width` (for a solid primary button with 100% width of the container%) +* `cta` (for blue text on white background) diff --git a/network-api/networkapi/templates/fragments/blocks/nav/button.yml b/network-api/networkapi/templates/fragments/blocks/nav/button.yml new file mode 100644 index 00000000000..2934097196b --- /dev/null +++ b/network-api/networkapi/templates/fragments/blocks/nav/button.yml @@ -0,0 +1,7 @@ +name: Navigation Button +context: + value: + label: "Learn more" + url: https://google.com + open_in_new_window: False + style: primary \ No newline at end of file diff --git a/network-api/networkapi/templates/fragments/blocks/nav/column.html b/network-api/networkapi/templates/fragments/blocks/nav/column.html new file mode 100644 index 00000000000..24a47b422a1 --- /dev/null +++ b/network-api/networkapi/templates/fragments/blocks/nav/column.html @@ -0,0 +1,16 @@ +{% load wagtailcore_tags wagtailadmin_tags %} + +{% fragment as base_styles %}tw-flex tw-flex-col tw-items-start tw-gap-8 tw-py-8{% endfragment %} +{% fragment as desktop_layout %}large:tw-py-22 large:tw-px-8 large:tw-ms-9 large:tw-col-span-1{% endfragment %} + +
+
{{ value.title }}
+
+ {% for item in value.nav_items %} +
{% include_block item %}
+ {% endfor %} + {% if value.has_button %} +
{% include_block value.button_value with style="cta" %}
+ {% endif %} +
+
diff --git a/network-api/networkapi/templates/fragments/blocks/nav/column.yml b/network-api/networkapi/templates/fragments/blocks/nav/column.yml new file mode 100644 index 00000000000..995c9bfe7b1 --- /dev/null +++ b/network-api/networkapi/templates/fragments/blocks/nav/column.yml @@ -0,0 +1,34 @@ +name: Navigation Column +context: + value: + title: "Research & Analysis" + nav_items: + - value: + label: "Trustworthy AI Progress Report" + description: "Promoting openness, competition, and accountability in AI" + url: /foo/bar + is_external: false + - value: + label: "Internet Health Report" + description: "Issues impacting a healthy internet" + url: /foo/bar + is_external: true + - value: + label: "AI Policy" + description: "Policy recommendations for AI" + url: /foo/bar + is_external: false + has_button: true + button: + value: + label: "See all research" + url: /foo/bar + is_external: false + style: "cta-link" + +tags: + include_block: + 'item': + template_name: "fragments/blocks/nav/item.html" + 'value.button with style="cta-link"': + template_name: "fragments/blocks/nav/variants/button--cta-link.html" \ No newline at end of file diff --git a/network-api/networkapi/templates/fragments/blocks/nav/dropdown.html b/network-api/networkapi/templates/fragments/blocks/nav/dropdown.html new file mode 100644 index 00000000000..a4cf6f4eb8e --- /dev/null +++ b/network-api/networkapi/templates/fragments/blocks/nav/dropdown.html @@ -0,0 +1,46 @@ +{% load static wagtailcore_tags wagtailadmin_tags %} + +{% fragment as title_base_typography %}tw-font-sans tw-font-bold tw-text-xl tw-capitalize tw-text-black{% endfragment %} +{% fragment as content_base_styles %}tw-py-0 tw-px-8 tw-gap-8 tw-overflow-y-auto{% endfragment %} +{% fragment as dropdown_selector_base %}tw-flex tw-flex-row tw-items-center tw-justify-between tw-w-full tw-py-12 tw-px-8 tw-gap-4{% endfragment %} + +{% fragment as content_desktop %}large:tw-container large:tw-hidden{% endfragment %} +{% fragment as content_desktop_border %}large:tw-border large:tw-border-gray-20{% endfragment %} +{% comment %} 5rem is the height of the navbar and 1px is the size of the dropdown's border {% endcomment %} +{% fragment as content_desktop_positioning %}large:tw-fixed large:tw-top-[calc(5rem+1px)] large:tw-left-0 large:tw-right-0 large:tw-mx-auto{% endfragment %} + +{% fragment as title_desktop_typography %}large:tw-text-lg large:tw-text-black{% endfragment %} + + +{% comment %} 2.5rem for half the navbar's height, 14px for half the title's line height, 6px for the title bottom border and 1 px for the dropdown border:{% endcomment %} +
+ + +
+
+ {% if value.has_overview %} + {% include_block value.overview_value with button=value.button_value %} + {% endif %} + {% for column in value.columns %} + {% include_block column %} + {% endfor %} + {% if value.has_featured_column %} + {% include_block value.featured_column_value %} + {% endif %} + {% comment %} + If there is a button but no overview, we want to render the button at the bottom + of the dropdown. Otherwise it will be rendered together with the overview. + {% endcomment %} + {% if value.has_button and not value.has_overview %} +
+ {% include_block value.button_value with style="primary-full-width" %} +
+ {% endif %} +
+
+
diff --git a/network-api/networkapi/templates/fragments/blocks/nav/dropdown.md b/network-api/networkapi/templates/fragments/blocks/nav/dropdown.md new file mode 100644 index 00000000000..e0dcb9e1ed4 --- /dev/null +++ b/network-api/networkapi/templates/fragments/blocks/nav/dropdown.md @@ -0,0 +1,3 @@ +# Navigation Dropdown + +A collection of navigation columns. \ No newline at end of file diff --git a/network-api/networkapi/templates/fragments/blocks/nav/dropdown.yml b/network-api/networkapi/templates/fragments/blocks/nav/dropdown.yml new file mode 100644 index 00000000000..7024dec14ad --- /dev/null +++ b/network-api/networkapi/templates/fragments/blocks/nav/dropdown.yml @@ -0,0 +1,29 @@ +name: Navigation Dropdown +context: + value: + title: "Who We Are" + has_overview: True + overview: + title: About Us + description: "Mozilla is a global nonprofit dedicated to keeping the Internet a public resource that is open and accessible to all." + columns: + - column: + - column: + has_featured_column: true + featured_column: + - featured_column: + has_button: true + button: + - button: + style: mobile + +tags: + include_block: + 'value.overview|first with button=value.button|first': + template_name: "fragments/blocks/nav/overview.html" + 'column': + template_name: "fragments/blocks/nav/column.html" + 'value.button|first with style="primary-full-width"': + template_name: "fragments/blocks/nav/variants/button--primary-full-width.html" + 'value.featured_column|first': + template_name: "fragments/blocks/nav/featured-column.html" \ No newline at end of file diff --git a/network-api/networkapi/templates/fragments/blocks/nav/featured-column.html b/network-api/networkapi/templates/fragments/blocks/nav/featured-column.html new file mode 100644 index 00000000000..1021444831f --- /dev/null +++ b/network-api/networkapi/templates/fragments/blocks/nav/featured-column.html @@ -0,0 +1,13 @@ +{% load wagtailcore_tags wagtailadmin_tags %} + +{% fragment as base_styles %}tw-flex tw-flex-col tw-items-start tw-gap-12 tw-py-8 tw-px-12 tw-bg-gray-02 tw-rounded-2xl{% endfragment %} +{% fragment as desktop_layout %}large:tw-py-22 large:tw-px-8 large:tw-ms-9 large:tw-rounded-none large:tw-col-span-1{% endfragment %} + +
+
{{ value.title }}
+
+ {% for item in value.nav_items %} +
{% include_block item %}
+ {% endfor %} +
+
diff --git a/network-api/networkapi/templates/fragments/blocks/nav/featured-column.yml b/network-api/networkapi/templates/fragments/blocks/nav/featured-column.yml new file mode 100644 index 00000000000..85009e26f87 --- /dev/null +++ b/network-api/networkapi/templates/fragments/blocks/nav/featured-column.yml @@ -0,0 +1,18 @@ +name: Navigation Featured Column +context: + value: + title: "Donate" + nav_items: + - value: + label: "Make a Donation" + url: /foo/bar + is_external: false + - value: + label: "Ways to Give" + url: /foo/bar + is_external: true + +tags: + include_block: + 'item': + template_name: "fragments/blocks/nav/featured-item.html" diff --git a/network-api/networkapi/templates/fragments/blocks/nav/featured-item.html b/network-api/networkapi/templates/fragments/blocks/nav/featured-item.html new file mode 100644 index 00000000000..0cd1fd1b7d5 --- /dev/null +++ b/network-api/networkapi/templates/fragments/blocks/nav/featured-item.html @@ -0,0 +1,12 @@ +{% load static i18n wagtailadmin_tags wagtailimages_tags %} + +{% fragment as font_styles %}tw-text-xl large:tw-text-lg tw-font-semibold tw-text-black{% endfragment %} +{% fragment as hover_styles %}group-hover/item:tw-text-blue-80 group-hover/item:tw-underline{% endfragment %} + +
+ {% image value.icon fill-18x18 preserve-svg %} + + {{ value.label }} + +
\ No newline at end of file diff --git a/network-api/networkapi/templates/fragments/blocks/nav/featured-item.md b/network-api/networkapi/templates/fragments/blocks/nav/featured-item.md new file mode 100644 index 00000000000..156502d91f8 --- /dev/null +++ b/network-api/networkapi/templates/fragments/blocks/nav/featured-item.md @@ -0,0 +1,3 @@ +# Navigation Featured Item + +A featured item in the navigation menu, holding a title, an icon and a link to a resource. \ No newline at end of file diff --git a/network-api/networkapi/templates/fragments/blocks/nav/featured-item.yml b/network-api/networkapi/templates/fragments/blocks/nav/featured-item.yml new file mode 100644 index 00000000000..abdb6e00fb6 --- /dev/null +++ b/network-api/networkapi/templates/fragments/blocks/nav/featured-item.yml @@ -0,0 +1,13 @@ +name: Navigation Featured Item +context: + value: + label: "Make a Donation" + icon: + url: /donate + is_external: True + +tags: + image: + value.icon fill-18x18 preserve-svg: + raw: + '' \ No newline at end of file diff --git a/network-api/networkapi/templates/fragments/blocks/nav/item.html b/network-api/networkapi/templates/fragments/blocks/nav/item.html index 36b93c94e84..73794f86c4c 100644 --- a/network-api/networkapi/templates/fragments/blocks/nav/item.html +++ b/network-api/networkapi/templates/fragments/blocks/nav/item.html @@ -1,11 +1,10 @@ {% load static i18n wagtailadmin_tags %} -{% fragment as font_styles %}tw-text-xl tw-font-semibold tw-capitalize tw-text-black{% endfragment %} -{% fragment as hover_styles %}group-hover:tw-text-blue-80 group-hover:tw-underline{% endfragment %} -{% fragment as after_arrow_styles %}after:tw-content-['_↗'] after:tw-font-['Nunito_Sans'] after:tw-text-gray-20 group-hover:after:tw-text-blue-80{% endfragment %} +{% fragment as font_styles %}tw-text-xl large:tw-text-lg tw-font-semibold tw-text-black{% endfragment %} +{% fragment as hover_styles %}group-hover/item:tw-text-blue-80 group-hover/item:tw-underline{% endfragment %} -
- + {{ value.label }} diff --git a/network-api/networkapi/templates/fragments/blocks/nav/overview.html b/network-api/networkapi/templates/fragments/blocks/nav/overview.html new file mode 100644 index 00000000000..1e4818e194b --- /dev/null +++ b/network-api/networkapi/templates/fragments/blocks/nav/overview.html @@ -0,0 +1,15 @@ +{% load wagtailcore_tags wagtailadmin_tags %} + +{% fragment as base_styles %}tw-flex tw-flex-col tw-items-start tw-gap-14 tw-pb-16{% endfragment %} +{% fragment as border_styles %}tw-border-gray-20 tw-border-b-[1px] large:tw-border-b-0 large:tw-border-r-[1px]{% endfragment %} +{% fragment as desktop_layout %}large:tw-col-span-1 large:tw-my-22 large:tw-pb-0 large:tw-ms-8 large:tw-ps-12 large:tw-pe-8 large:tw-border-r{% endfragment %} + +
+
+

{{ value.title }}

+
{{ value.description|richtext }}
+
+ {% if button %} + {% include_block button with style="primary" %} + {% endif %} +
diff --git a/network-api/networkapi/templates/fragments/blocks/nav/overview.md b/network-api/networkapi/templates/fragments/blocks/nav/overview.md new file mode 100644 index 00000000000..243e69d76d9 --- /dev/null +++ b/network-api/networkapi/templates/fragments/blocks/nav/overview.md @@ -0,0 +1,5 @@ +# Navigation Overview + +A block with a title and a description for the navigation dropdown menu. + +Takes an optional "button" block property, which will render the button block as part of the overview block. \ No newline at end of file diff --git a/network-api/networkapi/templates/fragments/blocks/nav/overview.yml b/network-api/networkapi/templates/fragments/blocks/nav/overview.yml new file mode 100644 index 00000000000..b2cbbacd4f6 --- /dev/null +++ b/network-api/networkapi/templates/fragments/blocks/nav/overview.yml @@ -0,0 +1,11 @@ +name: Navigation Overview +context: + value: + title: About Us + description: Mozilla is a global nonprofit dedicated to keeping the Internet a public resource that is open and accessible to all. + button: True + +tags: + include_block: + 'button with style="primary"': + template_name: "fragments/blocks/nav/button.html" \ No newline at end of file diff --git a/network-api/networkapi/templates/fragments/blocks/nav/variants/button--cta.html b/network-api/networkapi/templates/fragments/blocks/nav/variants/button--cta.html new file mode 100644 index 00000000000..9a30602631f --- /dev/null +++ b/network-api/networkapi/templates/fragments/blocks/nav/variants/button--cta.html @@ -0,0 +1 @@ +{% include "fragments/blocks/nav/button.html" with style="cta" %} \ No newline at end of file diff --git a/network-api/networkapi/templates/fragments/blocks/nav/variants/button--primary-full-width.html b/network-api/networkapi/templates/fragments/blocks/nav/variants/button--primary-full-width.html new file mode 100644 index 00000000000..0e4cf53e041 --- /dev/null +++ b/network-api/networkapi/templates/fragments/blocks/nav/variants/button--primary-full-width.html @@ -0,0 +1 @@ +{% include "fragments/blocks/nav/button.html" with style="primary-full-width" %} \ No newline at end of file diff --git a/network-api/networkapi/templates/fragments/nav/menu.html b/network-api/networkapi/templates/fragments/nav/menu.html new file mode 100644 index 00000000000..11601835960 --- /dev/null +++ b/network-api/networkapi/templates/fragments/nav/menu.html @@ -0,0 +1,29 @@ +{% extends "fragments/primary_nav.html" %} + +{% load i18n wagtailcore_tags wagtailadmin_tags %} + +{% block menu_container_classes %} + {{ block.super }} + large:tw-py-0 tw-bg-white tw-relative +{% endblock menu_container_classes %} + +{% block narrow_screen_menu %} +
+
+ {% for dropdown in menu.dropdowns %} + {% include_block dropdown with style="mobile" %} + {% endfor %} +
+
+ +
+
+{% endblock narrow_screen_menu %} + +{% block wide_screen_menu %} +
+ {% for dropdown in menu.dropdowns %} + {% include_block dropdown with style="desktop" %} + {% endfor %} +
+{% endblock wide_screen_menu %} \ No newline at end of file diff --git a/network-api/networkapi/templates/fragments/nav/menu.yml b/network-api/networkapi/templates/fragments/nav/menu.yml new file mode 100644 index 00000000000..9949b395b83 --- /dev/null +++ b/network-api/networkapi/templates/fragments/nav/menu.yml @@ -0,0 +1,12 @@ +name: Navigation Menu +context: + menu: + dropdowns: + - dropdown + - dropdown + - dropdown + +tags: + include_block: + 'dropdown': + template_name: "fragments/blocks/nav/dropdown.html" diff --git a/network-api/networkapi/templates/fragments/primary_nav.html b/network-api/networkapi/templates/fragments/primary_nav.html index 991266ebd69..79c9d6c04e8 100644 --- a/network-api/networkapi/templates/fragments/primary_nav.html +++ b/network-api/networkapi/templates/fragments/primary_nav.html @@ -4,15 +4,15 @@ {% else %}
{% endif %}
-