class StoryBlock(blocks.StreamBlock): heading = blocks.CharBlock(classname="full title") body_text = BodyTextBlock() large_text = LargeTextBlock() extendable_body = ExtendableBodyTextBlock() image = ImageBlock() quote = QuoteListBlock() video = YouTubeEmbed(label="Girl Effect YouTube Video") slider = SliderBlock() carousel_block = CarouselBlock(min_num=2, max_num=3, label="Carousel") media_text_overlay = MediaTextOverlayBlock( label="Full Width Media with Text Overlay") image_text_overlay = SnippetChooserBlock( FullWidthMediaAndTextSnippet, template="includes/fw_media.html", label="Full Width Media with Text Overlay Snippet", ) list_block = ListColumnBlock() link_row = blocks.ListBlock(LinkBlock(), template="blocks/inline_link_block.html", icon="link") anchor = AnchorBlock() statistic = StatisticBlock(label="Statistic Block") call_to_action = SnippetChooserBlock(CallToActionSnippet, template="blocks/call_to_action.html") class Meta: template = "blocks/stream_block.html"
def test_serialize(self): """The value of a SnippetChooserBlock (a snippet instance) should serialize to an ID""" block = SnippetChooserBlock(Advert) test_advert = Advert.objects.get(text='test_advert') self.assertEqual(block.get_prep_value(test_advert), test_advert.id) # None should serialize to None self.assertEqual(block.get_prep_value(None), None)
def test_form_response(self): block = SnippetChooserBlock(Advert) test_advert = Advert.objects.get(text='test_advert') value = block.value_from_datadict({'advert': str(test_advert.id)}, {}, 'advert') self.assertEqual(value, test_advert) empty_value = block.value_from_datadict({'advert': ''}, {}, 'advert') self.assertEqual(empty_value, None)
def test_deserialize(self): """The serialized value of a SnippetChooserBlock (an ID) should deserialize to a snippet instance""" block = SnippetChooserBlock(Advert) test_advert = Advert.objects.get(text='test_advert') self.assertEqual(block.to_python(test_advert.id), test_advert) # None should deserialize to None self.assertEqual(block.to_python(None), None)
class TestimonialsBlock(StructBlock): first_testimonial = SnippetChooserBlock(target_model=Testimonial, required=True) second_testimonial = SnippetChooserBlock(target_model=Testimonial, required=True) third_testimonial = SnippetChooserBlock(target_model=Testimonial, required=True) class Meta: image = 'pages/images/streamfield_blocks/testimonials.jpg' template = 'pages/streamfield_blocks/testimonials.html'
def test_form_render(self): block = SnippetChooserBlock(Advert, help_text="pick an advert, any advert") empty_form_html = block.render_form(None, 'advert') self.assertInHTML('<input id="advert" name="advert" placeholder="" type="hidden" />', empty_form_html) self.assertIn('createSnippetChooser("advert", "tests/advert");', empty_form_html) test_advert = Advert.objects.get(text='test_advert') test_advert_form_html = block.render_form(test_advert, 'advert') expected_html = '<input id="advert" name="advert" placeholder="" type="hidden" value="%d" />' % test_advert.id self.assertInHTML(expected_html, test_advert_form_html) self.assertIn("pick an advert, any advert", test_advert_form_html)
class TeamBlock(StructBlock): first_team_member = SnippetChooserBlock(target_model=TeamMember, required=True) second_team_member = SnippetChooserBlock(target_model=TeamMember, required=True) third_team_member = SnippetChooserBlock(target_model=TeamMember, required=True) fourth_team_member = SnippetChooserBlock(target_model=TeamMember, required=True) class Meta: image = 'pages/images/streamfield_blocks/team.jpg' template = 'pages/streamfield_blocks/team.html'
def test_clean(self): required_block = SnippetChooserBlock(Advert) nonrequired_block = SnippetChooserBlock(Advert, required=False) test_advert = Advert.objects.get(text='test_advert') self.assertEqual(required_block.clean(test_advert), test_advert) with self.assertRaises(ValidationError): required_block.clean(None) self.assertEqual(nonrequired_block.clean(test_advert), test_advert) self.assertEqual(nonrequired_block.clean(None), None)
class CallToActionWithTextBlock(blocks.StructBlock): call_to_action = SnippetChooserBlock(CallToActionSnippet) side_text = blocks.RichTextBlock() class Meta: icon = "redirect" template = "blocks/call_to_action_with_text_block.html"
class ResourceBlock(blocks.StructBlock): """A section of a ResourcePage""" title = blocks.CharBlock(required=True) hide_title = blocks.BooleanBlock(required=False, help_text='Should the section title be displayed?') content = blocks.StreamBlock([ ('text', blocks.RichTextBlock(blank=False, null=False, required=False, icon='pilcrow')), ('documents', blocks.ListBlock(ThumbnailBlock(), template='blocks/section-documents.html', icon='doc-empty')), ('contact_info', ContactInfoBlock()), ('internal_button', InternalButtonBlock()), ('external_button', ExternalButtonBlock()), ('page', blocks.PageChooserBlock(template='blocks/page-links.html')), ('disabled_page', blocks.CharBlock(blank=False, null=False, required=False, template='blocks/disabled-page-links.html', icon='placeholder', help_text='Name of a disabled link')), ('document_list', blocks.ListBlock(FeedDocumentBlock(), template='blocks/document-list.html', icon='doc-empty')), ('current_commissioners', CurrentCommissionersBlock()), ('fec_jobs', CareersBlock()), ('mur_search', MURSearchBlock()), ('table', TableBlock()), ('html', blocks.RawHTMLBlock()), ('reporting_example_cards', ReportingExampleCards()), ('contribution_limits_table', SnippetChooserBlock('home.EmbedTableSnippet', template = 'blocks/embed-table.html', icon='table')), ]) aside = blocks.StreamBlock([ ('title', blocks.CharBlock(required=False, icon='title')), ('document', ThumbnailBlock()), ('link', AsideLinkBlock()), ], template='blocks/section-aside.html', icon='placeholder') class Meta: template = 'blocks/section.html'
class Row(RowMixin): content = fields.StreamField([ ('column', SnippetChooserBlock('grid.Column')), ]) panels = RowMixin.panels + [ StreamFieldPanel('content'), ]
class MainContactInfo(blocks.StructBlock): header = blocks.CharBlock(required=False) body = blocks.RichTextBlock(required=False) contact = SnippetChooserBlock(ContactSnippetClass) class Meta: icon = 'wagtail' template = '_includes/organisms/main-contact-info.html'
class Column(ColumnMixin): content = fields.StreamField([ ('row', SnippetChooserBlock('grid.Row')), ] + LIVE_CLEAN_BLOCKS + UNCHAINED_COMPONENTS_BLOCKS) panels = ColumnMixin.panels + [ StreamFieldPanel('content'), ]
class StatisticBlock(blocks.StructBlock): title = blocks.CharBlock(max_length=255, required=False) statistics = blocks.ListBlock(SnippetChooserBlock(Statistic), ) link = LinkBlock(required=False) customisation = HeadingCustomisationBlock(required=False) class Meta: icon = "snippet" template = "blocks/statistic_block.html"
class InstitutionsBlock(StructBlock): block_heading = CharBlock(required=True) items = ListBlock( SnippetChooserBlock(target_model=Institution, required=True)) class Meta: image = 'pages/images/streamfield_blocks/institutions.jpg' template = 'pages/streamfield_blocks/institutions.html'
class MainContactInfo(blocks.StructBlock): contact = SnippetChooserBlock('v1.Contact') has_top_rule_line = blocks.BooleanBlock( default=False, required=False, help_text='Add a horizontal rule line to top of contact block.') class Meta: icon = 'wagtail' template = '_includes/organisms/main-contact-info.html'
class ResourceListing(UnchainedResourceListMixin, TranslatablePage): content = StreamField( [ ('row', SnippetChooserBlock('grid.Row')), ] + LIVE_CLEAN_PANEL_BLOCKS, default=[], ) content_panels = UnchainedResourceListMixin.content_panels + [ StreamFieldPanel('content'), ]
class StoryBlock(blocks.StreamBlock): heading = blocks.CharBlock(classname="full title", icon='title') paragraph = blocks.RichTextBlock() image = ImageBlock() quote = QuoteBlock() embed = EmbedBlock() call_to_action = SnippetChooserBlock( 'utils.CallToActionSnippet', template="blocks/call_to_action_block.html" ) document = DocumentBlock() class Meta: template = "blocks/stream_block.html"
class ContactExpandableGroup(blocks.StructBlock): """Expandable group that renders selected Contact snippets.""" group_title = blocks.CharBlock() contacts = blocks.ListBlock(SnippetChooserBlock('v1.Contact')) def get_context(self, value, parent_context=None): context = super(ContactExpandableGroup, self).get_context(value, parent_context=parent_context) # This block mimics the ExpandableGroup block. context['value'] = ExpandableGroup().to_python({ 'heading': value['group_title'], 'add_heading_id': True, 'expandables': [{ 'label': contact.heading, 'content': contact.contact_info.stream_data, } for contact in value['contacts']], }) return context def bulk_to_python(self, values): """Support bulk retrieval of Contacts to reduce database queries. This method leverages Wagtail's undocumented method for doing bulk retrieval of data for StreamField blocks. It retrieves all Contacts referenced by a StreamField in a single lookup, instead of the default behavior of doing one database query per SnippetChooserBlock. """ contact_ids = set( itertools.chain(*(block['contacts'] for block in values))) contact_model = self.child_blocks['contacts'].child_block.target_model contacts_by_id = contact_model.objects.in_bulk(contact_ids) for block in values: block['contacts'] = [ contacts_by_id[contact_id] for contact_id in block['contacts'] ] return values class Meta: icon = 'list-ul' template = '_includes/organisms/expandable-group.html' class Media: js = ["expandable-group.js"]
class Generic(UnchainedJSONRendererMixin, UnchainedCommonFieldsMixin, TranslatablePage): """Generic page which contains custom components.""" content = StreamField( [ ('row', SnippetChooserBlock('grid.Row')), ] + LIVE_CLEAN_PANEL_BLOCKS, default=[], ) content_panels = UnchainedCommonFieldsMixin.content_panels + [ StreamFieldPanel('content'), ] class Meta: verbose_name = "Generic Page"
class CTDataStreamBlock(StreamBlock): h2 = CharBlock(icon="title", classname="title") h3 = CharBlock(icon="title", classname="title") h4 = CharBlock(icon="title", classname="title") intro = RichTextBlock(icon="pilcrow") paragraph = RichTextBlock(icon="pilcrow") aligned_image = ImageBlock(label="Aligned image", icon="image") pullquote = PullQuoteBlock() aligned_html = AlignedHTMLBlock(icon="code", label='Raw HTML') document = DocumentChooserBlock(icon="doc-full-inverse") pym_iframe = PymIFrameBlock(icon="code", label='Pym IFrame') iframe = IFrameBlock(icon="code", label='S3 IFrame') external_iframe = ExternalIFrame(icon="code", label="External Site IFrame") sidebar_pullquote = SidebarPullQuote() sidebar_note = SidebarNote() react_widget = SnippetChooserBlock(ReactWidget, icon="code", label="Javascript Widget")
class Article(UnchainedResourceMixin, TranslatablePage, UnchainedIndexed): published_date = models.DateField( null=True, blank=True, help_text='Pick the published date of the article.') video_url = models.URLField( max_length=255, help_text="Provide the youtube embed link in the following format:" "'https://www.youtube.com/embed/kfvxmEuC7bU'", blank=True, ) core_fields = UnchainedResourceMixin.core_fields + ['video_url' ] + ['published_date'] parent_page_types = ['resource_listing.ResourceListing'] content = StreamField( [ ('row', SnippetChooserBlock('grid.Row')), ] + LIVE_CLEAN_PANEL_BLOCKS, default=[], ) content_panels = UnchainedResourceMixin.content_panels + [ FieldPanel('published_date'), FieldPanel('video_url'), StreamFieldPanel('content'), ] search_fields = Page.search_fields + [ index.SearchField('teaser'), index.SearchField('content'), ] settings_panels = UnchainedResourceMixin.settings_panels + [ FieldPanel('is_searchable'), ]
class Product(UnchainedProductDetailsMixin, TranslatablePage): parent_page_types = [ProductListing] content = StreamField( [ ('row', SnippetChooserBlock('grid.Row')), ] + LIVE_CLEAN_PANEL_BLOCKS, default=[], ) content_panels = UnchainedProductDetailsMixin.content_panels + [ StreamFieldPanel('content'), ] def get_resource_info(self, *args, **kwargs): """Returns the product information""" if 'product_slug' in kwargs and 'details_page' in kwargs: product_information = get_product_info(kwargs['product_slug']) category_names = self.extract_category_info( product_information['category_id_list']) product_information['category_names'] = category_names product_information['attributes'] = self.get_attributes( product_information['attributes']) related_products = list() for related_product in product_information['related_products']: related_product_info = get_product_info( related_product['slug']) related_product_info['relation_type'] = related_product[ 'relation_type'] related_products.append(related_product_info) product_information['related_products'] = related_products return product_information elif 'product_slug' in kwargs: return get_product_info(kwargs['product_slug']) else: return {} def product_details(self, request, *args, **kwargs): resource_info = self.get_resource_info(product_slug=kwargs['slug']) if 'slug' in kwargs and resource_info: context = self.get_context(request, *args, **kwargs) self.og_title = resource_info.get( 'name').title() if resource_info.get('name') else None self.og_title += ' | Live Clean' self.og_description = resource_info.get('description') self.og_url = (request.META['HTTP_HOST'] + resource_info.get('url') if resource_info.get('url') else None) self.og_type = 'product.item' if self.og_title: self.title = self.og_title if self.og_description: self.search_description = self.og_description if resource_info.get('images'): images_dict = dict(resource_info.get('images')[0]) if images_dict.get('url'): self.og_image_url = images_dict.get('url') context['content'] = WagtailObjectParser.render( self, request=request, is_page=True, page_id=self.id, product_slug=kwargs['slug'], details_page=True) return TemplateResponse( request, 'pages/generic/generic_page.html', context=context, ) else: raise Http404 def extract_category_info(self, category_id_list): """Extracts the category names""" categories = get_category_info() category_info = list() for category_id in category_id_list: category = self.get_category(category_id, categories) category_info.append(category['name']) return category_info def get_category(self, category_id, categories): """Extracts a category from a list of categories if the category's id is present.""" for category in categories: if category['id'] == category_id: return category return {} def get_attributes(self, attributes): """Returns attributes with presentation names, if present.""" product_properties = ProductProperties.objects.all() for attribute in attributes: attribute_presentation = self.get_attribute_presentation( name=attribute['name'], product_properties=product_properties) # Check if presentation name is present. if attribute_presentation and attribute_presentation.presentation_name: attribute[ 'presentation_name'] = attribute_presentation.presentation_name else: attribute['presentation_name'] = attribute['name'] # Check if description presentation is present. if attribute_presentation and attribute_presentation.description: attribute['description'] = attribute_presentation.description # For attributes under attributes if attribute['attributes']: for attr in attribute['attributes']: attr_presentation = self.get_attribute_presentation( name=attr['name'], product_properties=product_properties) # Check if presentation name is present. if attr_presentation and attr_presentation.presentation_name: attr[ 'presentation_name'] = attr_presentation.presentation_name else: attribute['presentation_name'] = attribute['name'] # Check if description presentation is present. if attr_presentation and attr_presentation.description: attr['description'] = attr_presentation.description return attributes def get_attribute_presentation(self, name, product_properties): attribute = next( (attr for attr in product_properties if attr.name == name), None) if attribute: return attribute else: return
class QuoteBlock(blocks.StructBlock): quote = SnippetChooserBlock(Quote) class Meta: icon = 'fa-quote-right' template = 'blocks/quote.html'
def test_reference_model_by_string(self): block = SnippetChooserBlock('tests.Advert') test_advert = Advert.objects.get(text='test_advert') self.assertEqual(block.to_python(test_advert.id), test_advert)
class MainContactInfo(blocks.StructBlock): contact = SnippetChooserBlock(ContactSnippetClass) class Meta: icon = 'wagtail' template = '_includes/organisms/main-contact-info.html'
class MultiPage(Page): """ Multipurpose page model, allowing pretty much all types to be combined onto one page From Page: title - Char(255) slug - Slug/Char(255) content_type - FK(content_type) live - Boolean has_unpublished_changes - Boolean url_path - Text owner - FK(auth_user) seo_title - Char(255) show_in_menus - Boolean search_description - Text go_live_at - DateTime expire_at - DateTime expired - Boolean locked - Boolean first_published_at - DateTime latest_revision_created_at - DateTime """ # Database fields subtitle = models.CharField(max_length=255, null=True, blank=True) author = models.CharField(max_length=255) # Because the author may not be the person entering it on the website body = StreamField([ ('heading', blocks.CharBlock(classname="full title")), ('text', blocks.RichTextBlock()), ('image', ImageChooserBlock()), ('table', TableBlock(template="website/blocks/table.html")), ('publications_list', blocks.ListBlock(SnippetChooserBlock(Publication, label="publication"), template="website/blocks/publications_list.html")), ('projects_list', blocks.ListBlock(SnippetChooserBlock(Project, label="project"))), ('qa_list', blocks.ListBlock(QandABlock(label="entry"), template="website/blocks/qa_list.html")), ]) date = models.DateField("Post date") feed_image = models.ForeignKey( 'wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+' ) # Search index configuration search_fields = Page.search_fields + [ index.SearchField('body'), index.SearchField('author'), index.FilterField('date'), ] # Editor panels configuration content_panels = Page.content_panels + [ FieldPanel('subtitle'), FieldPanel('author'), FieldPanel('date'), StreamFieldPanel('body'), InlinePanel('related_links', label="Related links"), ] promote_panels = [ MultiFieldPanel(Page.promote_panels, "Common page configuration"), ImageChooserPanel('feed_image'), ] # Parent page / subpage type rules parent_page_types = ['HomePage', 'MultiPage']