Exemple #1
0
class ContentImage(blocks.StructBlock):
    image = atoms.ImageBasic()
    image_width = blocks.ChoiceBlock(
        choices=[('full', 'full'), (470, '470px'), (270, '270px'),
                 (170, '170px')],
        default='full',
    )
    image_position = blocks.ChoiceBlock(
        choices=[('right', 'right'), ('left', 'left')],
        default='right',
        help_text='Does not apply if the image is full-width',
    )
    text = blocks.RichTextBlock(required=False, label='Caption')
    is_bottom_rule = blocks.BooleanBlock(
        required=False,
        default=True,
        label='Has bottom rule line',
        help_text='Check to add a horizontal rule line to bottom of inset.')

    class Meta:
        icon = 'image'
        template = '_includes/molecules/content-image.html'
        label = 'Image'
Exemple #2
0
class TextIntroduction(blocks.StructBlock):
    eyebrow = blocks.CharBlock(
        required=False,
        help_text=('Optional: Adds an H5 eyebrow above H1 heading text. '
                   'Only use in conjunction with heading.'),
        label='Pre-heading'
    )
    heading = blocks.CharBlock(required=False)
    intro = blocks.RichTextBlock(required=False)
    body = blocks.RichTextBlock(required=False)
    links = blocks.ListBlock(atoms.Hyperlink(required=False), required=False)
    has_rule = blocks.BooleanBlock(
        required=False,
        label="Has bottom rule",
        help_text=('Check this to add a horizontal rule line to bottom of '
                   'text introduction.')
    )

    def clean(self, value):
        cleaned = super(TextIntroduction, self).clean(value)

        # Eyebrow requires a heading.
        if cleaned.get('eyebrow') and not cleaned.get('heading'):
            raise ValidationError(
                'Validation error in TextIntroduction: '
                'pre-heading requires heading',
                params={'heading': ErrorList([
                    'Required if a pre-heading is entered.'
                ])}
            )

        return cleaned

    class Meta:
        icon = 'title'
        template = '_includes/molecules/text-introduction.html'
        classname = 'block__flush-top'
Exemple #3
0
class FeaturedContent(blocks.StructBlock):
    heading = blocks.CharBlock(required=False)
    body = blocks.RichTextBlock(required=False)

    category = blocks.ChoiceBlock(choices=ref.fcm_types, required=False)
    post = blocks.PageChooserBlock(required=False)

    show_post_link = blocks.BooleanBlock(required=False,
                                         label="Render post link?")
    post_link_text = blocks.CharBlock(required=False)

    image = atoms.ImageBasic(required=False)
    links = blocks.ListBlock(atoms.Hyperlink(required=False),
                             label='Additional Links')

    video = blocks.StructBlock([
        ('id',
         blocks.CharBlock(
             required=False,
             label='ID',
             help_text='E.g., in "https://www.youtube.com/watch?v=en0Iq8II4fA",'
             ' the ID is everything after the "?v=".')),
        ('url',
         blocks.CharBlock(required=False,
                          label='URL',
                          help_text='You must use the embed URL, e.g., '
                          'https://www.youtube.com/embed/'
                          'JPTg8ZB3j5c?autoplay=1&enablejsapi=1')),
        ('height', blocks.CharBlock(default='320', required=False)),
        ('width', blocks.CharBlock(default='568', required=False)),
    ])

    class Meta:
        template = '_includes/molecules/featured-content.html'
        icon = 'doc-full-inverse'
        label = 'Featured Content'
        classname = 'block__flush'
Exemple #4
0
class DropShadowBlock(blocks.StructBlock):
    drop_shadow_is_on = blocks.BooleanBlock(
        label="Drop Shadow Toggle",
        help_text="Show or hide drop shadow",
        required=False)
    text_hex = blocks.CharBlock(label="Text Hex Code",
                                max_length=7,
                                required=False)

    def clean(self, value):
        value = super().clean(value)

        hex_fields = ['text_hex']
        errors = {
            field: ['Please enter a valid hex code']
            for field in hex_fields if not validate_hex(value[field])
        }

        if errors:
            raise ValidationError(
                "Validation error in DropShadowBlock",
                params=errors,
            )
        return value
Exemple #5
0
class RegComment(blocks.StructBlock):
    document_id = blocks.CharBlock(
        required=True,
        label='Document ID',
        help_text=
        'Federal Register document ID number to which the comment should be submitted. Should follow this format: CFPB-YYYY-####-####'
    )
    generic_regs_link = blocks.BooleanBlock(
        required=False,
        default=True,
        label='Use generic Regs.gov link?',
        help_text=
        'If unchecked, the link to comment at Regulations.gov if you want to add attachments will link directly to the document given above. Leave this checked if this comment form is being published before the full document is live at Regulations.gov, then uncheck it when the full document has been published.'
    )
    id = blocks.CharBlock(
        required=False,
        label='Form ID',
        help_text=
        'Sets the `id` attribute in the form\'s markup. If not set, the form will be assigned a base id of `o-reg-comment_` with a random number appended.'
    )

    class Meta:
        icon = 'form'
        template = '_includes/organisms/reg-comment.html'
Exemple #6
0
class FilterControls(BaseExpandable):
    form_type = blocks.ChoiceBlock(choices=[
        ('filterable-list', 'Filterable List'),
        ('pdf-generator', 'PDF Generator'),
    ],
                                   default='filterable-list')
    title = blocks.BooleanBlock(default=True,
                                required=False,
                                label='Filter Title')
    post_date_description = blocks.CharBlock(default='Published')
    categories = blocks.StructBlock([
        ('filter_category', blocks.BooleanBlock(default=True, required=False)),
        ('show_preview_categories',
         blocks.BooleanBlock(default=True, required=False)),
        ('page_type', blocks.ChoiceBlock(choices=ref.page_types,
                                         required=False)),
    ])
    topics = blocks.BooleanBlock(default=True,
                                 required=False,
                                 label='Filter Topics')
    authors = blocks.BooleanBlock(default=True,
                                  required=False,
                                  label='Filter Authors')
    date_range = blocks.BooleanBlock(default=True,
                                     required=False,
                                     label='Filter Date Range')
    output_5050 = blocks.BooleanBlock(default=False,
                                      required=False,
                                      label="Render preview items as 50-50s")

    class Meta:
        label = 'Filter Controls'
        icon = 'form'

    class Media:
        js = ['filterable-list-controls.js']
Exemple #7
0
class LinkBlobGroup(blocks.StructBlock):
    heading = blocks.CharBlock(icon='title', required=False)
    has_top_border = blocks.BooleanBlock(required=False)
    has_bottom_border = blocks.BooleanBlock(required=False)
    link_blobs = blocks.ListBlock(molecules.HalfWidthLinkBlob())
class RelatedPosts(blocks.StructBlock):
    limit = blocks.CharBlock(
        default='3',
        help_text=('This limit applies to EACH TYPE of post this module '
                   'retrieves, not the total number of retrieved posts.')
    )
    show_heading = blocks.BooleanBlock(
        required=False,
        default=True,
        label='Show Heading and Icon?',
        help_text=('This toggles the heading and '
                   'icon for the related types.')
    )
    header_title = blocks.CharBlock(
        default='Further reading',
        label='Slug Title'
    )

    relate_posts = blocks.BooleanBlock(
        required=False,
        default=True,
        label='Blog Posts',
        editable=False
    )
    relate_newsroom = blocks.BooleanBlock(
        required=False,
        default=True,
        label='Newsroom',
        editable=False
    )
    relate_events = blocks.BooleanBlock(
        required=False,
        default=True,
        label='Events'
    )

    specific_categories = blocks.ListBlock(
        blocks.ChoiceBlock(choices=ref.related_posts_categories,
                           required=False),
        required=False
    )

    and_filtering = blocks.BooleanBlock(
        required=False,
        default=False,
        label='Match all topic tags',
        help_text=('If checked, related posts will only be pulled in if they '
                   'match ALL topic tags set on this page. Otherwise, related '
                   'posts can match any one topic tag.')
    )

    alternate_view_more_url = blocks.CharBlock(
        required=False,
        label='Alternate "View more" URL',
        help_text=('By default, the "View more" link will go to the Activity '
                   'Log, filtered based on the above parameters. Enter a URL '
                   'in this field to override that link destination.')
    )

    def get_context(self, value, parent_context=None):
        context = super(RelatedPosts, self).get_context(
            value,
            parent_context=parent_context
        )

        page = context['page']
        request = context['request']

        context.update({
            'posts': self.related_posts(page, value),
            'view_more_url': (
                value['alternate_view_more_url'] or
                self.view_more_url(page, request)
            ),
        })

        return context

    @staticmethod
    def related_posts(page, value):
        from v1.models.learn_page import AbstractFilterPage

        def tag_set(related_page):
            return set([tag.pk for tag in related_page.tags.all()])

        def match_all_topic_tags(queryset, page_tags):
            """Return pages that share every one of the current page's tags."""
            current_tag_set = set([tag.pk for tag in page_tags])
            return [page for page in queryset
                    if current_tag_set.issubset(tag_set(page))]

        related_types = []
        related_items = {}
        if value.get('relate_posts'):
            related_types.append('blog')
        if value.get('relate_newsroom'):
            related_types.append('newsroom')
        if value.get('relate_events'):
            related_types.append('events')
        if not related_types:
            return related_items

        tags = page.tags.all()
        and_filtering = value['and_filtering']
        specific_categories = value['specific_categories']
        limit = int(value['limit'])
        queryset = AbstractFilterPage.objects.live().exclude(
            id=page.id).order_by('-date_published').distinct().specific()

        for parent in related_types:  # blog, newsroom or events
            # Include children of this slug that match at least 1 tag
            children = Page.objects.child_of_q(Page.objects.get(slug=parent))
            filters = children & Q(('tags__in', tags))

            if parent == 'events':
                # Include archived events matches
                archive = Page.objects.get(slug='archive-past-events')
                children = Page.objects.child_of_q(archive)
                filters |= children & Q(('tags__in', tags))

            if specific_categories:
                # Filter by any additional categories specified
                categories = ref.get_appropriate_categories(
                    specific_categories=specific_categories,
                    page_type=parent
                )
                if categories:
                    filters &= Q(('categories__name__in', categories))

            related_queryset = queryset.filter(filters)

            if and_filtering:
                # By default, we need to match at least one tag
                # If specified in the admin, change this to match ALL tags
                related_queryset = match_all_topic_tags(related_queryset, tags)

            related_items[parent.title()] = related_queryset[:limit]

        # Return items in the dictionary that have non-empty querysets
        return {key: value for key, value in related_items.items() if value}

    @staticmethod
    def view_more_url(page, request):
        """Generate a URL to see more pages like this one.
        This method generates a link to the Activity Log page (which must
        exist and must have a unique site-wide slug of "activity-log") with
        filters set by the tags assigned to this page, like this:
        /activity-log/?topics=foo&topics=bar&topics=baz
        If for some reason a page with slug "activity-log" does not exist,
        this method will raise Page.DoesNotExist.
        """
        activity_log = Page.objects.get(slug='activity-log')
        url = activity_log.get_url(request)

        tags = urlencode([('topics', tag) for tag in page.tags.slugs()])
        if tags:
            url += '?' + tags

        return url

    class Meta:
        icon = 'link'
        template = '_includes/molecules/related-posts.html'
Exemple #9
0
class SliderBlock(blocks.StructBlock):
    image = ImageChooserBlock()

    name = blocks.CharBlock(
        label=_('Name'),
        max_length = 30,
        help_text = _('Appears below as navigation button.'),
        required = True,
    )

    subtext = blocks.CharBlock(
        label=_('Subtext'),
        max_length = 35,
        help_text = _('Appears under the navigation button.'),
        required = False,
    )

    button = blocks.BooleanBlock(
        label='Call to Action',
        default=False,
        help_text = _('Has a call to action button.'),
        required = False,
    )

    cta_text = blocks.CharBlock(
        label=_('CTA text'),
        max_length = 20,
        required = False,
    )

    cta_pos  = blocks.ChoiceBlock(
        label = _('CTA Position'),
        choices = (
            ('left', 'Left'),
            ('right', 'Right')
        ),
        required = False,
    )

    cta_color_picker = ColorPickerBlock(
        label = _('CTA background color selector'),
        required = False,
    )

    cta_link_type = blocks.ChoiceBlock(
        label = 'CTA link type',
        choices = (
            ('wagtail', 'Wagtail page'),
            ('url', 'Manual url')
        ),
        required = False,
    )

    cta_page_link = blocks.PageChooserBlock(
        label = 'CTA wagtail link',
        can_choose_root = True,
        required= False,
    )

    cta_url = blocks.CharBlock(
        label='CTA url',
        max_length = 255,
        required = False,
    )

    class Meta:
        icon = 'image'
class SublandingPage(CFGOVPage):
    header = StreamField([
        ('hero', molecules.Hero()),
    ], blank=True)
    content = StreamField([
        ('text_introduction', molecules.TextIntroduction()),
        ('featured_content', molecules.FeaturedContent()),
        ('info_unit_group', organisms.InfoUnitGroup()),
        ('image_text_25_75_group', organisms.ImageText2575Group()),
        ('image_text_50_50_group', organisms.ImageText5050Group()),
        ('full_width_text', organisms.FullWidthText()),
        ('half_width_link_blob_group', organisms.HalfWidthLinkBlobGroup()),
        ('third_width_link_blob_group', organisms.ThirdWidthLinkBlobGroup()),
        ('post_preview_snapshot', organisms.PostPreviewSnapshot()),
        ('well', organisms.Well()),
        ('table', organisms.Table(editable=False)),
        ('table_block',
         organisms.AtomicTableBlock(table_options={'renderer': 'html'})),
        ('contact', organisms.MainContactInfo()),
        ('formfield_with_button', molecules.FormFieldWithButton()),
        ('reg_comment', organisms.RegComment()),
        ('feedback', v1_blocks.Feedback()),
        ('snippet_list', organisms.SnippetList()),
    ],
                          blank=True)
    sidebar_breakout = StreamField([
        ('slug', blocks.CharBlock(icon='title')),
        ('heading', blocks.CharBlock(icon='title')),
        ('paragraph', blocks.RichTextBlock(icon='edit')),
        ('breakout_image',
         blocks.StructBlock([
             ('image', ImageChooserBlock()),
             ('is_round',
              blocks.BooleanBlock(required=False, default=True,
                                  label='Round?')),
             ('icon', blocks.CharBlock(help_text='Enter icon class name.')),
             ('heading',
              blocks.CharBlock(required=False, label='Introduction Heading')),
             ('body',
              blocks.TextBlock(required=False, label='Introduction Body')),
         ],
                            heading='Breakout Image',
                            icon='image')),
        ('related_posts', organisms.RelatedPosts()),
        ('job_listing_list', JobListingList()),
    ],
                                   blank=True)

    # General content tab
    content_panels = CFGOVPage.content_panels + [
        StreamFieldPanel('header'),
        StreamFieldPanel('content'),
    ]

    sidebar_panels = [
        StreamFieldPanel('sidebar_breakout'),
    ] + CFGOVPage.sidefoot_panels

    # Tab handler interface
    edit_handler = TabbedInterface([
        ObjectList(content_panels, heading='General Content'),
        ObjectList(sidebar_panels, heading='Sidebar'),
        ObjectList(CFGOVPage.settings_panels, heading='Configuration'),
    ])

    template = 'sublanding-page/index.html'

    objects = PageManager()

    def get_browsefilterable_posts(self, limit):
        filter_pages = [
            p.specific for p in self.get_appropriate_descendants()
            if 'FilterablePage' in p.specific_class.__name__
            and 'archive' not in p.title.lower()
        ]
        posts_list = []
        for page in filter_pages:
            eligible_children = AbstractFilterPage.objects.live().filter(
                CFGOVPage.objects.child_of_q(page))

            form = FilterableListForm(filterable_pages=eligible_children)
            for post in form.get_page_set():
                posts_list.append(post)
        return sorted(posts_list, key=lambda p: p.date_published,
                      reverse=True)[:limit]
Exemple #11
0
class ColophonImageListBlock(blocks.StructBlock):
    class Meta:
        template = "cms_pages/content_page_blocks/colophon.html"

    do_nothing = blocks.BooleanBlock(required=False)
Exemple #12
0
class MenuItem(models.Model):
    link_text = models.CharField(max_length=255,
                                 help_text='Display text for menu link',
                                 verbose_name="Menu label")

    page_link = models.ForeignKey(
        'wagtailcore.Page',
        null=True,
        blank=True,
        related_name='+',
        help_text=('Link to Wagtail overview page for this menu item '
                   '(leave blank if there is no overview page).'),
        verbose_name='Overview page link')

    order = models.PositiveSmallIntegerField(
        null=True,
        blank=True,
        help_text='Determines order in which this menu item appears in nav.')

    column_1 = StreamField([(
        'nav_group',
        v1_blocks.NavGroup(label='Menu items group'),
    )],
                           blank=True)

    column_2 = StreamField(
        [('nav_group', v1_blocks.NavGroup(label='Menu items group'))],
        blank=True)

    column_3 = StreamField(
        [('nav_group', v1_blocks.NavGroup(label='Menu items group'))],
        blank=True)

    column_4 = StreamField(
        [('nav_group', v1_blocks.NavGroup(label='Menu items group')),
         ('featured_content',
          v1_blocks.FeaturedMenuContent(label='Featured image item'))],
        blank=True)

    nav_footer = StreamField(
        [('nav_footer',
          blocks.StructBlock(
              [('draft',
                blocks.BooleanBlock(
                    required=False,
                    default=False,
                    help_text='If checked, this block will only show '
                    'on our sharing site (Content).',
                    label='Mark block as draft')),
               ('content', blocks.RichTextBlock(required=False))],
              label='Menu footer'))],
        blank=True)

    panels = [
        MultiFieldPanel([
            FieldPanel('link_text'),
            PageChooserPanel('page_link'),
        ],
                        heading='Menu Information'),
        FieldPanel('order'),
        StreamFieldPanel('column_1'),
        StreamFieldPanel('column_2'),
        StreamFieldPanel('column_3'),
        StreamFieldPanel('column_4'),
        StreamFieldPanel('nav_footer'),
    ]

    class Meta:
        ordering = ('order', )

    def __str__(self):
        return self.link_text

    def get_content(self, draft):
        """
        Filter menu item column content by by draft/live state
        and structure it for display in menu template.
        """
        cols = [getattr(self, 'column_' + str(i)) for i in range(1, 5)]
        self.nav_groups = filter(
            None, [self.get_active_block(col, draft) for col in cols])
        self.featured_content = self.nav_groups.pop() if len(self.nav_groups) \
            and self.nav_groups[-1].block_type == 'featured_content' else None
        self.footer = self.get_active_block(self.nav_footer, draft)
        return self

    @staticmethod
    def get_active_block(blocks, draft):
        """
        Returns first block whose state matches draft
        parameter. If draft parameter is true, will
        return last live block if there are no draft blocks.
        """
        return next((block for i, block in enumerate(blocks)
                     if block.value.get('draft', '') == draft or (
                         draft and i == len(blocks) - 1)), None)
Exemple #13
0
class OpeningTimeBlock(blocks.StructBlock):
    """
    A semi-structured opening times block.

    Period is left as a free text input to allow for
    """
    PUBLIC = 7
    PUBLIC_LABEL = 'Public holiday'
    WEEKDAYS = tuple(enumerate(calendar.day_name)) + ((PUBLIC, PUBLIC_LABEL),)

    weekday = blocks.ChoiceBlock(choices=WEEKDAYS, required=False)
    label = blocks.CharBlock(help_text='Optionally override default weekday label', required=False)
    date = blocks.DateBlock(help_text='Optionally specify a day these times are for', required=False)
    start = blocks.TimeBlock(required=False)
    end = blocks.TimeBlock(required=False)
    closed = blocks.BooleanBlock(default=False, required=False)

    class Meta:
        template = 'wagtail_extensions/blocks/openingtime.html'

    def clean(self, value):
        cleaned = super().clean(value)
        start, end, weekday, date = map(cleaned.get, ['start', 'end', 'weekday', 'date'])
        errors = defaultdict(ErrorList)

        if date and date < datetime.date.today():
            err = ValidationError('Dates need to be in the future')
            errors['date'].append(err)

        if (start or end) and not (weekday or date):
            err = ValidationError('Specifying a weekday or date is required')
            errors['weekday'].append(err)

        if start and end and end <= start:
            err = ValidationError('End must be after start')
            errors['end'].append(err)

        if errors:
            raise ValidationError("There is a problem with this opening time", params=errors)

        return cleaned

    def get_context(self, value, parent_context=None):
        ctx = super().get_context(value, parent_context=parent_context)
        weekday = ctx['value'].get('weekday')
        if weekday is not None:
            if weekday == self.PUBLIC:
                ctx['is_public'] = True
            else:
                # Next date with this weekday
                today = datetime.date.today()
                ctx['next_date'] = today + relativedelta(weekday=weekday)
        return ctx

    def to_python(self, value):
        weekday = value.get('weekday')
        if weekday:
            weekday_int = int(weekday)
            value['weekday'] = weekday_int

            label = value.get('label')
            if weekday_int == self.PUBLIC and not label:
                value['label'] = self.PUBLIC_LABEL
        return super().to_python(value)
class FilterableList(BaseExpandable):
    title = blocks.BooleanBlock(default=True,
                                required=False,
                                label='Filter Title')
    no_posts_message = blocks.CharBlock(
        required=False,
        help_text=mark_safe(
            'Message for the <a href="https://cfpb.github.io/'
            'capital-framework/components/cf-notifications/'
            '#recommended-notification-patterns">notification</a> '
            'that will be displayed instead of filter controls '
            'if there are no posts to filter.'))
    no_posts_explanation = blocks.CharBlock(
        required=False,
        help_text='Additional explanation for the notification that '
        'will be displayed if there are no posts to filter.')
    post_date_description = blocks.CharBlock(
        required=False,
        label='Date stamp descriptor',
        help_text='Strongly encouraged to help users understand the '
        'action that the date of the post is linked to, '
        'i.e. published, issued, released.')
    categories = blocks.StructBlock([
        ('filter_category', blocks.BooleanBlock(default=True, required=False)),
        ('show_preview_categories',
         blocks.BooleanBlock(default=True, required=False)),
        ('page_type',
         blocks.ChoiceBlock(choices=ref.filterable_list_page_types,
                            required=False)),
    ])
    topic_filtering = blocks.ChoiceBlock(
        choices=[
            ('no_filter', "Don't filter topics"),
            ('sort_alphabetically',
             'Filter topics, sort topic list alphabetically'),
            ('sort_by_frequency',
             'Filter topics, sort topic list by number of results'),
        ],
        required=True,
        help_text='Whether to include a dropdown in the filter controls '
        'for "Topics"')
    authors = blocks.BooleanBlock(default=True,
                                  required=False,
                                  label='Filter Authors')
    date_range = blocks.BooleanBlock(default=True,
                                     required=False,
                                     label='Filter Date Range')
    output_5050 = blocks.BooleanBlock(default=False,
                                      required=False,
                                      label="Render preview items as 50-50s")
    link_image_and_heading = blocks.BooleanBlock(
        default=False,
        required=False,
        help_text='Add links to post preview images and'
        ' headings in filterable list results')

    class Meta:
        label = 'Filterable List'
        icon = 'form'
        template = '_includes/organisms/filterable-list.html'

    class Media:
        js = ['filterable-list.js']

    @staticmethod
    def get_filterable_topics(filterable_page_ids, value):
        """Given a set of page IDs, return the list of filterable topics"""
        tags = Tag.objects.filter(
            v1_cfgovtaggedpages_items__content_object__id__in=
            filterable_page_ids  # noqa E501
        ).values_list('slug', 'name')

        sort_order = value.get('topic_filtering', 'sort_by_frequency')
        if sort_order == 'sort_alphabetically':
            return tags.distinct().order_by('name')
        elif sort_order == 'sort_by_frequency':
            return [tag for (tag, _) in Counter(tags).most_common()]
        else:
            return []

    def get_context(self, value, parent_context=None):
        context = super(FilterableList,
                        self).get_context(value, parent_context=parent_context)
        show_topics = value['topic_filtering'] == 'sort_by_frequency' or \
            value['topic_filtering'] == 'sort_alphabetically'
        # Different instances of FilterableList need to render their post
        # previews differently depending on the page type they live on. By
        # default post dates and tags are always shown.
        context.update({
            'show_post_dates': True,
            'show_post_tags': True,
            'show_topic_filter': show_topics,
        })

        # Pull out the page type selected when the FilterableList was
        # configured in the page editor, if one was specified. See the
        # 'categories' block definition above. The page type choices are
        # defined in v1.util.ref.page_types.
        page_type = value['categories'].get('page_type')

        # Pending a much-needed refactor of that code, this logic is being
        # placed here to keep FilterableList logic in one place. It would be
        # better if this kind of configuration lived on custom Page models.
        page_type_overrides = {
            'cfpb-researchers': {
                'show_post_dates': False
            },
            'consumer-reporting': {
                'show_post_dates': False
            },
            'foia-freq-req-record': {
                'show_post_tags': False
            },
        }

        context.update(page_type_overrides.get(page_type, {}))
        return context
class FilterableList(BaseExpandable):
    title = blocks.BooleanBlock(default=True, required=False,
                                label='Filter Title')
    no_posts_message = blocks.CharBlock(
        required=False,
        help_text=mark_safe(
            'Message for the <a href="https://cfpb.github.io/'
            'capital-framework/components/cf-notifications/'
            '#recommended-notification-patterns">notification</a> '
            'that will be displayed instead of filter controls '
            'if there are no posts to filter.'))
    no_posts_explanation = blocks.CharBlock(
        required=False,
        help_text='Additional explanation for the notification that '
                  'will be displayed if there are no posts to filter.')
    post_date_description = blocks.CharBlock(default='Published')
    categories = blocks.StructBlock([
        ('filter_category',
         blocks.BooleanBlock(default=True, required=False)),
        ('show_preview_categories',
         blocks.BooleanBlock(default=True, required=False)),
        ('page_type', blocks.ChoiceBlock(
            choices=ref.filterable_list_page_types,
            required=False
        )),
    ])
    topics = blocks.BooleanBlock(default=True, required=False,
                                 label='Filter Topics')
    authors = blocks.BooleanBlock(default=True, required=False,
                                  label='Filter Authors')
    date_range = blocks.BooleanBlock(default=True, required=False,
                                     label='Filter Date Range')
    output_5050 = blocks.BooleanBlock(default=False, required=False,
                                      label="Render preview items as 50-50s")
    link_image_and_heading = blocks.BooleanBlock(
        default=False,
        required=False,
        help_text='Add links to post preview images and'
                  ' headings in filterable list results'
    )

    class Meta:
        label = 'Filterable List'
        icon = 'form'
        template = '_includes/organisms/filterable-list.html'

    class Media:
        js = ['filterable-list.js']

    def get_context(self, value, parent_context=None):
        context = super(FilterableList, self).get_context(
            value,
            parent_context=parent_context
        )

        # Different instances of FilterableList need to render their post
        # previews differently depending on the page type they live on. By
        # default post dates and tags are always shown.
        context.update({
            'show_post_dates': True,
            'show_post_tags': True,
        })

        # Pull out the page type selected when the FilterableList was
        # configured in the page editor, if one was specified. See the
        # 'categories' block definition above. The page type choices are
        # defined in v1.util.ref.page_types.
        page_type = value['categories'].get('page_type')

        # Pending a much-needed refactor of that code, this logic is being
        # placed here to keep FilterableList logic in one place. It would be
        # better if this kind of configuration lived on custom Page models.
        page_type_overrides = {
            'cfpb-researchers': {'show_post_dates': False},
            'consumer-reporting': {'show_post_dates': False},
            'foia-freq-req-record': {'show_post_tags': False},
        }

        context.update(page_type_overrides.get(page_type, {}))
        return context
Exemple #16
0
class SublandingPage(CFGOVPage):
    header = StreamField([
        ('hero', molecules.Hero()),
    ], blank=True)
    content = StreamField([
        ('text_introduction', molecules.TextIntroduction()),
        ('featured_content', molecules.FeaturedContent()),
        ('image_text_25_75_group', organisms.ImageText2575Group()),
        ('image_text_50_50_group', organisms.ImageText5050Group()),
        ('full_width_text', organisms.FullWidthText()),
        ('half_width_link_blob_group', organisms.HalfWidthLinkBlobGroup()),
        ('post_preview_snapshot', organisms.PostPreviewSnapshot()),
        ('well', organisms.Well()),
        ('table', organisms.Table()),
        ('contact', organisms.MainContactInfo()),
        ('formfield_with_button', molecules.FormFieldWithButton()),
        ('reg_comment', organisms.RegComment()),
    ],
                          blank=True)
    sidebar_breakout = StreamField([
        ('slug', blocks.CharBlock(icon='title')),
        ('heading', blocks.CharBlock(icon='title')),
        ('paragraph', blocks.RichTextBlock(icon='edit')),
        ('breakout_image',
         blocks.StructBlock([
             ('image', ImageChooserBlock()),
             ('is_round',
              blocks.BooleanBlock(required=False, default=True,
                                  label='Round?')),
             ('icon', blocks.CharBlock(help_text='Enter icon class name.')),
             ('heading',
              blocks.CharBlock(required=False, label='Introduction Heading')),
             ('body',
              blocks.TextBlock(required=False, label='Introduction Body')),
         ],
                            heading='Breakout Image',
                            icon='image')),
        ('related_posts', organisms.RelatedPosts()),
    ],
                                   blank=True)

    # General content tab
    content_panels = CFGOVPage.content_panels + [
        StreamFieldPanel('header'),
        StreamFieldPanel('content'),
    ]

    sidebar_panels = [
        StreamFieldPanel('sidebar_breakout'),
    ] + CFGOVPage.sidefoot_panels

    # Tab handler interface
    edit_handler = TabbedInterface([
        ObjectList(content_panels, heading='General Content'),
        ObjectList(sidebar_panels, heading='Sidebar'),
        ObjectList(CFGOVPage.settings_panels, heading='Configuration'),
    ])

    template = 'sublanding-page/index.html'

    def get_browsefilterable_posts(self, request, limit):
        filter_pages = [
            p.specific
            for p in self.get_appropriate_descendants(request.site.hostname)
            if 'FilterablePage' in p.specific_class.__name__
            and 'archive' not in p.title.lower()
        ]
        filtered_controls = {}
        for page in filter_pages:
            id = str(util.get_form_id(page, request.GET))
            if id not in filtered_controls.keys():
                filtered_controls.update({id: []})
            form_class = page.get_form_class()
            posts = filterable_context.get_page_set(
                page, form_class(parent=page, hostname=request.site.hostname),
                request.site.hostname)
            if filtered_controls[id]:
                filtered_controls[id] += posts
            else:
                filtered_controls[id] = posts
        posts_tuple_list = [(id, post)
                            for id, posts in filtered_controls.iteritems()
                            for post in posts]
        posts = sorted(posts_tuple_list,
                       key=lambda p: p[1].date_published,
                       reverse=True)[:limit]
        return posts
class InfoUnitGroup(blocks.StructBlock):
    format = blocks.ChoiceBlock(
        choices=[
            ('50-50', '50/50'),
            ('33-33-33', '33/33/33'),
            ('25-75', '25/75'),
        ],
        default='50-50',
        label='Format',
        help_text='Choose the number and width of info unit columns.',
    )

    heading = v1_blocks.HeadingBlock(required=False)

    intro = blocks.RichTextBlock(
        required=False,
        help_text='If this field is not empty, '
                  'the Heading field must also be set.'
    )

    link_image_and_heading = blocks.BooleanBlock(
        default=True,
        required=False,
        help_text=('Check this to link all images and headings to the URL of '
                   'the first link in their unit\'s list, if there is a link.')
    )

    has_top_rule_line = blocks.BooleanBlock(
        default=False,
        required=False,
        help_text=('Check this to add a horizontal rule line to top of '
                   'info unit group.')
    )

    lines_between_items = blocks.BooleanBlock(
        default=False,
        required=False,
        label='Show rule lines between items',
        help_text=('Check this to show horizontal rule lines between info '
                   'units.')
    )

    info_units = blocks.ListBlock(molecules.InfoUnit())

    sharing = blocks.StructBlock([
        ('shareable', blocks.BooleanBlock(label='Include sharing links?',
                                          help_text='If checked, share links '
                                                    'will be included below '
                                                    'the items.',
                                          required=False)),
        ('share_blurb', blocks.CharBlock(help_text='Sets the tweet text, '
                                                   'email subject line, and '
                                                   'LinkedIn post text.',
                                         required=False)),
    ])

    def clean(self, value):
        cleaned = super(InfoUnitGroup, self).clean(value)

        # Intro paragraph may only be specified with a heading.
        if cleaned.get('intro') and not cleaned.get('heading'):
            raise ValidationError(
                'Validation error in InfoUnitGroup: intro with no heading',
                params={'heading': ErrorList([
                    'Required if paragraph is not empty. (If it looks empty, '
                    'click into it and hit the delete key a bunch of times.)'
                ])}
            )

        # If 25/75, info units must have images.
        if cleaned.get('format') == '25-75':
            for unit in cleaned.get('info_units'):
                if not unit['image']['upload']:
                    raise ValidationError(
                        ('Validation error in InfoUnitGroup: '
                         '25-75 with no image'),
                        params={'format': ErrorList([
                            'Info units must include images when using the '
                            '25/75 format. Search for an "FPO" image if you '
                            'need a temporary placeholder.'
                        ])}
                    )

        return cleaned

    class Meta:
        icon = 'list-ul'
        template = '_includes/organisms/info-unit-group-2.html'
Exemple #18
0
class SliderBlock(blocks.StructBlock):
    image = ImageChooserBlock()

    name = blocks.CharBlock(
        label='Naam',
        max_length = 30,
        help_text = 'Verschijnt onderaan als navigatieknop.',
        required = True,
    )

    subtext = blocks.CharBlock(
        label='Subtekst',
        max_length = 35,
        help_text = 'Verschijnt onder de navigatieknop.',
        required = False,
    )

    button = blocks.BooleanBlock(
        label='Call to Action',
        default=False,
        help_text = 'Heeft een call to action knop.',
        required = False,
    )

    cta_text = blocks.CharBlock(
        label='CTA tekst',
        max_length = 20,
        required = False,
    )

    cta_pos  = blocks.ChoiceBlock(
        label = 'CTA Positie',
        choices = (
            ('left', _('Left')),
            ('right', _('Right'))
        ),
        required = False,
    )
    
    cta_color_picker = ColorPickerBlock(
        label = 'CTA achtergrondkleur kiezer',
        required = False,
    )

    cta_link_type = blocks.ChoiceBlock(
        label = 'CTA link type',
        choices = (
            ('wagtail', 'Wagtailpagina'),
            ('url', 'Handmatige url')
        ),
        required = False,
    )

    cta_page_link = blocks.PageChooserBlock(
        label = 'CTA wagtail link',
        can_choose_root = True,
        required= False,
    )

    cta_url = blocks.CharBlock(
        label='CTA url',
        max_length = 255,
        required = False,
    )

    class Meta:
        icon = 'image'
class HomePage(Page):
    header = StreamField([
        ('hbanner', blocks.StructBlock([
            ('banner', blocks.CharBlock(blank=True, classname="full title", icon='title'))
        ], required=False, icon='bold')),

        ('hfull', blocks.StructBlock([
            ('full', blocks.CharBlock(blank=True, classname="full title", icon='title'))
        ], required=False, icon='placeholder')),

        ('hcode', blocks.StructBlock([
            ('code', blocks.RawHTMLBlock(blank=True, classname="full"))
        ], icon='code'))
    ], blank=True)

    article = StreamField([
        ('aabout', blocks.StructBlock([
            ('about_pages', blocks.StreamBlock([
                ('about', blocks.StructBlock([
                    ('blink', blocks.CharBlock(blank=True, classname="full")),
                    ('use_image', blocks.BooleanBlock(default=False, help_text="Use picture instead of blink", required=False, classname="full")),
                    ('image', ImageChooserBlock(required=False, classname="full")),
                    ('boxes', blocks.StreamBlock([
                        ('title', blocks.CharBlock(blank=True, classname="full title", icon='title')),
                        ('content', blocks.RichTextBlock(blank=True, classname="full"))
                    ]))
                ], icon='doc-full'))
            ], icon='cogs')),
        ], icon='radio-empty')),

        ('amotd', blocks.StructBlock([
            ('modt', blocks.CharBlock(max_length=16, default="Sky's The Limit", classname="full")),
        ], icon='pilcrow')),

        ('asharingan', blocks.StructBlock([
            ('sharingan', blocks.StructBlock([
                ('sharingan_1', blocks.RichTextBlock(default="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", classname="full")),
                ('sharingan_2', blocks.RichTextBlock(default="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", classname="full")),
                ('sharingan_3', blocks.RichTextBlock(default="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", classname="full")),

                ('nyan_titel', blocks.CharBlock(max_length=16, default="The Team", classname="full")),
                ('show_team', blocks.BooleanBlock(default=False, help_text="Whether team block will be shown on page", required=False, classname="full")),
                ('team', blocks.StreamBlock([
                    ('member', blocks.StructBlock([
                        ('pic', ImageChooserBlock(blank=True, classname="full")),
                        ('name', blocks.CharBlock(blank=True, max_length=16, default="", classname="full")),
                        ('description', blocks.CharBlock(max_length=128, default="", classname="full"))
                    ], icon='user'))
                ], required=False))
            ]))
        ], icon='view')),

        ('aspaceship', blocks.StructBlock([
        ], icon='pick')),

        ('agallery', blocks.StructBlock([
            ('title', blocks.CharBlock(blank=True, classname="full")),
            ('gallery', blocks.StreamBlock([
                ('image', ImageChooserBlock(blank=True, classname="full")),
            ]))
        ], icon='grip')),

        ('acode', blocks.StructBlock([
            ('code', blocks.RawHTMLBlock(blank=True, classname="full"))
        ], icon='code'))
    ], blank=True)



    main_content_panels = [
        StreamFieldPanel('header'),
        StreamFieldPanel('article')
    ]

    edit_handler = TabbedInterface([
        ObjectList(Page.content_panels + main_content_panels, heading='Main'),
        ObjectList(Page.promote_panels + Page.settings_panels, heading='Settings', classname="settings")
    ])
Exemple #20
0
class SocialBlock(blocks.StructBlock):
    view = blocks.BooleanBlock(required=True, default=True)

    class Meta:
        template = 'home/social_block.html'
Exemple #21
0
class Hero(blocks.StructBlock):
    heading = blocks.CharBlock(
        required=False,
        help_text=mark_safe(
            'For complete guidelines on creating heroes, visit our '
            '<a href="https://cfpb.github.io/design-manual/global-elements/heroes.html">'  # noqa: E501
            'Design Manual</a>.'
            '<ul class="help">Character counts (including spaces) at largest '
            'breakpoint:'
            '<li>&bull; 41 characters max (one-line heading)</li>'
            '<li>&bull; 82 characters max (two-line heading)</li></ul>')
    )
    body = blocks.RichTextBlock(
        label="Sub-heading",
        required=False,
        help_text=mark_safe(
            '<ul class="help">Character counts (including spaces) at largest '
            'breakpoint:'
            '<li>&bull; 165-186 characters (after a one-line heading)</li>'
            '<li>&bull; 108-124 characters (after a two-line heading)</li>'
            '</ul>')
    )
    image = ImageChooserBlock(
        label="Large image",
        required=False,
        help_text=mark_safe(
            'When saving illustrations, use a transparent background. '
            '<a href="https://cfpb.github.io/design-manual/global-elements/heroes.html#style">'  # noqa: E501
            'See image dimension guidelines.</a>')
    )
    small_image = ImageChooserBlock(
        required=False,
        help_text=mark_safe(
            '<b>Optional.</b> Provides an alternate image for '
            'small displays when using a photo or bleeding hero. '
            'Not required for the standard illustration. '
            '<a href="https://cfpb.github.io/design-manual/global-elements/heroes.html#style">'  # noqa:E501
            'See image dimension guidelines.</a>')
    )
    background_color = blocks.CharBlock(
        required=False,
        help_text=mark_safe(
            'Specify a hex value (with the # sign) from our '
            '<a href="https://cfpb.github.io/design-manual/brand-guidelines/color-principles.html">'  # noqa: E501
            'official color palette</a>.')
    )
    is_overlay = blocks.BooleanBlock(
        label="Photo",
        required=False,
        help_text=mark_safe(
            '<b>Optional.</b> Uses the large image as a background under '
            'the entire hero, creating the "Photo" style of hero (see '
            '<a href="https://cfpb.github.io/design-manual/global-elements/heroes.html">'  # noqa: E501
            'Design Manual</a> for details). When using this option, '
            'make sure to specify a background color (above) for the '
            'left/right margins that appear when screens are wider than '
            '1200px and for the text section when the photo and text '
            'stack at mobile sizes.')
    )
    is_white_text = blocks.BooleanBlock(
        label="White text",
        required=False,
        help_text=mark_safe(
            '<b>Optional.</b> Turns the hero text white. Useful if using '
            'a dark background color or background image.')
    )
    is_bleeding = blocks.BooleanBlock(
        label="Bleed",
        required=False,
        help_text=mark_safe(
            '<b>Optional.</b> Select if you want the illustration to bleed '
            'vertically off the top and bottom of the hero space.')
    )

    class Meta:
        icon = 'image'
        template = '_includes/molecules/hero.html'
        classname = 'block__flush-top block__flush-bottom'
class ModelTable(ModelBlock):
    """Abstract StructBlock that can generate a table from a Django model.

    Subclasses must override the 'fields' and 'field_headers' class attributes
    to specify which model fields to include in the generated table.

    By default model instance values will be converted to text for display
    in table rows. To override this, subclasses may define custom field
    formatter methods, using the name 'make_FIELD_value'. This may be useful
    if fields are non-text types, for example when formatting dates.

    For example:

        def make_created_value(self, instance, value):
            return value.strftime('%b %d, %Y')

    """
    fields = None
    field_headers = None

    first_row_is_table_header = blocks.BooleanBlock(
        required=False,
        default=True,
        help_text='Display the first row as a header.'
    )
    first_col_is_header = blocks.BooleanBlock(
        required=False,
        default=False,
        help_text='Display the first column as a header.'
    )
    is_full_width = blocks.BooleanBlock(
        required=False,
        default=False,
        help_text='Display the table at full width.'
    )
    is_striped = blocks.BooleanBlock(
        required=False,
        default=False,
        help_text='Display the table with striped rows.'
    )
    is_stacked = blocks.BooleanBlock(
        required=False,
        default=True,
        help_text='Stack the table columns on mobile.'
    )
    empty_table_msg = blocks.CharBlock(
        label='No Table Data Message',
        required=False,
        help_text='Message to display if there is no table data.'
    )

    def render(self, value, context=None):
        rows = [self.field_headers]

        rows.extend([
            self.make_row(instance) for instance in self.get_queryset(value)
        ])

        table_value = {
            'data': rows,
        }

        table_value.update((k, value.get(k)) for k in (
            'first_row_is_table_header',
            'first_col_is_header',
            'is_full_width',
            'is_striped',
            'is_stacked',
            'empty_table_msg',
        ))

        table = AtomicTableBlock()
        value = table.to_python(table_value)
        return table.render(value, context=context)

    def make_row(self, instance):
        return [
            self.make_value(instance, field)
            for field in self.fields
        ]

    def make_value(self, instance, field):
        value = getattr(instance, field)
        return self.format_field_value(instance, field, value)

    def format_field_value(self, instance, field, value):
        custom_formatter_name = 'make_{}_value'.format(field)
        custom_formatter = getattr(self, custom_formatter_name, None)

        if custom_formatter:
            return custom_formatter(instance, value)
        else:
            return smart_text(value)

    class Meta:
        icon = 'table'
Exemple #23
0
class Track(Page):
    page_body = RichTextField(
        blank=True,
        help_text=
        "The main content of the page, above the list of steps and form")
    form_submission_message = models.CharField(
        max_length=255,
        help_text=
        "The text that is shown to the user after they make a form submissiom",
        default="Based on your choices we suggest looking at the following:")
    question = models.CharField(
        max_length=255,
        blank=True,
        help_text="The question for the form on the page (optional)")
    choices = StreamField([
        ('label', blocks.CharBlock(required=True)),
    ],
                          blank=True,
                          null=True)

    has_strict_rules = models.BooleanField(
        default=False,
        help_text=
        "If the rule definitions are strict it will ONLY display results that match the exact answers (instead of the union of the answers)"
    )

    rules = StreamField(
        [('rule',
          blocks.StructBlock([
              ('name', ChoiceRulesBlock()),
              ('pages', blocks.ListBlock(blocks.PageChooserBlock())),
              ('override', blocks.BooleanBlock(default=False, required=False))
          ]))],
        default=[],
        blank=True)

    default_steps = StreamField(
        [('page', blocks.PageChooserBlock())],
        blank=True,
        default=[],
        help_text=
        "The steps to show if someone submits a form with answers that are not covered by the rules"
    )

    content_panels = Page.content_panels + [
        FieldPanel('page_body', classname='full'),
        MultiFieldPanel(
            [
                MultiFieldPanel([
                    FieldPanel('question'),
                    FieldPanel('form_submission_message'),
                    StreamFieldPanel('choices')
                ]),
                FieldPanel('has_strict_rules'),
                StreamFieldPanel('rules'),
                StreamFieldPanel('default_steps'),
            ],
            heading="Options form for the page to help narrow down choices",
            classname="collapsible")
    ]

    template = 'roadmap/track/base.html'

    def steps(self):
        # Get list of all step pages that are descendants of this page
        events = Step.objects.live().descendant_of(self)
        return events

    # Directs people to the walk through or self service routes
    # Walk through path uses the choices model to filter steps to take
    def route(self, request, path_components):
        if len(request.GET):
            return RouteResult(self,
                               kwargs={'template': 'roadmap/track/base.html'})
        else:
            return super(Track, self).route(request, path_components)

    def serve(self, request, template=''):
        if template == '':
            template = self.template
        #Kind of hacky but intercept request if it has 'submit-choice' in the slug
        #Serve the rules for the selected choices
        if len(request.GET):
            #Get selected checkbox values from params in request
            selected_choices = list(request.GET.values())

            #Sort the choices so we have them in the same order as the admin defined rules
            selected_choices.sort()

            pages = []  #list of pages that will be presented to the user
            default_pages = [
            ]  #default pages if there isn't a rule defined for the choices the user selected
            all_selected_choices = ','.join(selected_choices)

            #loop through each admin defined rule to see if we have a defined rule for the selected choices
            if self.has_strict_rules:
                #Find the one rule that matches the selected choices and only suggest those steps
                for rule in self.rules:
                    if rule.value['override'] and re.search(
                            rule.value['name'], all_selected_choices):
                        pages = rule.value['pages']
                        break
                    if rule.value['name'] == all_selected_choices:
                        pages = rule.value['pages']
            else:
                #Union all the pages that match with a rule
                for rule in self.rules:
                    if rule.value['override'] and re.search(
                            rule.value['name'], all_selected_choices):
                        pages = rule.value['pages']
                        break
                    if rule.value['name'] in selected_choices:
                        for page in rule.value['pages']:
                            if page not in pages:
                                pages.append(page)

            for page in self.default_steps:
                #if the user defines default pages in the admin then create a list of pages
                #otherwise the default default_pages list is all the steps in the track
                default_pages.append(Page.objects.get(id=page.value.id))

            if not pages:
                if not default_pages:
                    default_pages = Step.objects.live().descendant_of(self)
                pages = default_pages

            request.path = '/'.join(request.path.split('/')[:3])

            return render(
                request, template, {
                    'steps': list(map((lambda page: page.specific), pages)),
                    'page': self,
                    'selected_choices': ','.join(map(str, selected_choices)),
                    'default_pages': default_pages,
                    'showMessage': True
                })
        #Otherwise just render the track page with the appropriate template
        return render(request, template, {'page': self, 'steps': self.steps()})
class ChartBlock(blocks.StructBlock):
    title = blocks.CharBlock(required=True)
    # todo: make radio buttons
    chart_type = blocks.ChoiceBlock(
        choices=[
            ('bar', 'Bar | % y-axis values'),
            ('line', 'Line | millions/billions y-axis values'),
            ('line-index', 'Line-Index | integer y-axis values'),
            ('tile_map', 'Tile Map | grid-like USA map'),
        ],
        required=True
    )
    color_scheme = blocks.ChoiceBlock(
        choices=[
            ('blue', 'Blue'),
            ('gold', 'Gold'),
            ('green', 'Green'),
            ('navy', 'Navy'),
            ('neutral', 'Neutral'),
            ('purple', 'Purple'),
            ('teal', 'Teal'),
        ],
        required=False,
        help_text='Chart\'s color scheme. See '
                  '"https://github.com/cfpb/cfpb-chart-builder'
                  '#createchart-options-".')
    data_source = blocks.CharBlock(
        required=True,
        help_text='Location of the chart\'s data source relative to '
                  '"https://files.consumerfinance.gov/data/". For example,'
                  '"consumer-credit-trends/auto-loans/num_data_AUT.csv".')
    date_published = blocks.DateBlock(
        help_text='Automatically generated when CCT cron job runs'
    )
    description = blocks.CharBlock(
        required=True,
        help_text='Briefly summarize the chart for visually impaired users.')

    has_top_rule_line = blocks.BooleanBlock(
        default=False,
        required=False,
        help_text=('Check this to add a horizontal rule line to top of '
                   'chart block.')
    )

    last_updated_projected_data = blocks.DateBlock(
        help_text='Month of latest entry in dataset'
    )

    metadata = blocks.CharBlock(
        required=False,
        help_text='Optional metadata for the chart to use. '
                  'For example, with CCT this would be the chart\'s "group".')
    note = blocks.CharBlock(
        required=False,
        help_text='Text to display as a footnote. For example, '
                  '"Data from the last six months are not final."')
    y_axis_label = blocks.CharBlock(
        required=False,
        help_text='Custom y-axis label. '
                  'NOTE: Line-Index chart y-axis '
                  'is not overridable with this field!'
    )

    class Meta:
        label = 'Chart Block'
        icon = 'image'
        template = '_includes/organisms/chart.html'

    class Media:
        js = ['chart.js']
Exemple #25
0
class TableBlock(blocks.StructBlock):
    table = blocks.TextBlock(rows=10, help_text=_(u'Enter your table as comma separated values, one line for each row.'))
    caption = blocks.CharBlock()
    header_row = blocks.BooleanBlock(required=False, help_text=_(u'Render first row as header if checked'))
    header_column = blocks.BooleanBlock(required=False, help_text=_(u'Render first column as header if checked'))
    block_classes = blocks.CharBlock(required=False)
Exemple #26
0
class SocialMedia(blocks.StructBlock):
    is_share_view = blocks.BooleanBlock(
        default=True,
        required=False,
        label='Desired action: share this page',
        help_text='If unchecked, social media icons will link users to '
        'official CFPB accounts. Do not fill in any further fields.')

    blurb = blocks.CharBlock(
        required=False,
        default="Look what I found on the CFPB's site!",
        help_text='Sets the tweet text, email subject line, '
        'and LinkedIn post text.')

    twitter_text = blocks.CharBlock(
        required=False,
        max_length=100,
        help_text='(Optional) Custom text for Twitter shares. If blank, '
        'will default to value of blurb field above.')
    twitter_related = blocks.CharBlock(
        required=False,
        help_text='(Optional) A comma-separated list of accounts related '
        'to the content of the shared URL. Do not enter the '
        ' @ symbol. If blank, it will default to just "cfpb".')
    twitter_hashtags = blocks.CharBlock(
        required=False,
        help_text='(Optional) A comma-separated list of hashtags to be '
        'appended to default tweet text.')
    twitter_lang = blocks.CharBlock(
        required=False,
        help_text='(Optional) Loads text components in the specified '
        'language, if other than English. E.g., use "es" '
        ' for Spanish. '
        'See https://dev.twitter.com/web/overview/languages '
        'for a list of supported language codes.')

    email_title = blocks.CharBlock(
        required=False,
        help_text='(Optional) Custom subject for email shares. If blank, '
        'will default to value of blurb field above.')

    email_text = blocks.CharBlock(
        required=False,
        help_text='(Optional) Custom text for email shares. If blank, will '
        'default to "Check out this page from the CFPB".')

    email_signature = blocks.CharBlock(
        required=False,
        help_text='(Optional) Adds a custom signature line to email shares. ')
    linkedin_title = blocks.CharBlock(
        required=False,
        help_text='(Optional) Custom title for LinkedIn shares. If blank, '
        'will default to value of blurb field above.')

    linkedin_text = blocks.CharBlock(
        required=False,
        help_text='(Optional) Custom text for LinkedIn shares.')

    class Meta:
        icon = 'site'
        template = '_includes/molecules/social-media.html'
Exemple #27
0
class ReadMoreBlock(StructBlock):
    name = blocks.CharBlock(required=False)
    page = PageChooserBlock(
        required=False,
        help_text='Links to the original page (in "{}") will automatically '
        'point to translated pages (if they exist) in other '
        'languages. Links to a translation will always point to that '
        'translated page, in all languages.'.format(
            TranslatablePageMixin.original_lang_code))
    link = blocks.URLBlock(required=False)
    button = blocks.BooleanBlock(required=False)
    alignment = ChoiceBlock(
        choices=[
            ('left', 'Left'),
            ('center', 'Center'),
            ('right', 'Right'),
        ],
        required=False,
    )

    def __init__(self, required=True, local_blocks=None, **kwargs):
        super().__init__(local_blocks=local_blocks, **kwargs)
        self._required = required

    @property
    def required(self):
        return self._required

    def get_context(self, value, parent_context=None):
        context = super().get_context(value, parent_context)
        page = value.get('page')
        if page is not None:
            page = TranslatablePageMixin.get_translated_page(page.specific)
        link = value.get('link')
        context.update({
            'href': page.url if page else link,
            'external': not bool(page),
            'text': value.get('name') or _('Read more'),
            'align': value.get('alignment'),
            'button': value.get('button'),
        })
        return context

    def clean(self, value):
        at_lest_one_field_required_fields = ['page', 'link']
        if self.required and not any([
                bool(value.get(field))
                for field in at_lest_one_field_required_fields
        ]):
            error_message = 'At least one of {} is required'.format(
                at_lest_one_field_required_fields)
            errors = {
                field: ErrorList([error_message])
                for field in at_lest_one_field_required_fields
            }
            raise ValidationError(error_message, params=errors)
        return super().clean(value)

    class Meta:
        icon = 'link'
        label = _('Read more')
        template = 'widgets/read-more-link.html'
        help_text = 'Choose either a page or an external link'