diff --git a/network-api/networkapi/mozfest/migrations/0021_add_spaces_cards.py b/network-api/networkapi/mozfest/migrations/0021_add_spaces_cards.py new file mode 100644 index 00000000000..b493258be23 --- /dev/null +++ b/network-api/networkapi/mozfest/migrations/0021_add_spaces_cards.py @@ -0,0 +1,23 @@ +# Generated by Django 3.1.11 on 2021-10-06 19:51 + +from django.db import migrations +import networkapi.wagtailpages.pagemodels.blog.blog_category +import wagtail.core.blocks +import wagtail.core.blocks.static_block +import wagtail.core.fields +import wagtail.images.blocks + + +class Migration(migrations.Migration): + + dependencies = [ + ('mozfest', '0020_auto_20210910_2042'), + ] + + operations = [ + migrations.AlterField( + model_name='mozfestprimarypage', + name='body', + field=wagtail.core.fields.StreamField([('paragraph', wagtail.core.blocks.RichTextBlock(features=['bold', 'italic', 'large', 'h2', 'h3', 'h4', 'h5', 'ol', 'ul', 'link', 'hr'], template='wagtailpages/blocks/rich_text_block.html')), ('card_grid', wagtail.core.blocks.StructBlock([('cards', wagtail.core.blocks.ListBlock(wagtail.core.blocks.StructBlock([('image', wagtail.images.blocks.ImageChooserBlock()), ('alt_text', wagtail.core.blocks.CharBlock(help_text="Alt text for card's image.", required=False)), ('title', wagtail.core.blocks.CharBlock(help_text='Heading for the card.')), ('body', wagtail.core.blocks.TextBlock(help_text='Body text of the card.')), ('link_url', wagtail.core.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.core.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.core.blocks.StructBlock([('image', wagtail.images.blocks.ImageChooserBlock()), ('altText', wagtail.core.blocks.CharBlock(help_text='Image description (for screen readers).', required=True)), ('caption', wagtail.core.blocks.CharBlock(required=False)), ('captionURL', wagtail.core.blocks.CharBlock(help_text='Optional URL that this caption should link out to.', required=False)), ('image_width', wagtail.core.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.core.blocks.StructBlock([('image', wagtail.images.blocks.ImageChooserBlock()), ('altText', wagtail.core.blocks.CharBlock(help_text='Image description (for screen readers).', required=True)), ('text', wagtail.core.blocks.RichTextBlock(features=['bold', 'italic', 'h2', 'h3', 'h4', 'h5', 'h6', 'ol', 'ul', 'link'])), ('url', wagtail.core.blocks.CharBlock(help_text='Optional URL that this image should link out to.', required=False)), ('top_divider', wagtail.core.blocks.BooleanBlock(help_text='Optional divider above content block.', required=False)), ('bottom_divider', wagtail.core.blocks.BooleanBlock(help_text='Optional divider below content block.', required=False))])), ('image_text_mini', wagtail.core.blocks.StructBlock([('image', wagtail.images.blocks.ImageChooserBlock()), ('altText', wagtail.core.blocks.CharBlock(help_text='Image description (for screen readers).', required=True)), ('text', wagtail.core.blocks.RichTextBlock(features=['bold', 'italic', 'link']))])), ('image_grid', wagtail.core.blocks.StructBlock([('grid_items', wagtail.core.blocks.ListBlock(wagtail.core.blocks.StructBlock([('image', wagtail.images.blocks.ImageChooserBlock()), ('alt_text', wagtail.core.blocks.CharBlock(help_text='Alt text for this image.', required=False)), ('caption', wagtail.core.blocks.CharBlock(help_text='Please remember to properly attribute any images we use.', required=False)), ('url', wagtail.core.blocks.CharBlock(help_text='Optional URL that this figure should link out to.', required=False)), ('square_image', wagtail.core.blocks.BooleanBlock(default=True, help_text='If left checked, the image will be cropped to be square.', required=False))])))])), ('video', wagtail.core.blocks.StructBlock([('url', wagtail.core.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')), ('caption', wagtail.core.blocks.CharBlock(required=False)), ('captionURL', wagtail.core.blocks.CharBlock(help_text='Optional URL for caption to link to.', required=False)), ('video_width', wagtail.core.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.core.blocks.StructBlock([('url', wagtail.core.blocks.CharBlock(help_text='Please note that only URLs from allow-listed domains will work.')), ('height', wagtail.core.blocks.IntegerBlock(help_text='Optional integer pixel value for custom iFrame height', required=False)), ('caption', wagtail.core.blocks.CharBlock(required=False)), ('captionURL', wagtail.core.blocks.CharBlock(help_text='Optional URL that this caption should link out to.', required=False)), ('iframe_width', wagtail.core.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'))])), ('linkbutton', wagtail.core.blocks.StructBlock([('label', wagtail.core.blocks.CharBlock()), ('URL', wagtail.core.blocks.CharBlock()), ('styling', wagtail.core.blocks.ChoiceBlock(choices=[('btn-primary', 'Primary button'), ('btn-secondary', 'Secondary button')]))])), ('spacer', wagtail.core.blocks.StructBlock([('size', wagtail.core.blocks.ChoiceBlock(choices=[('1', 'quarter spacing'), ('2', 'half spacing'), ('3', 'single spacing'), ('4', 'one and a half spacing'), ('5', 'triple spacing')]))])), ('quote', wagtail.core.blocks.StructBlock([('quotes', wagtail.core.blocks.ListBlock(wagtail.core.blocks.StructBlock([('quote', wagtail.core.blocks.CharBlock()), ('attribution', wagtail.core.blocks.CharBlock(required=False))])))])), ('pulse_listing', wagtail.core.blocks.StructBlock([('search_terms', wagtail.core.blocks.CharBlock(help_text='Test your search at mozillapulse.org/search', label='Search', required=False)), ('max_number_of_results', wagtail.core.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.core.blocks.BooleanBlock(default=False, help_text='Featured items are selected by Pulse moderators.', label='Display only featured entries', required=False)), ('newest_first', wagtail.core.blocks.ChoiceBlock(choices=[('True', 'Show newer entries first'), ('False', 'Show older entries first')], label='Sort')), ('advanced_filter_header', wagtail.core.blocks.static_block.StaticBlock(admin_text='-------- ADVANCED FILTERS: OPTIONS TO DISPLAY FEWER, MORE TARGETED RESULTS. --------', label=' ')), ('issues', wagtail.core.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.core.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.core.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.core.blocks.StructBlock([('max_number_of_results', wagtail.core.blocks.IntegerBlock(default=12, help_text='Pick up to 48 profiles.', max_value=48, min_value=1, required=True)), ('advanced_filter_header', wagtail.core.blocks.static_block.StaticBlock(admin_text='-------- ADVANCED FILTERS: OPTIONS TO DISPLAY FEWER, MORE TARGETED RESULTS. --------', label=' ')), ('profile_type', wagtail.core.blocks.CharBlock(default='', help_text='Example: Fellow.', required=False)), ('program_type', wagtail.core.blocks.CharBlock(default='', help_text='Example: Tech Policy.', required=False)), ('year', wagtail.core.blocks.CharBlock(default='', required=False))])), ('profile_by_id', wagtail.core.blocks.StructBlock([('ids', wagtail.core.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.core.blocks.StructBlock([('max_number_of_results', wagtail.core.blocks.IntegerBlock(default=12, help_text='Pick up to 48 profiles.', max_value=48, min_value=1, required=True)), ('advanced_filter_header', wagtail.core.blocks.static_block.StaticBlock(admin_text='-------- ADVANCED FILTERS: OPTIONS TO DISPLAY FEWER, MORE TARGETED RESULTS. --------', label=' ')), ('profile_type', wagtail.core.blocks.CharBlock(default='', help_text='Example: Fellow.', required=False)), ('program_type', wagtail.core.blocks.CharBlock(default='', help_text='Example: Tech Policy.', required=False)), ('year', wagtail.core.blocks.CharBlock(default='', required=False)), ('filter_values', wagtail.core.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.core.blocks.StructBlock([('title', wagtail.core.blocks.CharBlock(required=False)), ('tag_filter', wagtail.core.blocks.CharBlock(help_text='Test this filter at foundation.mozilla.org/blog/tags/', label='Filter by Tag', required=False)), ('category_filter', wagtail.core.blocks.ChoiceBlock(choices=networkapi.wagtailpages.pagemodels.blog.blog_category.BlogPageCategory.get_categories, help_text='Test this filter at foundation.mozilla.org/blog/category/', label='Filter by Category', required=False)), ('top_divider', wagtail.core.blocks.BooleanBlock(help_text='Optional divider above content block.', required=False)), ('bottom_divider', wagtail.core.blocks.BooleanBlock(help_text='Optional divider below content block.', required=False))])), ('blog_set', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.CharBlock()), ('top_divider', wagtail.core.blocks.BooleanBlock(help_text='Optional divider above content block.', required=False)), ('bottom_divider', wagtail.core.blocks.BooleanBlock(help_text='Optional divider below content block.', required=False)), ('blog_pages', wagtail.core.blocks.ListBlock(wagtail.core.blocks.PageChooserBlock(page_type=['wagtailpages.BlogPage'])))])), ('airtable', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock(help_text="Copied from the Airtable embed code. The word 'embed' will be in the url")), ('height', wagtail.core.blocks.IntegerBlock(default=533, help_text='The pixel height on desktop view, usually copied from the Airtable embed code'))])), ('typeform', wagtail.core.blocks.StructBlock([('embed_id', wagtail.core.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.core.blocks.ChoiceBlock(choices=[('btn-primary', 'Primary button'), ('btn-secondary', 'Secondary button')])), ('button_text', wagtail.core.blocks.CharBlock(help_text='This is a text prompt for users to open the typeform content', required=True))])), ('space_card_list', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.CharBlock()), ('space_cards', wagtail.core.blocks.ListBlock(wagtail.core.blocks.StructBlock([('image', wagtail.images.blocks.ImageChooserBlock()), ('title', wagtail.core.blocks.CharBlock(help_text='Heading for the card.')), ('body', wagtail.core.blocks.TextBlock(help_text='Body text of the card.')), ('link', wagtail.core.blocks.StreamBlock([('internal', wagtail.core.blocks.StructBlock([('link', wagtail.core.blocks.PageChooserBlock(help_text='Page that this should link out to.'))])), ('external', wagtail.core.blocks.StructBlock([('link', wagtail.core.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 Space Cards.'))]))]), + ), + ] diff --git a/network-api/networkapi/mozfest/models.py b/network-api/networkapi/mozfest/models.py index ad8ae64977a..6691e4bf3ac 100644 --- a/network-api/networkapi/mozfest/models.py +++ b/network-api/networkapi/mozfest/models.py @@ -19,6 +19,8 @@ Signup ) +from networkapi.wagtailpages.pagemodels import customblocks + class MozfestPrimaryPage(FoundationMetadataPageMixin, FoundationBannerInheritanceMixin, Page): header = models.CharField( @@ -50,7 +52,11 @@ class MozfestPrimaryPage(FoundationMetadataPageMixin, FoundationBannerInheritanc help_text='Choose an existing, or create a new, sign-up form' ) - body = StreamField(base_fields) + body = StreamField( + base_fields + [ + ('space_card_list', customblocks.SpaceCardListBlock()), + ] + ) content_panels = Page.content_panels + [ FieldPanel('header'), diff --git a/network-api/networkapi/wagtailpages/pagemodels/customblocks/__init__.py b/network-api/networkapi/wagtailpages/pagemodels/customblocks/__init__.py index 9c2c7606f41..eefdbdf9d6c 100644 --- a/network-api/networkapi/wagtailpages/pagemodels/customblocks/__init__.py +++ b/network-api/networkapi/wagtailpages/pagemodels/customblocks/__init__.py @@ -20,6 +20,7 @@ from .typeform_block import TypeformBlock from .quote_block import QuoteBlock from .single_quote_block import SingleQuoteBlock +from .space_card_list_block import SpaceCardListBlock from .video_block import VideoBlock from .youtube_regret_block import YoutubeRegretBlock from .articles import ArticleRichText, ArticleDoubleImageBlock, ArticleFullWidthImageBlock, ArticleImageBlock @@ -54,6 +55,7 @@ PulseProjectList, QuoteBlock, SingleQuoteBlock, + SpaceCardListBlock, RecentBlogEntries, TypeformBlock, VideoBlock, diff --git a/network-api/networkapi/wagtailpages/pagemodels/customblocks/common/__init__.py b/network-api/networkapi/wagtailpages/pagemodels/customblocks/common/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/network-api/networkapi/wagtailpages/pagemodels/customblocks/common/link_blocks.py b/network-api/networkapi/wagtailpages/pagemodels/customblocks/common/link_blocks.py new file mode 100644 index 00000000000..d2b177c93d8 --- /dev/null +++ b/network-api/networkapi/wagtailpages/pagemodels/customblocks/common/link_blocks.py @@ -0,0 +1,61 @@ +""" +These are utility blocks used for StructBlocks related to MozFest. +""" + +from wagtail.core import blocks +from wagtail.documents.blocks import DocumentChooserBlock + + +class InternalLinkBlockStructValue(blocks.StructValue): + @property + def link_url(self): + return self.get('link').url + + +class ExternalLinkBlockStructValue(blocks.StructValue): + @property + def link_url(self): + return self.get('link') + + +class DocumentLinkBlockStructValue(blocks.StructValue): + @property + def link_url(self): + return self.get('document').url + + +class InternalLinkBlock(blocks.StructBlock): + link = blocks.PageChooserBlock(help_text='Page that this should link out to.') + + class Meta: + value_class = InternalLinkBlockStructValue + + +class ExternalLinkBlock(blocks.StructBlock): + link = blocks.URLBlock(help_text='URL that this should link out to.') + + class Meta: + value_class = ExternalLinkBlockStructValue + + +class DocumentLinkBlock(blocks.StructBlock): + document = DocumentChooserBlock(help_text='Document that this should link out to.') + + class Meta: + value_class = DocumentLinkBlockStructValue + + +class LabelledLinkBaseBlock(blocks.StructBlock): + label = blocks.CharBlock(help_text='Label for this link.') + + +class LabelledInternalLinkBlock(InternalLinkBlock, LabelledLinkBaseBlock): + pass + + +class LabelledExternalLinkBlock(ExternalLinkBlock, LabelledLinkBaseBlock): + pass + + +class LabelledDocumentLinkBlock(DocumentLinkBlock, LabelledLinkBaseBlock): + pass diff --git a/network-api/networkapi/wagtailpages/pagemodels/customblocks/space_card_list_block.py b/network-api/networkapi/wagtailpages/pagemodels/customblocks/space_card_list_block.py new file mode 100644 index 00000000000..25f2adcd658 --- /dev/null +++ b/network-api/networkapi/wagtailpages/pagemodels/customblocks/space_card_list_block.py @@ -0,0 +1,39 @@ +from wagtail.core import blocks +from wagtail.images.blocks import ImageChooserBlock + +from .common.link_blocks import InternalLinkBlock, ExternalLinkBlock + + +class SpaceCardBlock(blocks.StructBlock): + image = ImageChooserBlock() + + title = blocks.CharBlock( + help_text='Heading for the card.' + ) + + body = blocks.TextBlock( + help_text='Body text of the card.' + ) + + link = blocks.StreamBlock( + [ + ('internal', InternalLinkBlock()), + ('external', ExternalLinkBlock()), + ], + help_text='Page or external URL this card will link out to.', + max_num=1, + ) + + class Meta: + icon = 'form' + label = 'Space Card' + + +class SpaceCardListBlock(blocks.StructBlock): + title = blocks.CharBlock() + + space_cards = blocks.ListBlock(SpaceCardBlock(), help_text='A list of Space Cards.') + + class Meta: + icon = 'placeholder' + template = 'wagtailpages/blocks/space_card_list_block.html' diff --git a/network-api/networkapi/wagtailpages/templates/wagtailpages/blocks/space_card_list_block.html b/network-api/networkapi/wagtailpages/templates/wagtailpages/blocks/space_card_list_block.html new file mode 100644 index 00000000000..9478cdaf93d --- /dev/null +++ b/network-api/networkapi/wagtailpages/templates/wagtailpages/blocks/space_card_list_block.html @@ -0,0 +1,29 @@ +{% load wagtailcore_tags wagtailimages_tags %} + +
+

{{ value.title }}

+ +
+ {% for space in self.space_cards %} + {# Space Card #} + + +
+ {{ space.title }} +

{{ space.body }}

+
+ +
+ {% image space.image fill-158x158 as img_desktop %} + {% image space.image fill-290x125 as img_mobile %} + + + + +
+ +
+ {% endfor %} +
+