def test_complex_form_fields(self): TestBlock = StreamBlock([ ('h2', CharBlock()), ('h3', CharBlock()), ('p', RichTextBlock()), ('singlelinefield', SingleLineFormFieldBlock()), ('multilinefield', MultiLineFormFieldBlock()), ('emailfield', EmailFormFieldBlock()), ('numberfield', NumberFormFieldBlock()), ('urlfield', UrlFormFieldBlock()), ('checkboxfield', CheckboxFormFieldBlock()), ('dropdownfield', DropdownFormFieldBlock()), ('radiofield', RadioFormFieldBlock()), ('datefield', DateFormFieldBlock()), ('datetimefield', DateTimeFormFieldBlock()), ]) value = TestBlock.to_python( json.loads('''\ [{ "value": { "choices": [{ "description": "Black", "key": "black" }, { "description": "Blue", "key": "blue" }, { "description": "Green", "key": "green" }, { "description": "Orange", "key": "orange" }, { "description": "Red", "key": "red" }, { "description": "White", "key": "white" }, { "description": "Yellow", "key": "yellow" }], "label": "What is your favorite color?", "required": true, "allow_multiple_selections": false, "help_text": "Choose your favorite color from the list below." }, "type": "dropdownfield" }, { "value": { "label": "What animal comes to mind when you think of your favorite color?", "default_value": "", "required": true, "help_text": "Don't think really hard. First animal that comes to mind." }, "type": "singlelinefield" }, { "value": { "label": "Why is this your favorite color?", "default_value": "", "required": false, "help_text": "Give us two to three sentences that describe why you like this color." }, "type": "multilinefield" }, { "value": { "choices": [{ "description": "Female", "key": "female" }, { "description": "Male", "key": "male" }], "label": "Gender", "required": false, "help_text": "Are you male or female?" }, "type": "radiofield" }, { "value": { "label": "When is your birthday.", "required": false, "help_text": "This helps us correlate favorite colors by birth month." }, "type": "datefield" }, { "value": { "label": "Can we share your answers?", "default_checked": true, "required": false, "help_text": "If you check this box we will share your responses anonymously with everyone that comes to our website." }, "type": "checkboxfield" }]''')) finder = FormFieldFinder() fields = finder.find_form_fields(TestBlock, value) self.assertEqual(len(fields), 6) self.assertIsInstance(fields[0].block, DropdownFormFieldBlock) self.assertIsInstance(fields[1].block, SingleLineFormFieldBlock) self.assertIsInstance(fields[2].block, MultiLineFormFieldBlock) self.assertIsInstance(fields[3].block, RadioFormFieldBlock) self.assertIsInstance(fields[4].block, DateFormFieldBlock) self.assertIsInstance(fields[5].block, CheckboxFormFieldBlock) self.assertEqual(fields[0].value["label"], 'What is your favorite color?') self.assertEqual( fields[1].value["label"], 'What animal comes to mind when you think of your favorite color?') self.assertEqual(fields[2].value["label"], 'Why is this your favorite color?') self.assertEqual(fields[3].value["label"], 'Gender') self.assertEqual(fields[4].value["label"], 'When is your birthday.') self.assertEqual(fields[5].value["label"], 'Can we share your answers?')
class ProjectPage( ArchiveablePageAbstract, BasicPageAbstract, ContentPage, FeatureablePageAbstract, ShareablePageAbstract, ThemeablePageAbstract, ): body = StreamField( BasicPageAbstract.body_default_blocks + [ BasicPageAbstract.body_poster_block, BasicPageAbstract.body_recommended_block, BasicPageAbstract.body_text_border_block, ], blank=True, ) image_banner = models.ForeignKey( 'images.CigionlineImage', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', verbose_name='Banner Image', ) project_contacts = StreamField( [ ('contact', PageChooserBlock(required=True, page_type='people.PersonPage')), ], blank=True, ) project_leads = StreamField( [ ('project_lead', PageChooserBlock(required=True, page_type='people.PersonPage')), ('external_project_lead', CharBlock(required=True)), ], blank=True, ) project_members = StreamField( [ ('project_member', PageChooserBlock(required=True, page_type='people.PersonPage')), ('external_project_member', CharBlock(required=True)), ], blank=True, ) project_types = ParentalManyToManyField('research.ProjectType', blank=True) related_files = StreamField( [ ('file', DocumentChooserBlock()), ], blank=True, ) # Reference field for the Drupal-Wagtail migrator. Can be removed after. drupal_node_id = models.IntegerField(blank=True, null=True) content_panels = [ BasicPageAbstract.title_panel, BasicPageAbstract.body_panel, MultiFieldPanel( [ FieldPanel('publishing_date'), FieldPanel('project_types'), ], heading='General Information', classname='collapsible collapsed', ), MultiFieldPanel( [ StreamFieldPanel('project_leads'), StreamFieldPanel('project_members'), StreamFieldPanel('project_contacts'), ], heading='People', classname='collapsible collapsed', ), MultiFieldPanel( [ ImageChooserPanel('image_hero'), ImageChooserPanel('image_banner'), ], heading='Images', classname='collapsible collapsed', ), MultiFieldPanel( [ FieldPanel('topics'), StreamFieldPanel('related_files'), ], heading='Related', classname='collapsible collapsed', ), MultiFieldPanel( [ InlinePanel( 'featured_pages', max_num=9, min_num=0, label='Page', ), ], heading='Featured Content', classname='collapsible collapsed', ), ] promote_panels = Page.promote_panels + [ FeatureablePageAbstract.feature_panel, ShareablePageAbstract.social_panel, SearchablePageAbstract.search_panel, ] settings_panels = Page.settings_panels + [ ArchiveablePageAbstract.archive_panel, BasicPageAbstract.submenu_panel, ThemeablePageAbstract.theme_panel, ] search_fields = ArchiveablePageAbstract.search_fields \ + BasicPageAbstract.search_fields \ + ContentPage.search_fields parent_page_types = ['core.BasicPage', 'research.ProjectListPage'] subpage_types = [] templates = 'research/project_page.html' def get_featured_pages(self): featured_page_ids = self.featured_pages.order_by('sort_order').values_list('featured_page', flat=True) pages = Page.objects.specific().prefetch_related( 'authors__author', 'topics', ).in_bulk(featured_page_ids) return [pages[x] for x in featured_page_ids] def get_template(self, request, *args, **kwargs): standard_template = super(ProjectPage, self).get_template(request, *args, **kwargs) if self.theme: return f'themes/{self.get_theme_dir()}/project_page.html' return standard_template def get_context(self, request): context = super().get_context(request) context['featured_pages'] = self.get_featured_pages() return context class Meta: verbose_name = 'Activity' verbose_name_plural = 'Activities'
class PullQuoteBlock(StructBlock): quote = TextBlock("quote title") attribution = CharBlock() class Meta: icon = "openquote"
class HomePage(HomePageMetaData, SectionBodyMixin, Page): def __str__(self): return self.title class Meta(): verbose_name = 'Home Page' parent_page_types = [] # prevent from being a child page featured_publication = models.ForeignKey( Page, null=True, blank=True, on_delete=models.SET_NULL, related_name='+', help_text='The page to showcase in the page hero', verbose_name='Featured page' ) hero_image = models.ForeignKey( 'wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', help_text='Overwrites the hero image of the featured publication' ) hero_link_caption = models.CharField( max_length=255, blank=True, help_text='Text to display on the link button', default='View full page' ) featured_content = StreamField([ ('content', StructBlock([ ('title', CharBlock()), ('body', RichTextBlock(features=RICHTEXT_FEATURES_NO_FOOTNOTES)), ('related_page', PageChooserBlock(required=False)), ('button_caption', CharBlock(required=False, help_text='Overwrite title text from the related page')) ], template='home/blocks/featured_content.html')) ], null=True, blank=True) featured_work_heading = models.CharField( blank=True, null=True, default='Featured work', max_length=200, verbose_name='Section heading' ) content_panels = Page.content_panels + [ MultiFieldPanel([ PageChooserPanel('featured_publication', [ 'publications.PublicationPage', 'publications.ShortPublicationPage', 'publications.LegacyPublicationPage', 'publications.AudioVisualMedia', 'news.NewsStoryPage', 'blog.BlogArticlePage', 'events.EventPage', 'project.ProjectPage' ]), ImageChooserPanel('hero_image'), FieldPanel('hero_link_caption') ], heading='Hero Section'), StreamFieldPanel('featured_content'), MultiFieldPanel([ FieldPanel('featured_work_heading'), InlinePanel('featured_pages', label='Featured Pages') ], heading='Featured Work'), StreamFieldPanel('sections'), InlinePanel('page_notifications', label='Notifications') ] def get_context(self, request, *args, **kwargs): context = super().get_context(request, *args, **kwargs) context['featured_pages'] = [link.other_page for link in self.featured_pages.all().order_by('sort_order') if link.other_page.live] return context
class AddedStreamFieldWithEmptyListDefaultPage(Page): body = StreamField([('title', CharBlock())], default=[])
class LinkBlock(StructBlock): link = URLBlock(required=True) link_text = CharBlock(required=True)
def __init__(self): super().__init__(StructBlock([ ('image', ImageChooserBlock()), ('caption', CharBlock(max_length=255, required=False)), ]))
class SupporterBlock(StructBlock): name = CharBlock() image = ImageBlock(required=False) content = RichTextBlock()
class Migration(migrations.Migration): dependencies = [ ('wagtailcore', '0040_page_draft_title'), ] operations = [ migrations.CreateModel( name='AlbumPage', fields=[ ('page_ptr', models.OneToOneField(auto_created=True, on_delete=models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.Page')), ('jumbotron', StreamField( (('jumbotron_block', StructBlock( (('heading_text', CharBlock(required=True)), ('paragraph_text', CharBlock(required=True)), ('text_color', ChoiceBlock( blank=True, choices=[('', 'Select the text color'), ('text-primary', 'Primary Text'), ('text-secondary', 'Secondary Text'), ('text-success', 'Success Text'), ('text-danger', 'Danger Text'), ('text-warning', 'Warning Text'), ('text-info', 'Info Text'), ('text-light', 'Light Text'), ('text-dark', 'Dark Text'), ('text-muted', 'Muted Text'), ('text-white', 'White Text')])), ('bg_color', ChoiceBlock( blank=True, choices=[ ('', 'Select the background color'), ('bg-primary', 'Primary Background'), ('bg-secondary', 'Secondary Backgorund'), ('bg-success', 'Success Backgorund'), ('bg-danger', 'Danger Backgorund'), ('bg-warning', 'Warning Backgorund'), ('bg-info', 'Info Backgorund'), ('bg-light', 'Light Backgorund'), ('bg-dark', 'Dark Backgorund'), ('bg-white', 'White Backgorund') ]))))), ), blank=True, verbose_name='Jumbotron')), ('card', StreamField((('card_block', StructBlock( (('card_image', ImageChooserBlock()), ('card_text', CharBlock())))), ), blank=True, verbose_name='Card')), ('language_link', models.ForeignKey(blank=True, null=True, on_delete=models.deletion.SET_NULL, related_name='+', to='wagtailcore.Page')), ], options={ 'abstract': False, }, bases=('wagtailcore.page', models.Model), ), ]
class ContactBlock(StructBlock): name = CharBlock() website = URLBlock() email = EmailBlock()
class SectorBlock(StructBlock): name = CharBlock() image = ImageBlock(required=False) contacts = ListBlock(ContactBlock)
class FAQBlock(StructBlock): question = CharBlock() answer = RichTextBlock()
class TestStructBlock(StructBlock): title = CharBlock() description = CharBlock() field = SingleLineFormFieldBlock()
class TestBlock(StreamBlock): field = SingleLineFormFieldBlock(icon='placeholder') p = CharBlock()
class QuestionAnswerBlock(StructBlock): question = CharBlock() answer = RichTextBlock() class Meta: template = 'cms_home/blocks/question_answer_block.html'
class CategoryQuestionBlock(OptionalFormFieldBlock): class Meta: template = 'stream_forms/render_list_field.html' # Overwrite field label and help text so we can defer to the category # as required field_label = CharBlock( label=_('Label'), required=False, help_text=_('Leave blank to use the default Category label'), ) help_text = TextBlock( required=False, label=_('Leave blank to use the default Category help text'), ) category = ModelChooserBlock('categories.Category') multi = BooleanBlock(label='Multi select', required=False) def get_field_class(self, struct_value): if struct_value['multi']: return forms.MultipleChoiceField else: return forms.ChoiceField def use_defaults_from_category(self, kwargs, category): category_fields = {'label': 'name', 'help_text': 'help_text'} for field in category_fields.keys(): if not kwargs.get(field): kwargs[field] = getattr(category, category_fields[field]) return kwargs def get_field_kwargs(self, struct_value): kwargs = super().get_field_kwargs(struct_value) category = struct_value['category'] kwargs = self.use_defaults_from_category(kwargs, category) choices = category.options.values_list('id', 'value') kwargs.update({'choices': choices}) return kwargs def get_widget(self, struct_value): if struct_value['multi']: category = struct_value['category'] category_size = category.options.count() # Pick widget according to number of options to maintain good usability. if category_size < 32: return forms.CheckboxSelectMultiple else: return Select2MultipleWidget else: return forms.RadioSelect def prepare_data(self, value, data, serialize): category = value['category'] if data: data = category.options.filter(id__in=data).values_list('value', flat=True) return data def get_searchable_content(self, value, data): return None def no_response(self): return ['No Response']
class FAQBlock(StructBlock): title = CharBlock(required=False) faqs = ListBlock(QuestionAnswerBlock) class Meta: template = 'cms_home/blocks/faq_block.html'
class NamedLinkGroupBlock(StructBlock): title = CharBlock() link_group = LinkGroupBlock()
class NewsletterPage(Page): class CallToActionChoices(models.TextChoices): EXPLORE = ('explore', 'Explore') LEARN_MORE = ('learn_more', 'Learn More') LISTEN = ('listen', 'Listen') NO_CTA = ('no_cta', 'No CTA') PDF = ('pdf', 'PDF') READ = ('read', 'Read') RSVP = ('rsvp', 'RSVP') SHARE_FACEBOOK = ('share_facebook', 'Share (Facebook)') SHARE_LINKEDIN = ('share_linkedin', 'Share (LinkedIn)') SHARE_TWITTER = ('share_twitter', 'Share (Twitter)') SUBSCRIBE = ('subscribe', 'Subscribe') WATCH = ('watch', 'Watch') body = StreamField( [ ('advertisement_block', StructBlock([ ('title', CharBlock(required=False)), ('text', RichTextBlock(required=False)), ('url', URLBlock(required=True)), ('image', ImageChooserBlock(required=False)), ('cta', ChoiceBlock( choices=CallToActionChoices.choices, verbose_name='CTA', required=True, )), ])), ('content_block', StructBlock([ ('content', PageChooserBlock(required=False)), ('url', URLBlock(required=False)), ('title_override', CharBlock(required=False)), ('text_override', RichTextBlock(required=False)), ('cta', ChoiceBlock( choices=CallToActionChoices.choices, verbose_name='CTA', required=True, )), ('line_separator_above', BooleanBlock( verbose_name='Add line separator above block', )), ])), ('featured_content_block', StructBlock([ ('content', PageChooserBlock(required=False)), ('url', URLBlock(required=False)), ('title_override', CharBlock(required=False)), ('text_override', RichTextBlock(required=False)), ('image_override', ImageChooserBlock(required=False)), ('cta', ChoiceBlock( choices=CallToActionChoices.choices, verbose_name='CTA', required=True, )), ])), ('social_block', StructBlock([ ('title', CharBlock(required=False)), ('text', RichTextBlock(required=False)), ])), ('text_block', StructBlock([ ('title', CharBlock(required=False)), ('text', RichTextBlock(required=False)), ])), ], blank=True, ) html_file = models.ForeignKey( 'wagtaildocs.Document', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', ) # Reference field for the Drupal-Wagtail migrator. Can be removed after. drupal_node_id = models.IntegerField(blank=True, null=True) content_panels = Page.content_panels + [ MultiFieldPanel( [ StreamFieldPanel('body'), ], heading='Body', classname='collapsible', ), MultiFieldPanel( [ DocumentChooserPanel('html_file'), ], heading='HTML File', classname='collapsible collapsed', ), ] parent_page_types = ['newsletters.NewsletterListPage'] subpage_types = [] templates = 'newsletters/newsletter_page.html' class Meta: verbose_name = 'Newsletter' verbose_name_plural = 'Newsletters'
class SponsorCategoryBlock(StructBlock): category_title = CharBlock() sponsor_group = SponsorGroupBlock()
class EventPage( BasicPageAbstract, ContentPage, FeatureablePageAbstract, ShareablePageAbstract, ): class EventAccessOptions(models.IntegerChoices): PRIVATE = (0, 'Private') PUBLIC = (1, 'Public') class InvitationTypes(models.IntegerChoices): RSVP_REQUIRED = (0, 'RSVP Required') INVITATION_ONLY = (1, 'Invitation Only') NO_RSVP = (2, 'No RSVP Required') embed_youtube = models.URLField(blank=True) event_access = models.IntegerField(choices=EventAccessOptions.choices, default=EventAccessOptions.PUBLIC, null=True, blank=False) event_end = models.DateTimeField(blank=True, null=True) flickr_album_url = models.URLField(blank=True) invitation_type = models.IntegerField( choices=InvitationTypes.choices, default=InvitationTypes.RSVP_REQUIRED) location_address1 = models.CharField(blank=True, max_length=255, verbose_name='Address (Line 1)') location_address2 = models.CharField(blank=True, max_length=255, verbose_name='Address (Line 2)') location_city = models.CharField(blank=True, max_length=255, verbose_name='City') location_country = models.CharField(blank=True, max_length=255, verbose_name='Country') location_name = models.CharField(blank=True, max_length=255) location_postal_code = models.CharField(blank=True, max_length=32, verbose_name='Postal Code') location_province = models.CharField(blank=True, max_length=255, verbose_name='Province/State') projects = ParentalManyToManyField('research.ProjectPage', blank=True) registration_url = models.URLField(blank=True, max_length=512) related_files = StreamField( [ ('file', DocumentChooserBlock()), ], blank=True, ) speakers = StreamField( [ ('speaker', PageChooserBlock(required=True, page_type='people.PersonPage')), ('external_speaker', CharBlock(required=True)), ], blank=True, ) twitter_hashtag = models.CharField(blank=True, max_length=64) website_button_text = models.CharField( blank=True, max_length=64, help_text= 'Override the button text for the event website. If empty, the button will read "Event Website".' ) website_url = models.URLField(blank=True, max_length=512) # Reference field for the Drupal-Wagtail migrator. Can be removed after. drupal_node_id = models.IntegerField(blank=True, null=True) content_panels = [ BasicPageAbstract.title_panel, BasicPageAbstract.body_panel, BasicPageAbstract.images_panel, FieldPanel('publishing_date', heading='Event start'), FieldPanel('event_end'), MultiFieldPanel( [ FieldPanel('event_access'), FieldPanel('invitation_type'), FieldPanel('website_url'), FieldPanel('website_button_text'), FieldPanel('registration_url'), ], heading='Event Details', classname='collapsible collapsed', ), MultiFieldPanel( [ StreamFieldPanel('speakers'), ], heading='Speakers', classname='collapsible collapsed', ), MultiFieldPanel( [ FieldPanel('location_name'), FieldPanel('location_address1'), FieldPanel('location_address2'), FieldPanel('location_city'), FieldPanel('location_province'), FieldPanel('location_postal_code'), FieldPanel('location_country'), ], heading='Location', classname='collapsible collapsed', ), MultiFieldPanel( [ FieldPanel('embed_youtube'), StreamFieldPanel('related_files'), ], heading='Media', classname='collapsible collapsed', ), MultiFieldPanel( [ FieldPanel('twitter_hashtag'), FieldPanel('flickr_album_url'), ], heading='Event Social Media', classname='collapsible collapsed', ), MultiFieldPanel( [ FieldPanel('topics'), FieldPanel('projects'), ], heading='Related', classname='collapsible collapsed', ), ] promote_panels = Page.promote_panels + [ FeatureablePageAbstract.feature_panel, ShareablePageAbstract.social_panel, ] parent_page_types = ['events.EventListPage'] subpage_types = [] templates = 'events/event_page.html' class Meta: verbose_name = 'Event' verbose_name_plural = 'Events'
class MultiInputCharFieldBlock(CharFieldBlock): number_of_inputs = IntegerBlock(default=2, label=_('Max number of inputs')) add_button_text = CharBlock(required=False, default=_('Add new item')) class Meta: label = _('Text field (single line) (multiple inputs)')
class AddedStreamFieldWithoutDefaultPage(Page): body = StreamField([('title', CharBlock())])
class FormFieldBlock(StructBlock): field_label = CharBlock(label=_('Label')) help_text = TextBlock(required=False, label=_('Help text')) help_link = URLBlock(required=False, label=_('Help link')) field_class = forms.CharField widget = None class Meta: template = 'stream_forms/render_field.html' def get_slug(self, struct_value): return force_str(slugify(anyascii(struct_value['field_label']))) def get_field_class(self, struct_value): return self.field_class def get_widget(self, struct_value): return self.widget def get_field_kwargs(self, struct_value): kwargs = { 'label': struct_value['field_label'], 'help_text': conditional_escape(struct_value['help_text']), 'required': struct_value.get('required', False) } if 'default_value' in struct_value: kwargs['initial'] = struct_value['default_value'] form_widget = self.get_widget(struct_value) if form_widget is not None: kwargs['widget'] = form_widget return kwargs def get_field(self, struct_value): field_kwargs = self.get_field_kwargs(struct_value) return self.get_field_class(struct_value)(**field_kwargs) def decode(self, value): """Convert JSON representation into actual python objects""" return value def serialize(self, value, context): field_kwargs = self.get_field_kwargs(value) return { 'question': field_kwargs['label'], 'answer': context.get('data'), 'type': self.name, } def serialize_no_response(self, value, context): return { 'question': value['field_label'], 'answer': 'No Response', 'type': 'no_response', } def prepare_data(self, value, data, serialize=False): return bleach_value(str(data)) def render(self, value, context): data = context.get('data') data = self.prepare_data(value, data, context.get('serialize', False)) context.update(data=data or self.no_response()) if context.get('serialize'): if not data: return self.serialize_no_response(value, context) return self.serialize(value, context) return super().render(value, context) def get_searchable_content(self, value, data): return str(data) def no_response(self): return "No response"
class StreamModel(models.Model): body = StreamField([ ('text', CharBlock()), ('rich_text', RichTextBlock()), ('image', ImageChooserBlock()), ])
class ComplexPage(Page): footer_colour = models.CharField("Hover colour", max_length=255, blank=True, null=True) story = StreamField([ ('new_line_break', BooleanBlock(required=False, default=False)), ('title', CharBlock(blank=True, classname="full title", template='home/blocks/inner_title.html', icon='title')), ('text_block', StructBlock([ ('text', RichTextBlock(blank=True)), ('size', ChoiceBlock(required=False, max_length=20, choices=( (u'6', u'Half'), (u'9', u'Full'), ))), ], template='home/blocks/text.html', icon='pilcrow')), ('image', StructBlock([ ('new_line_break', BooleanBlock(required=False, default=False)), ('image', ImageChooserBlock(blank=True, required=False)), ('caption', CharBlock(required=False)), ('size', ChoiceBlock(required=False, max_length=20, choices=( (u'4', u'Small'), (u'6', u'Medium'), (u'8', u'Large'), ))), ], template='home/blocks/image.html', icon='image')), ('slideshow', StreamBlock( [('slide', StructBlock([ ('image', ImageChooserBlock(blank=True, required=False)), ('caption', CharBlock(required=False)), ], icon='image'))], template='home/blocks/slideshow.html', icon='code')), ('visual_link', StructBlock( [('new_line_break', BooleanBlock(required=False, default=False)), ('image', ImageChooserBlock(blank=True, required=False)), ('title', CharBlock(required=False)), ('size', ChoiceBlock(required=False, max_length=20, choices=( (u'4', u'Small'), (u'6', u'Medium'), (u'8', u'Large'), ))), ('page_link', PageChooserBlock(blank=True))], template='home/blocks/visual_link.html', icon='image')), ], blank=True) content_panels = Page.content_panels + [ StreamFieldPanel('story'), ] promote_panels = [ FieldPanel('footer_colour'), ] + Page.promote_panels
class HomePage(Page): body = StreamField([("title", CharBlock()), ("paragraph", RichTextBlock())]) content_panels = Page.content_panels + [StreamFieldPanel("body")]
class ServicesPage(TypesetBodyMixin, HeroMixin, Page): """ http://development-initiatives.surge.sh/page-templates/09-consultancy-services """ def get_context(self, request): context = super(ServicesPage, self).get_context(request) topic_filter = request.GET.get('topic', None) if topic_filter: examples = ServicesPageRelatedExample.objects.filter( topics__slug=topic_filter) else: examples = ServicesPageRelatedExample.objects.all() context['topics'] = ExampleTopic.objects.all() context['selected_topic'] = topic_filter context['examples'] = examples context['related_news'] = get_related_pages( self, self.services_related_news.all(), NewsStoryPage.objects, min_len=0) return context contact_text = models.CharField( blank=True, null=True, max_length=250, default= 'Find out more about our consultancy services and what we can do for you' ) contact_button_text = models.CharField(blank=True, null=True, max_length=100, default='Get in touch') contact_email = models.EmailField(blank=True, null=True) specialities = StreamField([ ('speciality', StructBlock([('image', ImageChooserBlock(required=False)), ('heading', CharBlock(required=False)), ('body', RichTextBlock(required=False, features=RICHTEXT_FEATURES_NO_FOOTNOTES)) ])) ]) skills = StreamField([ ('skill', StructBlock([('heading', CharBlock(required=False)), ('body', RichTextBlock(required=False, features=RICHTEXT_FEATURES_NO_FOOTNOTES)) ])) ]) richtext_columns = StreamField( [('column', StructBlock([('heading', TextBlock(required=False, icon='title')), ('content', RichTextBlock(features=RICHTEXT_FEATURES_NO_FOOTNOTES, icon='fa-paragraph'))], template='blocks/richtext_column.html'))], null=True, blank=True) sections = StreamField(SectionStreamBlock(), verbose_name="Sections", null=True, blank=True) class Meta: verbose_name = 'Services Page' content_panels = Page.content_panels + [ hero_panels(), MultiFieldPanel([ FieldPanel('contact_text'), FieldPanel('contact_button_text'), FieldPanel('contact_email') ], heading='Contact aside'), StreamFieldPanel('body'), StreamFieldPanel('specialities'), StreamFieldPanel('skills'), InlinePanel('services_related_news', label="Related news"), InlinePanel('services_related_example', label="Project examples"), StreamFieldPanel('richtext_columns'), StreamFieldPanel('sections'), InlinePanel('page_notifications', label='Notifications') ] subpage_types = ['general.General'] parent_page_types = [WhatWeDoPage]
class NamedBackerBlock(StructBlock): name = CharBlock() class Meta: template = 'blog/blocks/named_backer.html'
class LinkBlock(StructBlock): """ A Link which can either be to a (off-site) URL, to a page in the site, or to a document. Use this instead of URLBlock. """ link_to = ChoiceBlock( choices=[("page", "Page"), ("file", "File"), ("custom_url", "Custom URL")], required=False, classname="link_choice_type_selector", ) page = PageChooserBlock(required=False, classname="page_link") file = DocumentChooserBlock(required=False, classname="file_link") custom_url = CharBlock( max_length=300, required=False, classname="custom_url_link url_field", validators=[URLOrAbsolutePathValidator()], ) new_window = BooleanBlock( label="Open in new window", required=False, classname="new_window_toggle" ) class Meta: label = None value_class = URLValue icon = "fa-share-square" form_classname = "link_block" form_template = "wagtailadmin/block_forms/link_block.html" template = "blocks/link_block.html" def set_name(self, name): """ Over ride StructBlock set_name so label can remain empty in streamblocks """ self.name = name def clean(self, value): clean_values = super().clean(value) errors = {} url_default_values = { "page": None, "file": None, "custom_url": "", } url_type = clean_values.get("link_to") # Check that a value has been uploaded for the chosen link type if url_type != "" and clean_values.get(url_type) in [None, ""]: errors[url_type] = ErrorList( ["You need to add a {} link".format(url_type.replace("_", " "))] ) else: try: # Remove values added for link types not selected url_default_values.pop(url_type, None) for field in url_default_values: clean_values[field] = url_default_values[field] except KeyError: errors[url_type] = ErrorList(["Enter a valid link type"]) if errors: raise StreamBlockValidationError(block_errors=errors) return clean_values