Beispiel #1
0
class AnswerPage(CFGOVPage):
    """
    Page type for Ask CFPB answers.
    """
    from ask_cfpb.models import Answer
    question = RichTextField(blank=True, editable=False)
    answer = RichTextField(blank=True, editable=False)
    snippet = RichTextField(
        blank=True, help_text='Optional answer intro', editable=False)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    publish_date = models.DateTimeField(default=timezone.now)
    answer_base = models.ForeignKey(
        Answer,
        blank=True,
        null=True,
        related_name='answer_pages',
        on_delete=models.SET_NULL)
    redirect_to = models.ForeignKey(
        Answer,
        blank=True,
        null=True,
        on_delete=models.SET_NULL,
        related_name='redirected_pages',
        help_text="Choose another Answer to redirect this page to")

    content = StreamField([
        ('feedback', v1_blocks.Feedback()),
    ], blank=True)

    content_panels = CFGOVPage.content_panels + [
        FieldPanel('redirect_to'),
    ]

    sidebar = StreamField([
        ('call_to_action', molecules.CallToAction()),
        ('related_links', molecules.RelatedLinks()),
        ('related_metadata', molecules.RelatedMetadata()),
        ('email_signup', organisms.EmailSignUp()),
        ('sidebar_contact', organisms.SidebarContactInfo()),
        ('rss_feed', molecules.RSSFeed()),
        ('social_media', molecules.SocialMedia()),
        ('reusable_text', v1_blocks.ReusableTextChooserBlock(ReusableText)),
    ], blank=True)

    sidebar_panels = [StreamFieldPanel('sidebar'), ]

    search_fields = Page.search_fields + [
        index.SearchField('question'),
        index.SearchField('answer'),
        index.SearchField('answer_base'),
        index.FilterField('language')
    ]

    edit_handler = TabbedInterface([
        ObjectList(content_panels, heading='Content'),
        ObjectList(sidebar_panels, heading='Sidebar (English only)'),
        ObjectList(CFGOVPage.settings_panels, heading='Configuration'),
    ])

    objects = CFGOVPageManager()

    def get_context(self, request, *args, **kwargs):
        context = super(AnswerPage, self).get_context(request)
        context['answer_id'] = self.answer_base.id
        context['related_questions'] = self.answer_base.related_questions.all()
        context['description'] = self.snippet if self.snippet \
            else Truncator(self.answer).words(40, truncate=' ...')
        context['audiences'] = [
            {'text': audience.name,
             'url': '/ask-cfpb/audience-{}'.format(
                    slugify(audience.name))}
            for audience in self.answer_base.audiences.all()]
        if self.language == 'es':
            tag_dict = self.Answer.valid_tags(language='es')
            context['tags_es'] = [tag for tag in self.answer_base.tags_es
                                  if tag in tag_dict['valid_tags']]
            context['tweet_text'] = Truncator(self.question).chars(
                100, truncate=' ...')
            context['disclaimer'] = get_reusable_text_snippet(
                SPANISH_DISCLAIMER_SNIPPET_TITLE)
            context['category'] = self.answer_base.category.first()
        elif self.language == 'en':
            # we're not using tags on English pages yet, so cut the overhead
            # tag_dict = self.Answer.valid_tags()
            # context['tags'] = [tag for tag in self.answer_base.tags
            #                    if tag in tag_dict['valid_tags']]
            context['about_us'] = get_reusable_text_snippet(
                ABOUT_US_SNIPPET_TITLE)
            context['disclaimer'] = get_reusable_text_snippet(
                ENGLISH_DISCLAIMER_SNIPPET_TITLE)
            context['last_edited'] = self.answer_base.last_edited
            # breadcrumbs and/or category should reflect
            # the referrer if it is a consumer tools portal or
            # ask category page
            context['category'], context['breadcrumb_items'] = \
                get_question_referrer_data(
                    request, self.answer_base.category.all())
            subcategories = []
            for subcat in self.answer_base.subcategory.all():
                if subcat.parent == context['category']:
                    subcategories.append(subcat)
                for related in subcat.related_subcategories.all():
                    if related.parent == context['category']:
                        subcategories.append(related)
            context['subcategories'] = set(subcategories)

        return context

    def get_template(self, request):
        printable = request.GET.get('print', False)
        if self.language == 'es':
            if printable == 'true':
                return 'ask-cfpb/answer-page-spanish-printable.html'

            return 'ask-cfpb/answer-page-spanish.html'

        return 'ask-cfpb/answer-page.html'

    def __str__(self):
        if self.answer_base:
            return '{}: {}'.format(self.answer_base.id, self.title)
        else:
            return self.title

    @property
    def status_string(self):
        if self.redirect_to:
            if not self.live:
                return _("redirected but not live")
            else:
                return _("redirected")
        else:
            return super(AnswerPage, self).status_string

    # Returns an image for the page's meta Open Graph tag
    @property
    def meta_image(self):
        if self.answer_base.social_sharing_image:
            return self.answer_base.social_sharing_image

        if not self.answer_base.category.exists():
            return None

        return self.answer_base.category.first().category_image
Beispiel #2
0
class HomePage(Page):
    """
    The Home Page. This looks slightly more complicated than it is. You can
    see if you visit your site and edit the homepage that it is split between
    a:
    - Hero area
    - Body area
    - A promotional area
    - Moveable featured site sections
    """

    # Hero section of HomePage
    image = models.ForeignKey('wagtailimages.Image',
                              null=True,
                              blank=True,
                              on_delete=models.SET_NULL,
                              related_name='+',
                              help_text='Homepage image')
    hero_text = models.CharField(
        max_length=255, help_text='Write an introduction for the bakery')
    hero_cta = models.CharField(verbose_name='Hero CTA',
                                max_length=255,
                                help_text='Text to display on Call to Action')
    hero_cta_link = models.ForeignKey(
        'wagtailcore.Page',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
        verbose_name='Hero CTA link',
        help_text='Choose a page to link to for the Call to Action')

    # Body section of the HomePage
    body = StreamField(BaseStreamBlock(),
                       verbose_name="Home content block",
                       blank=True)

    # Promo section of the HomePage
    promo_image = models.ForeignKey('wagtailimages.Image',
                                    null=True,
                                    blank=True,
                                    on_delete=models.SET_NULL,
                                    related_name='+',
                                    help_text='Promo image')
    promo_title = models.CharField(
        null=True,
        blank=True,
        max_length=255,
        help_text='Title to display above the promo copy')
    promo_text = RichTextField(null=True,
                               blank=True,
                               help_text='Write some promotional copy')

    # Featured sections on the HomePage
    # You will see on templates/base/home_page.html that these are treated
    # in different ways, and displayed in different areas of the page.
    # Each list their children items that we access via the children function
    # that we define on the individual Page models e.g. BlogIndexPage
    featured_section_1_title = models.CharField(
        null=True,
        blank=True,
        max_length=255,
        help_text='Title to display above the promo copy')
    featured_section_1 = models.ForeignKey(
        'wagtailcore.Page',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
        help_text='First featured section for the homepage. Will display up to '
        'three child items.',
        verbose_name='Featured section 1')

    featured_section_2_title = models.CharField(
        null=True,
        blank=True,
        max_length=255,
        help_text='Title to display above the promo copy')
    featured_section_2 = models.ForeignKey(
        'wagtailcore.Page',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
        help_text='Second featured section for the homepage. Will display up to '
        'three child items.',
        verbose_name='Featured section 2')

    featured_section_3_title = models.CharField(
        null=True,
        blank=True,
        max_length=255,
        help_text='Title to display above the promo copy')
    featured_section_3 = models.ForeignKey(
        'wagtailcore.Page',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
        help_text='Third featured section for the homepage. Will display up to '
        'six child items.',
        verbose_name='Featured section 3')

    content_panels = Page.content_panels + [
        MultiFieldPanel([
            ImageChooserPanel('image'),
            FieldPanel('hero_text', classname="full"),
            MultiFieldPanel([
                FieldPanel('hero_cta'),
                PageChooserPanel('hero_cta_link'),
            ])
        ],
                        heading="Hero section"),
        MultiFieldPanel([
            ImageChooserPanel('promo_image'),
            FieldPanel('promo_title'),
            FieldPanel('promo_text'),
        ],
                        heading="Promo section"),
        StreamFieldPanel('body'),
        MultiFieldPanel([
            MultiFieldPanel([
                FieldPanel('featured_section_1_title'),
                PageChooserPanel('featured_section_1'),
            ]),
            MultiFieldPanel([
                FieldPanel('featured_section_2_title'),
                PageChooserPanel('featured_section_2'),
            ]),
            MultiFieldPanel([
                FieldPanel('featured_section_3_title'),
                PageChooserPanel('featured_section_3'),
            ])
        ],
                        heading="Featured homepage sections",
                        classname="collapsible")
    ]

    def __str__(self):
        return self.title
Beispiel #3
0
class HomePage(Page):
    body = RichTextField(blank=True)

    content_panels = Page.content_panels + [
        FieldPanel('body', classname="full")
    ]
Beispiel #4
0
class EventPage(Page, SocialFields, ListingFields):
    start_date = models.DateField()
    start_time = models.TimeField(blank=True, null=True)
    # Permit null=True on end_date, as we use Coalesce to query 'end_date or start_date'
    end_date = models.DateField(blank=True, null=True)
    end_time = models.TimeField(blank=True, null=True)

    street_address_1 = models.CharField(_('Street Address 1'),
                                        blank=True,
                                        max_length=255)
    street_address_2 = models.CharField(_('Street Address 2'),
                                        blank=True,
                                        max_length=255)
    city = models.CharField(_('City'), blank=True, max_length=255)
    region = models.CharField(_('State or county'), blank=True, max_length=255)
    postcode = models.CharField(_('Zip or postal code'),
                                blank=True,
                                max_length=255)
    country = models.CharField(_('Country'), blank=True, max_length=255)
    phone = models.CharField(_('Phone'), blank=True, max_length=255)

    introduction = RichTextField(blank=True)
    body = StreamField(StoryBlock())

    subpage_types = []
    parent_page_types = ['events.EventIndexPage']

    search_fields = Page.search_fields + [
        index.SearchField('introduction'),
        index.SearchField('body'),
    ]

    content_panels = Page.content_panels + [
        MultiFieldPanel([
            FieldRowPanel([
                FieldPanel('start_date'),
                FieldPanel('start_time'),
            ])
        ],
                        heading='Start'),
        MultiFieldPanel([
            FieldRowPanel([
                FieldPanel('end_date'),
                FieldPanel('end_time'),
            ])
        ],
                        heading='End'),
        InlinePanel('event_types', label="Event types"),
        MultiFieldPanel([
            FieldPanel('street_address_1'),
            FieldPanel('street_address_2'),
            FieldPanel('city'),
            FieldPanel('region'),
            FieldPanel('postcode'),
            FieldPanel('country'),
            FieldPanel('phone'),
        ], _('Location')),
        FieldPanel('introduction'),
        StreamFieldPanel('body'),
        InlinePanel('related_pages', label="Related pages"),
    ]

    promote_panels = (Page.promote_panels + SocialFields.promote_panels +
                      ListingFields.promote_panels)

    def clean_fields(self, exclude=None):
        errors = defaultdict(list)
        try:
            super().clean_fields(exclude)
        except ValidationError as e:
            errors.update(e.message_dict)

        # Require start time if there's an end time
        if self.end_time and not self.start_time:
            errors["start_time"].append(
                _("If you enter an end time, you must also enter a start time")
            )

        if self.end_date and self.end_date < self.start_date:
            errors["end_date"].append(
                _("Events involving time travel are not supported"))
        elif self.end_date == self.start_date and self.end_time and self.end_time < self.start_time:
            errors["end_time"].append(
                _("Events involving time travel are not supported"))

        if errors:
            raise ValidationError(errors)

    def get_context(self, request, *args, **kwargs):
        context = super().get_context(request, *args, **kwargs)

        # Access siblings like this to get the same order as on the index page
        context['sidebar_pages'] = self.get_parent().specific.upcoming_events

        return context
Beispiel #5
0
class ActivityPage(CFGOVPage):
    """
    A model for the Activity Detail page.
    """
    # Allow Activity pages to exist under the ActivityIndexPage or the Trash
    parent_page_types = [ActivityIndexPage, HomePage]
    subpage_types = []
    objects = CFGOVPageManager()

    date = models.DateField('Updated', default=timezone.now)
    summary = models.TextField('Summary', blank=False)
    big_idea = RichTextField('Big idea', blank=False)
    essential_questions = RichTextField('Essential questions', blank=False)
    objectives = RichTextField('Objectives', blank=False)
    what_students_will_do = RichTextField('What students will do', blank=False)  # noqa: E501
    activity_file = models.ForeignKey(
        'wagtaildocs.Document',
        null=True,
        blank=False,
        on_delete=models.SET_NULL,
        related_name='+',
        verbose_name='Teacher guide'
    )
    # TODO: to figure out how to use Document choosers on ManyToMany fields
    handout_file = models.ForeignKey(
        'wagtaildocs.Document',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
        verbose_name='Student file 1'
    )
    handout_file_2 = models.ForeignKey(
        'wagtaildocs.Document',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
        verbose_name='Student file 2'
    )
    handout_file_3 = models.ForeignKey(
        'wagtaildocs.Document',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
        verbose_name='Student file 3'
    )
    building_block = ParentalManyToManyField('teachers_digital_platform.ActivityBuildingBlock', blank=False)  # noqa: E501
    school_subject = ParentalManyToManyField('teachers_digital_platform.ActivitySchoolSubject', blank=False)  # noqa: E501
    topic = ParentalTreeManyToManyField('teachers_digital_platform.ActivityTopic', blank=False)  # noqa: E501
    # Audience
    grade_level = ParentalManyToManyField('teachers_digital_platform.ActivityGradeLevel', blank=False)  # noqa: E501
    age_range = ParentalManyToManyField('teachers_digital_platform.ActivityAgeRange', blank=False)  # noqa: E501
    student_characteristics = ParentalManyToManyField('teachers_digital_platform.ActivityStudentCharacteristics', blank=True)  # noqa: E501
    # Activity Characteristics
    activity_type = ParentalManyToManyField('teachers_digital_platform.ActivityType', blank=False)  # noqa: E501
    teaching_strategy = ParentalManyToManyField('teachers_digital_platform.ActivityTeachingStrategy', blank=False)  # noqa: E501
    blooms_taxonomy_level = ParentalManyToManyField('teachers_digital_platform.ActivityBloomsTaxonomyLevel', blank=False)  # noqa: E501
    activity_duration = models.ForeignKey(ActivityDuration, blank=False, on_delete=models.PROTECT)  # noqa: E501
    # Standards taught
    jump_start_coalition = ParentalManyToManyField(
        'teachers_digital_platform.ActivityJumpStartCoalition',
        blank=True,
        verbose_name='Jump$tart Coalition',
    )
    council_for_economic_education = ParentalManyToManyField(
        'teachers_digital_platform.ActivityCouncilForEconEd',
        blank=True,
        verbose_name='Council for Economic Education',
    )
    content_panels = CFGOVPage.content_panels + [
        FieldPanel('date'),
        FieldPanel('summary'),
        FieldPanel('big_idea'),
        FieldPanel('essential_questions'),
        FieldPanel('objectives'),
        FieldPanel('what_students_will_do'),
        MultiFieldPanel(
            [
                DocumentChooserPanel('activity_file'),
                DocumentChooserPanel('handout_file'),
                DocumentChooserPanel('handout_file_2'),
                DocumentChooserPanel('handout_file_3'),
            ],
            heading="Download activity",
        ),
        FieldPanel('building_block', widget=forms.CheckboxSelectMultiple),
        FieldPanel('school_subject', widget=forms.CheckboxSelectMultiple),
        FieldPanel('topic', widget=forms.CheckboxSelectMultiple),

        MultiFieldPanel(
            [
                FieldPanel('grade_level', widget=forms.CheckboxSelectMultiple),  # noqa: E501
                FieldPanel('age_range', widget=forms.CheckboxSelectMultiple),
                FieldPanel('student_characteristics', widget=forms.CheckboxSelectMultiple),  # noqa: E501
            ],
            heading="Audience",
        ),
        MultiFieldPanel(
            [
                FieldPanel('activity_type', widget=forms.CheckboxSelectMultiple),  # noqa: E501
                FieldPanel('teaching_strategy', widget=forms.CheckboxSelectMultiple),  # noqa: E501
                FieldPanel('blooms_taxonomy_level', widget=forms.CheckboxSelectMultiple),  # noqa: E501
                FieldPanel('activity_duration'),
            ],
            heading="Activity characteristics",
        ),
        MultiFieldPanel(
            [
                FieldPanel('council_for_economic_education', widget=forms.CheckboxSelectMultiple),  # noqa: E501
                FieldPanel('jump_start_coalition', widget=forms.CheckboxSelectMultiple),  # noqa: E501
            ],
            heading="National standards",
        ),
    ]

    edit_handler = TabbedInterface([
        ObjectList(content_panels, heading='General Content'),
        ObjectList(CFGOVPage.sidefoot_panels, heading='Sidebar/Footer'),
        ObjectList(CFGOVPage.settings_panels, heading='Configuration'),
    ])

    # admin use only
    search_fields = Page.search_fields + [
        index.SearchField('summary'),
        index.SearchField('big_idea'),
        index.SearchField('essential_questions'),
        index.SearchField('objectives'),
        index.SearchField('what_students_will_do'),
        index.FilterField('date'),
        index.FilterField('building_block'),
        index.FilterField('school_subject'),
        index.FilterField('topic'),
        index.FilterField('grade_level'),
        index.FilterField('age_range'),
        index.FilterField('student_characteristics'),
        index.FilterField('activity_type'),
        index.FilterField('teaching_strategy'),
        index.FilterField('blooms_taxonomy_level'),
        index.FilterField('activity_duration'),
        index.FilterField('jump_start_coalition'),
        index.FilterField('council_for_economic_education'),
    ]

    def get_subtopic_ids(self):
        """
        Get a list of this activity's subtopic ids
        """
        topic_ids = [topic.id for topic in self.topic.all()]
        root_ids = ActivityTopic.objects \
            .filter(id__in=topic_ids, parent=None) \
            .values_list('id', flat=True)
        return set(topic_ids) - set(root_ids)

    def get_grade_level_ids(self):
        """
        Get a list of this activity's grade_level ids
        """
        grade_level_ids = [
            grade_level.id for grade_level in self.grade_level.all()
        ]
        return grade_level_ids

    def get_related_activities_url(self):
        """
        Generate a search url for related Activities by
        subtopic and grade-level
        """
        parent_page = self.get_parent()
        subtopic_ids = [str(x) for x in self.get_subtopic_ids()]
        grade_level_ids = [str(y) for y in self.get_grade_level_ids()]

        url = parent_page.get_url() + '?q='
        if subtopic_ids:
            subtopics = '&topic=' + \
                        '&topic='.join(subtopic_ids)
            url += subtopics
        if grade_level_ids:
            grade_levels = '&grade_level=' + \
                           '&grade_level='.join(grade_level_ids)
            url += grade_levels
        return url

    def get_topics_list(self, parent=None):
        """
        Get a hierarchical list of this activity's topics.

        parent: ActivityTopic
        """
        if parent:
            descendants = set(parent.get_descendants()) & set(self.topic.all())  # noqa: E501
            children = parent.get_children()
            children_list = []
            # If this parent has descendants in self.topic, add its children.
            if descendants:
                for child in children:
                    if set(child.get_descendants()) & set(self.topic.all()):
                        children_list.append(self.get_topics_list(child))
                    elif child in self.topic.all():
                        children_list.append(child.title)

                if children_list:
                    return parent.title + " (" + ', '.join(children_list) + ")"  # noqa: E501
            # Otherwise, just add the parent.
            else:
                return parent.title
        else:
            # Build root list of topics and recurse their children.
            topic_list = []
            topic_ids = [topic.id for topic in self.topic.all()]
            ancestors = ActivityTopic.objects.filter(id__in=topic_ids).get_ancestors(True)  # noqa: E501
            roots = ActivityTopic.objects.filter(parent=None) & ancestors
            for root_topic in roots:
                topic_list.append(self.get_topics_list(root_topic))

            if topic_list:
                return ', '.join(topic_list)
            else:
                return ''

    class Meta:
        verbose_name = "TDP Activity page"
Beispiel #6
0
class Page(WagtailPage):
    body = RichTextField(blank=True)

    content_panels = WagtailPage.content_panels + [
        FieldPanel('body', classname='full')
    ]
Beispiel #7
0
class ContributorPage(Page):
    first_name = models.CharField(max_length=255, blank=True, default="")
    last_name = models.CharField(max_length=255, blank=True, default="")
    nickname = models.CharField(max_length=1024, blank=True, default="")

    email = models.EmailField(blank=True, default="")
    twitter_handle = models.CharField(max_length=16, blank=True, default="")

    subtitle = RichTextField(blank=True, default="")
    short_bio = RichTextField(blank=True, default="")
    long_bio = RichTextField(blank=True, default="")

    headshot = models.ForeignKey('images.AttributedImage',
                                 null=True,
                                 blank=True,
                                 on_delete=models.SET_NULL,
                                 related_name='+')
    featured = models.BooleanField(default=False)

    search_fields = Page.search_fields + [
        index.SearchField('first_name', partial_match=True),
        index.SearchField('last_name', partial_match=True),
        index.SearchField('twitter_handle', partial_match=True),
        index.SearchField('subtitle', partial_match=True),
        index.SearchField('short_bio', partial_match=True),
        index.SearchField('long_bio', partial_match=True),
    ]

    def search_result_text(self):
        if self.subtitle:
            self.search_result_text = self.subtitle
        elif self.short_bio:
            self.search_result_text = self.short_bio
        else:
            self.search_result_text = self.long_bio
        return self.search_result_text

    def save(self, *args, **kwargs):
        if self.twitter_handle and not self.twitter_handle.startswith("@"):
            self.twitter_handle = "@{}".format(self.twitter_handle)
        super(ContributorPage, self).save(*args, **kwargs)

    @property
    def full_name(self):
        return "{} {}".format(self.first_name, self.last_name)

    @property
    def last_comma_first_name(self):
        return "{}, {}".format(self.last_name, self.first_name)

    @property
    def display_twitter_handle(self):
        if self.twitter_handle:
            return self.twitter_handle[1:]
        return self.twitter_handle

    def __str__(self):
        return "{} {} - {}".format(self.first_name, self.last_name, self.email)

    def get_admin_display_title(self):
        return '{} ({})'.format(self.title, self.id)

    content_panels = Page.content_panels + [
        FieldPanel('first_name'),
        FieldPanel('last_name'),
        FieldPanel('email'),
        FieldPanel('twitter_handle'),
        RichTextFieldPanel('short_bio'),
        RichTextFieldPanel('long_bio'),
        ImageChooserPanel('headshot'),
    ]

    promote_panels = Page.promote_panels + [
        MultiFieldPanel([
            FieldPanel('featured'),
        ],
                        heading="Featuring Settings")
    ]

    edit_handler = TabbedInterface([
        ObjectList(content_panels, heading='Content'),
        ObjectList(promote_panels, heading='Promote'),
        ObjectList(Page.settings_panels,
                   heading='Settings',
                   classname="settings"),
    ])
Beispiel #8
0
class SignalSource(models.Model):
    signal = ParentalKey('Signal', related_name='signal_sources')
    source = RichTextField(blank=False)

    class Meta:
        ordering = ('pk',)
Beispiel #9
0
class DriverOfChange(FlisPage):
    is_creatable = False

    image = models.ForeignKey(
        'pages.FlisImage',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+'
    )

    short_title = models.CharField(max_length=64)
    geographical_scope = models.ForeignKey(
        'flis_metadata.GeographicalScope',
        null=True,
        blank=True,
        on_delete=models.SET_NULL
    )
    country = models.ForeignKey(
        'flis_metadata.Country',
        null=True,
        blank=True,
        on_delete=models.SET_NULL
    )

    steep_category = models.ForeignKey(
        'SteepCategory',
        null=True,
        blank=True,
        on_delete=models.SET_NULL
    )
    time_horizon = models.ForeignKey(
        'TimeHorizon',
        null=True,
        blank=True,
        on_delete=models.SET_NULL
    )
    summary = RichTextField(null=True, blank=True)

    impacts = ParentalManyToManyField(
        'Impact',
        blank=True,
    )
    implications = ParentalManyToManyField(
        'Implication',
        blank=True,
    )
    indicators = ParentalManyToManyField(
        'Indicator',
        blank=True,
    )
    sources = ParentalManyToManyField(
        'Source',
        blank=True,
    )
    figures = ParentalManyToManyField(
        'Figure',
        blank=True,
    )

    content_panels = Page.content_panels + [
        ImageChooserPanel('image'),
        FieldPanel('short_title'),
        SnippetChooserPanel('geographical_scope'),
        SnippetChooserPanel('country'),
        SnippetChooserPanel('steep_category'),
        SnippetChooserPanel('time_horizon'),
        FieldPanel('summary'),
        FieldPanel('impacts', widget=autocomplete.ModelSelect2Multiple(url='impacts-autocomplete')),
        FieldPanel('implications', widget=autocomplete.ModelSelect2Multiple(url='implications-autocomplete')),
        FieldPanel('indicators', widget=autocomplete.ModelSelect2Multiple(url='indicators-autocomplete')),
        FieldPanel('sources', widget=autocomplete.ModelSelect2Multiple(url='sources-autocomplete')),
        FieldPanel('figures', widget=autocomplete.ModelSelect2Multiple(url='figures-autocomplete')),
    ]

    class Meta:
        verbose_name = 'Signal of Change'
        verbose_name_plural = 'Signals of Change'
Beispiel #10
0
class DashboardPage(RoutablePageWithDefault):
    impact_models_description = RichTextField(null=True, blank=True)
    content_panels = Page.content_panels + [
        RichTextFieldPanel('impact_models_description'),
    ]

    def get_context(self, request, *args, **kwargs):
        context = super().get_context(request, *args, **kwargs)
        base_impact_models = request.user.userprofile.owner.all().order_by(
            'name')
        if request.user.is_authenticated() and request.user.is_superuser:
            base_impact_models = BaseImpactModel.objects.all()
        impage = ImpactModelsPage.objects.get()
        impage_details = lambda imid: "<span class='action'><a href='{0}' class=''>{{0}}</a></span>".format(
            impage.url + impage.reverse_subpage('details', args=(imid, )))
        impage_edit = lambda imid: "<span class='action'><i class='fa fa-edit'></i> <a href='{0}' class=''>Edit model information for {{0}}</a></span>".format(
            impage.url + impage.reverse_subpage('edit_base', args=(imid, )))
        impage_create = lambda bmid, srid: "<span class='action'><i class='fa fa-file-o'></i> <a href='{0}' class=''>Enter ALL new model information for {{0}}</a></span>".format(
            impage.url + impage.reverse_subpage('create', args=(bmid, srid)))
        impage_duplicate = lambda imid, srid: "<span class='action'><i class='fa fa-files-o'></i> <a href='{0}' class=''>Use model information for {{0}} as starting point for {{1}}</a></span>".format(
            impage.url + impage.reverse_subpage('duplicate', args=(imid, srid)
                                                ))
        context['head'] = {
            'cols': [{
                'text': 'Model'
            }, {
                'text': 'Sector'
            }, {
                'text': 'Simulation round'
            }, {
                'text': 'Public'
            }, {
                'text': 'Action'
            }]
        }

        bodyrows = []
        for bims in base_impact_models:
            for imodel in bims.impact_model.all():
                values = [
                    [impage_details(bims.id).format(bims.name)],
                    [bims.sector.name],
                    [imodel.simulation_round.name],
                    [
                        '<i class="fa fa-{}" aria-hidden="true"></i>'.format(
                            'check' if imodel.public else 'times')
                    ],
                    [
                        impage_edit(imodel.id).format(
                            imodel.simulation_round.name)
                    ],
                ]
                row = {
                    'cols': [{
                        'texts': x
                    } for x in values],
                }
                bodyrows.append(row)
            duplicate_impact_model = bims.can_duplicate_from()
            for sr in bims.get_missing_simulation_rounds():
                duplicate_model_text = ''
                if duplicate_impact_model:
                    duplicate_model_text = impage_duplicate(
                        duplicate_impact_model.id,
                        sr.id).format(duplicate_impact_model.simulation_round,
                                      sr.name)
                values = [
                    [bims.name],
                    [bims.sector.name],
                    [sr.name],
                    [],
                    [
                        impage_create(bims.id, sr.id).format(sr.name),
                        duplicate_model_text
                    ],
                ]
                row = {
                    'cols': [{
                        'texts': x
                    } for x in values],
                }
                bodyrows.append(row)
        context['body'] = {'rows': bodyrows}
        if request.user.groups.filter(name='ISIMIP-Team').exists():
            context['show_participants_link'] = True
        return context

    @route(r'^$')
    def base(self, request):
        if not request.user.is_authenticated():
            messages.info(
                request,
                'This is a restricted area. To proceed you need to log in.')
            return HttpResponseRedirect(self.reverse_subpage('login'))

        return TemplateResponse(request, self.get_template(request),
                                self.get_context(request))

    @route(r'participants/$')
    def participants(self, request):
        if not request.user.is_authenticated():
            messages.info(
                request,
                'This is a restricted area. To proceed you need to log in.')
            return HttpResponseRedirect(self.reverse_subpage('login'))
        subpage = {'title': 'ISIMIP participants', 'url': ''}
        context = {'page': self, 'subpage': subpage, 'headline': ''}
        return show_participants(request, extra_context=context)

    @route(r'download/$')
    def download(self, request):
        if not request.user.is_authenticated():
            messages.info(
                request,
                'This is a restricted area. To proceed you need to log in.')
            return HttpResponseRedirect(self.reverse_subpage('login'))
        return participant_download(self, request)

    @route(r'logout/$')
    def logout(self, request):
        subpage = {'title': 'Logout', 'url': ''}
        context = {'page': self, 'subpage': subpage, 'headline': ''}
        return logout(request, extra_context=context)

    @route(r'login/$')
    def login(self, request):
        subpage = {'title': 'Login', 'url': ''}
        context = {'page': self, 'subpage': subpage, 'headline': ''}
        return login(request,
                     extra_context=context,
                     authentication_form=AuthenticationForm)

    @route(r'change-password/$')
    def change_password(self, request):
        if not request.user.is_authenticated():
            messages.info(
                request,
                'This is a restricted area. To proceed you need to log in.')
            return HttpResponseRedirect(self.reverse_subpage('login'))
        subpage = {'title': 'Change password', 'url': ''}
        context = {'page': self, 'subpage': subpage, 'headline': ''}
        return password_change(request, extra_context=context)

    @route(r'update-contact-information/$')
    def update_contact_information(self, request):
        if not request.user.is_authenticated():
            messages.info(
                request,
                'This is a restricted area. To proceed you need to log in.')
            return HttpResponseRedirect(self.reverse_subpage('login'))
        subpage = {'title': 'Update contact information', 'url': ''}
        context = {'page': self, 'subpage': subpage, 'headline': ''}
        return update_contact_information_view(request,
                                               self,
                                               extra_context=context)
Beispiel #11
0
class Signal(FlisPage):
    class Meta:
        ordering = ('-first_published_at',)

    SIGNAL_TYPES = (
        ('megatrend', 'Megatrend'),
        ('trend', 'Trend'),
        ('weak_signal', 'Weak Signal'),
        ('wild_card', 'Wild Card'),
        ('other', 'Other'),
    )

    parent_page_types = ['pages.StaticIndex']

    short_title = models.CharField(max_length=256)

    type_of_signal = models.ForeignKey(
        'TypeOfSignal',
        null=True,
        blank=False,
        on_delete=models.SET_NULL
    )

    cover_image = models.ForeignKey(
        'pages.FlisImage',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+'
    )

    geographical_scope = models.ForeignKey(
        'GeographicalScope',
        null=True,
        blank=True,
        on_delete=models.SET_NULL
    )

    headline = models.TextField(max_length=256)

    description = RichTextField()

    origin_of_signal = models.ForeignKey(
        'OriginOfSignal',
        null=True,
        blank=True,
        on_delete=models.SET_NULL
    )

    time_horizon = models.ForeignKey(
        'TimeHorizon',
        null=True,
        blank=True,
        on_delete=models.SET_NULL
    )

    overall_impact = models.ForeignKey(
        'OverallImpact',
        null=True,
        blank=True,
        on_delete=models.SET_NULL
    )

    impact_description = RichTextField(blank=True, null=True)

    implications = RichTextField(blank=True, null=True)

    strategies = ParentalManyToManyField(
        'EUStrategy',
        blank=True,
    )

    date_of_signal_detection = models.DateField(null=True)
    date_of_last_modification = models.DateField(blank=True, null=True,
                                                 verbose_name='Date of last modification to the signal')

    likelihood = models.ForeignKey(
        'RelevanceOfSignalLikelihood',
        null=True,
        blank=True,
        on_delete=models.SET_NULL
    )

    severity = models.ForeignKey(
        'RelevanceOfSignalSeverity',
        null=True,
        blank=True,
        on_delete=models.SET_NULL
    )

    keywords = ClusterTaggableManager(through=SignalTag, blank=True)

    content_panels = [
        FieldPanel('short_title'),
        FieldPanel('type_of_signal', widget=FlisListModelSelect2),
        FieldPanel('title'),
        ImageChooserPanel('cover_image'),
        FieldPanel('geographical_scope', widget=FlisListModelSelect2),
        FieldPanel('headline', widget=CharsLeftArea),
        FieldPanel('description'),
        InlinePanel('images', label='Images'),
        FieldPanel('origin_of_signal', widget=FlisListModelSelect2),
        FieldPanel('time_horizon', widget=FlisListModelSelect2),
        FieldPanel('overall_impact', widget=FlisListModelSelect2),
        FieldPanel('impact_description'),
        FieldPanel('implications'),
        InlinePanel('signal_sources', label='Source'),
        InlinePanel('eea_indicators', label='Related EEA Indicator'),
        FieldPanel('overall_impact', widget=FlisListModelSelect2),
        FieldPanel('strategies', widget=Select2Multiple),
        FieldPanel('date_of_signal_detection'),
        FieldPanel('date_of_last_modification'),
        MultiFieldPanel([
            FieldPanel('likelihood'),
            FieldPanel('severity'),
        ], heading='Relevance of the signal'),
        FieldPanel('keywords', widget=TaggitSelect2(url='tags-autocomplete')),
    ]

    def save(self, *args, **kwargs):
        original = self.__class__.objects.get(pk=self.pk) if self.pk else None

        if any(
                [
                            original and self.date_of_last_modification == original.date_of_last_modification,
                            self.date_of_last_modification is None
                ]
        ):
            self.date_of_last_modification = timezone.now()

        super().save(*args, **kwargs)
Beispiel #12
0
class HomePage(RoutablePageWithDefault):
    parent_page_types = ['wagtailcore.Page']

    teaser_title = models.CharField(max_length=500)
    teaser_text = RichTextField()
    teaser_link_external = models.URLField(
        "External link",
        blank=True,
        help_text="Will be ignored if an internal link is provided")
    teaser_link_internal = models.ForeignKey(
        'wagtailcore.Page',
        verbose_name="Or internal link",
        help_text='If set, this has precedence over the external link.',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
    )

    content = StreamField([('row',
                            RowBlock([
                                ('teaser', SmallTeaserBlock()),
                                ('bigteaser', BigTeaserBlock(wideimage=True)),
                                ('blog', BlogBlock()),
                                ('numbers', IsiNumbersBlock()),
                                ('twitter', TwitterBlock()),
                            ]))])

    content_panels = Page.content_panels + [
        MultiFieldPanel([
            FieldPanel('teaser_title'),
            RichTextFieldPanel('teaser_text'),
            MultiFieldPanel([
                FieldPanel('teaser_link_external'),
                PageChooserPanel('teaser_link_internal'),
            ]),
        ],
                        heading='Teaser'),
        StreamFieldPanel('content'),
    ]

    search_fields = Page.search_fields + [
        index.SearchField('teaser_text'),
        index.SearchField('teaser_title', partial_match=True),
        index.SearchField('content'),
    ]

    def get_context(self, request, *args, **kwargs):
        context = super().get_context(request, *args, **kwargs)
        if self.teaser_link_internal:
            link = self.teaser_link_internal.url
        else:
            link = self.teaser_link_external
        context['teaser'] = {
            'title': self.teaser_title,
            'text': self.teaser_text,
            'button': {
                'href': link,
                'text': 'Read more',
                'fontawesome': 'facebook',
            }
        }
        context['noborder'] = True
        return context

    @route(r'search/$')
    def search(self, request):
        subpage = {'title': 'Search', 'url': ''}
        context = {'page': self, 'subpage': subpage, 'headline': ''}
        return search(request, extra_context=context)
Beispiel #13
0
class AbstractFilterPage(CFGOVPage):
    header = StreamField([
        ('article_subheader', blocks.RichTextBlock(icon='form')),
        ('text_introduction', molecules.TextIntroduction()),
        ('item_introduction', organisms.ItemIntroduction()),
    ],
                         blank=True)
    preview_title = models.CharField(max_length=255, null=True, blank=True)
    preview_subheading = models.CharField(max_length=255,
                                          null=True,
                                          blank=True)
    preview_description = RichTextField(null=True, blank=True)
    secondary_link_url = models.CharField(max_length=500,
                                          null=True,
                                          blank=True)
    secondary_link_text = models.CharField(max_length=255,
                                           null=True,
                                           blank=True)
    preview_image = models.ForeignKey('v1.CFGOVImage',
                                      null=True,
                                      blank=True,
                                      on_delete=models.SET_NULL,
                                      related_name='+')
    date_published = models.DateField(default=date.today)
    date_filed = models.DateField(null=True, blank=True)
    comments_close_by = models.DateField(null=True, blank=True)

    # Configuration tab panels
    settings_panels = [
        MultiFieldPanel(CFGOVPage.promote_panels, 'Settings'),
        InlinePanel('categories', label="Categories", max_num=2),
        FieldPanel('tags', 'Tags'),
        MultiFieldPanel([
            FieldPanel('preview_title', classname="full"),
            FieldPanel('preview_subheading', classname="full"),
            FieldPanel('preview_description', classname="full"),
            FieldPanel('secondary_link_url', classname="full"),
            FieldPanel('secondary_link_text', classname="full"),
            ImageChooserPanel('preview_image'),
        ],
                        heading='Page Preview Fields',
                        classname='collapsible'),
        FieldPanel('authors', 'Authors'),
        MultiFieldPanel([
            FieldPanel('date_published'),
            FieldPanel('date_filed'),
            FieldPanel('comments_close_by'),
        ],
                        'Relevant Dates',
                        classname='collapsible'),
        MultiFieldPanel(Page.settings_panels, 'Scheduled Publishing'),
    ]

    # This page class cannot be created.
    is_creatable = False

    objects = CFGOVPageManager()

    search_fields = CFGOVPage.search_fields + [index.SearchField('header')]

    @classmethod
    def generate_edit_handler(self, content_panel):
        content_panels = [
            StreamFieldPanel('header'),
            content_panel,
        ]
        return TabbedInterface([
            ObjectList(self.content_panels + content_panels,
                       heading='General Content'),
            ObjectList(CFGOVPage.sidefoot_panels, heading='Sidebar'),
            ObjectList(self.settings_panels, heading='Configuration'),
        ])

    # Returns an image for the page's meta Open Graph tag
    @property
    def meta_image(self):
        parent_meta = super(AbstractFilterPage, self).meta_image
        return parent_meta or self.preview_image
Beispiel #14
0
class EventPage(AbstractFilterPage):
    # General content fields
    body = RichTextField('Subheading', blank=True)
    archive_body = RichTextField(blank=True)
    live_body = RichTextField(blank=True)
    future_body = RichTextField(blank=True)
    start_dt = models.DateTimeField("Start", blank=True, null=True)
    end_dt = models.DateTimeField("End", blank=True, null=True)
    future_body = RichTextField(blank=True)
    archive_image = models.ForeignKey('wagtailimages.Image',
                                      null=True,
                                      blank=True,
                                      on_delete=models.SET_NULL,
                                      related_name='+')
    video_transcript = models.ForeignKey('wagtaildocs.Document',
                                         null=True,
                                         blank=True,
                                         on_delete=models.SET_NULL,
                                         related_name='+')
    speech_transcript = models.ForeignKey('wagtaildocs.Document',
                                          null=True,
                                          blank=True,
                                          on_delete=models.SET_NULL,
                                          related_name='+')
    flickr_url = models.URLField("Flickr URL", blank=True)
    youtube_url = models.URLField(
        "Youtube URL",
        blank=True,
        help_text="Format: https://www.youtube.com/embed/video_id. "
        "It can be obtained by clicking on Share > "
        "Embed on Youtube.",
        validators=[
            RegexValidator(regex=r'^https?:\/\/www\.youtube\.com\/embed\/.*$')
        ])

    live_stream_availability = models.BooleanField("Streaming?",
                                                   default=False,
                                                   blank=True)
    live_stream_url = models.URLField(
        "URL",
        blank=True,
        help_text="Format: https://www.youtube.com/embed/video_id.")
    live_stream_date = models.DateTimeField("Go Live Date",
                                            blank=True,
                                            null=True)
    # Venue content fields
    venue_name = models.CharField(max_length=100, blank=True)
    venue_street = models.CharField(max_length=100, blank=True)
    venue_suite = models.CharField(max_length=100, blank=True)
    venue_city = models.CharField(max_length=100, blank=True)
    venue_state = USStateField(blank=True)
    venue_zip = models.IntegerField(blank=True, null=True)
    agenda_items = StreamField([('item', AgendaItemBlock())], blank=True)

    objects = CFGOVPageManager()

    search_fields = AbstractFilterPage.search_fields + [
        index.SearchField('body'),
        index.SearchField('archive_body'),
        index.SearchField('live_stream_url'),
        index.SearchField('flickr_url'),
        index.SearchField('youtube_url'),
        index.SearchField('future_body'),
        index.SearchField('agenda_items')
    ]

    # General content tab
    content_panels = CFGOVPage.content_panels + [
        FieldPanel('body', classname="full"),
        FieldRowPanel([
            FieldPanel('start_dt', classname="col6"),
            FieldPanel('end_dt', classname="col6"),
        ]),
        MultiFieldPanel([
            FieldPanel('archive_body', classname="full"),
            ImageChooserPanel('archive_image'),
            DocumentChooserPanel('video_transcript'),
            DocumentChooserPanel('speech_transcript'),
            FieldPanel('flickr_url'),
            FieldPanel('youtube_url'),
        ],
                        heading='Archive Information'),
        FieldPanel('live_body', classname="full"),
        FieldPanel('future_body', classname="full"),
        MultiFieldPanel([
            FieldPanel('live_stream_availability'),
            FieldPanel('live_stream_url'),
            FieldPanel('live_stream_date'),
        ],
                        heading='Live Stream Information'),
    ]
    # Venue content tab
    venue_panels = [
        FieldPanel('venue_name'),
        MultiFieldPanel([
            FieldPanel('venue_street'),
            FieldPanel('venue_suite'),
            FieldPanel('venue_city'),
            FieldPanel('venue_state'),
            FieldPanel('venue_zip'),
        ],
                        heading='Venue Address'),
    ]
    # Agenda content tab
    agenda_panels = [
        StreamFieldPanel('agenda_items'),
    ]
    # Promotion panels
    promote_panels = [
        MultiFieldPanel(AbstractFilterPage.promote_panels,
                        "Page configuration"),
    ]
    # Tab handler interface
    edit_handler = TabbedInterface([
        ObjectList(content_panels, heading='General Content'),
        ObjectList(venue_panels, heading='Venue Information'),
        ObjectList(agenda_panels, heading='Agenda Information'),
        ObjectList(AbstractFilterPage.sidefoot_panels, heading='Sidebar'),
        ObjectList(AbstractFilterPage.settings_panels,
                   heading='Configuration'),
    ])

    template = 'events/event.html'

    @property
    def page_js(self):
        return super(EventPage, self).page_js + ['video-player.js']

    def location_image_url(self, scale='2', size='276x155', zoom='12'):
        center = 'Washington, DC'
        if self.venue_city:
            center = self.venue_city
        if self.venue_state:
            center = center + ', ' + self.venue_state
        options = {
            'center': center,
            'scale': scale,
            'size': size,
            'zoom': zoom
        }
        url = 'https://maps.googleapis.com/maps/api/staticmap?'
        return '{url}{options}'.format(url=url, options=urlencode(options))
Beispiel #15
0
class HomePage(Page):
    body = RichTextField(null=True, blank=True)

    content_panels = Page.content_panels + [
        FieldPanel('body'),
    ]
Beispiel #16
0
class Month(index.Indexed, models.Model):
    """
    These are the months that can be selected from other models and given theme
    """
    MONTHS = (
        (1, 'January'),
        (2, 'Febuary'),
        (3, 'March'),
        (4, 'April'),
        (5, 'May'),
        (6, 'June'),
        (7, 'July'),
        (8, 'August'),
        (9, 'September'),
        (10, 'October'),
        (11, 'November'),
        (12, 'December'),
    )

    theme = models.CharField(max_length=60, help_text="Month theme label")
    month = models.IntegerField(choices=MONTHS)
    year = models.IntegerField(validators=[MinValueValidator(2010)])
    description = RichTextField(
        help_text="Text to display in sidebar theme box",
        blank=True)

    leader = models.ForeignKey(
        'articles.Article',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='leader',
        help_text='Leader of the Month',
        limit_choices_to={'live': True}
    )
    feature_girl_1 = models.ForeignKey(
        'articles.Article',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='feature_girl_1',
        help_text='Feature Girl #1'
    )
    feature_girl_2 = models.ForeignKey(
        'articles.Article',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='feature_girl_2',
        help_text='Feature Girl #2'
    )
    music_spotlight = models.ForeignKey(
        'articles.Article',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='music_spotlight',
        help_text='Music Spotlight'
    )

    # Search indexing
    search_fields = [
        index.SearchField('theme', partial_match=True, boost=10),
        index.FilterField('year'),
    ]

    class Meta:
        unique_together = (('month', 'year'), )

    def __str__(self):
        return self.MONTHS[self.month - 1][1] + ' ' + str(self.year) + \
            ' ' + self.theme

    def string_date(self):
        return self.MONTHS[self.month - 1][1] + ' ' + str(self.year)

    def featured_articles(self):
        articles = []
        if self.leader:
            articles.append(('LEADER OF THE MONTH', self.leader))
        if self.feature_girl_1:
            articles.append(('FEATURE GIRL #1', self.feature_girl_1))
        if self.feature_girl_2:
            articles.append(('FEATURE GIRL #2', self.feature_girl_2))
        if self.music_spotlight:
            articles.append(('MUSIC SPOTLIGHT', self.music_spotlight))
        return articles

    def get_absolute_url(self):
        return reverse('month', kwargs={
            'year': self.year,
            'month': self.month})
Beispiel #17
0
class StaticPage(AbstractJinjaPage, Page):
    body = RichTextField(verbose_name="Текст сторінки")
    template = "cms_pages/static_page.jinja"

    class Meta:
        verbose_name = "Статична сторінка"
Beispiel #18
0
class EventPage(Page):
    date_from = models.DateField("Start date")
    date_to = models.DateField(
        _("End date"),
        null=True,
        blank=True,
        help_text=_("Not required if event is on a single day"),
    )
    time_from = models.TimeField(_("Start time"), null=True, blank=True)
    time_to = models.TimeField(_("End time"), null=True, blank=True)
    audience = models.CharField(max_length=255, choices=EVENT_AUDIENCE_CHOICES)
    location = models.CharField(max_length=255)

    intro_en = RichTextField(blank=True)
    intro_tet = RichTextField(blank=True)
    intro = TranslatedField("intro_en", "intro_tet")
    body_en = RichTextField(blank=True)
    body_tet = RichTextField(blank=True)
    body = TranslatedField("body_en", "body_tet")

    title_en = RichTextField(blank=True)
    title_tet = RichTextField(blank=True)
    title = TranslatedField("title_en", "title_tet")
    cost = models.CharField(max_length=255)
    signup_link = models.URLField(blank=True)
    feed_image = models.ForeignKey(
        "wagtailimages.Image",
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="+",
    )

    search_fields = Page.search_fields + [
        index.SearchField("get_audience_display"),
        index.SearchField("location"),
        index.SearchField("body_en"),
        index.SearchField("body_tet")
    ]

    @property
    def event_index(self):
        # Find closest ancestor which is an event index
        return self.get_ancestors().type(EventIndexPage).last()

    def serve(self, request):
        if "format" in request.GET:
            if request.GET["format"] == "ical":
                # Export to ical format
                response = HttpResponse(export_event(self, "ical"),
                                        content_type="text/calendar")
                response["Content-Disposition"] = ("attachment; filename=" +
                                                   self.slug + ".ics")
                return response
            else:
                # Unrecognised format error
                message = ("Could not export event\n\nUnrecognised format: " +
                           request.GET["format"])
                return HttpResponse(message, content_type="text/plain")
        else:
            # Display event page as usual
            return super(EventPage, self).serve(request)
Beispiel #19
0
class ContributorListPage(Page):
    subpage_types = ['ContributorPage']
    intro_text = RichTextField(blank=True)
    body = RichTextField(blank=True)

    def get_rows(self, contributors, number_of_columns=3, max_columns=4):
        rows = []
        number_of_items = len(contributors)
        number_of_rows = number_of_items // number_of_columns
        row_remainder = number_of_items % number_of_columns

        if row_remainder > number_of_rows:
            number_of_rows += 1
        elif row_remainder <= number_of_rows and row_remainder != 0:
            if number_of_columns < max_columns:
                number_of_columns += 1
            else:
                number_of_rows += 1

        for row_index in range(0, number_of_rows):
            row = contributors[(
                row_index *
                number_of_columns):(row_index * number_of_columns) +
                               number_of_columns]
            rows.append(row)
        return rows

    @property
    def recent_contributors(self):
        endtime = timezone.now()
        starttime = endtime - datetime.timedelta(days=365)
        contributors = (
            ContributorPage.objects.live().filter(  # noqa
                featured=False,
                article_links__article__isnull=False,
                article_links__isnull=False,
                article_links__article__first_published_at__range=[
                    starttime, endtime
                ]).order_by('last_name', 'first_name').distinct())

        return self.get_rows(contributors, number_of_columns=4)

    @property
    def nonfeatured_contributors(self):
        contributors = ContributorPage.objects.live().filter(
            featured=False).order_by('last_name', 'first_name')
        return self.get_rows(contributors)

    @property
    def featured_contributors(self):
        contributors = ContributorPage.objects.live().filter(
            featured=True).order_by('last_name', 'first_name')
        return self.get_rows(contributors)

    content_panels = Page.content_panels + [
        FieldPanel('intro_text'),
        FieldPanel('body'),
    ]

    edit_handler = TabbedInterface([
        ObjectList(content_panels, heading='Content'),
        ObjectList(Page.promote_panels, heading='Promote'),
        ObjectList(Page.settings_panels,
                   heading='Settings',
                   classname="settings"),
    ])
Beispiel #20
0
class FormPage(AbstractEmailForm):
    intro = RichTextField(blank=True)
    thank_you_text = RichTextField(blank=True)
Beispiel #21
0
class AnswerPage(CFGOVPage):
    """
    Page type for Ask CFPB answers.
    """
    from ask_cfpb.models import Answer
    last_edited = models.DateField(
        blank=True,
        null=True,
        help_text="Change the date to today if you make a significant change.")
    question = models.TextField(blank=True)
    statement = models.TextField(
        blank=True,
        help_text=(
            "(Optional) Use this field to rephrase the question title as "
            "a statement. Use only if this answer has been chosen to appear "
            "on a money topic portal (e.g. /consumer-tools/debt-collection)."))
    short_answer = RichTextField(blank=True,
                                 features=['link', 'document-link'],
                                 help_text='Optional answer intro')
    answer_content = StreamField(ask_blocks.AskAnswerContent(),
                                 blank=True,
                                 verbose_name='Answer')
    answer_base = models.ForeignKey(Answer,
                                    blank=True,
                                    null=True,
                                    related_name='answer_pages',
                                    on_delete=models.SET_NULL)
    redirect_to_page = models.ForeignKey(
        'self',
        blank=True,
        null=True,
        on_delete=models.SET_NULL,
        related_name='redirect_to_pages',
        help_text="Choose another AnswerPage to redirect this page to")
    featured = models.BooleanField(
        default=False,
        help_text=("Check to make this one of two featured answers "
                   "on the landing page."))
    featured_rank = models.IntegerField(blank=True, null=True)
    category = models.ManyToManyField(
        'Category',
        blank=True,
        help_text=("Categorize this answer. "
                   "Avoid putting into more than one category."))
    search_tags = models.CharField(
        max_length=1000,
        blank=True,
        help_text="Search words or phrases, separated by commas")
    related_resource = models.ForeignKey(RelatedResource,
                                         blank=True,
                                         null=True,
                                         on_delete=models.SET_NULL)
    related_questions = ParentalManyToManyField(
        'self',
        symmetrical=False,
        blank=True,
        related_name='related_question',
        help_text='Maximum of 3 related questions')
    portal_topic = ParentalManyToManyField(
        PortalTopic,
        blank=True,
        help_text='Limit to 1 portal topic if possible')
    primary_portal_topic = ParentalKey(
        PortalTopic,
        blank=True,
        null=True,
        on_delete=models.SET_NULL,
        related_name='primary_portal_topic',
        help_text=("Use only if assigning more than one portal topic, "
                   "to control which topic is used as a breadcrumb."))
    portal_category = ParentalManyToManyField(PortalCategory, blank=True)

    user_feedback = StreamField([
        ('feedback', v1_blocks.Feedback()),
    ],
                                blank=True)

    content_panels = CFGOVPage.content_panels + [
        MultiFieldPanel([
            FieldPanel('last_edited'),
            FieldPanel('question'),
            FieldPanel('statement'),
            FieldPanel('short_answer')
        ],
                        heading="Page content",
                        classname="collapsible"),
        StreamFieldPanel('answer_content'),
        MultiFieldPanel([
            SnippetChooserPanel('related_resource'),
            AutocompletePanel('related_questions',
                              page_type='ask_cfpb.AnswerPage',
                              is_single=False)
        ],
                        heading="Related resources",
                        classname="collapsible"),
        MultiFieldPanel([
            FieldPanel('portal_topic', widget=forms.CheckboxSelectMultiple),
            FieldPanel('primary_portal_topic'),
            FieldPanel('portal_category', widget=forms.CheckboxSelectMultiple)
        ],
                        heading="Portal tags",
                        classname="collapsible"),
        MultiFieldPanel([FieldPanel('featured')],
                        heading="Featured answer on Ask landing page",
                        classname="collapsible"),
        MultiFieldPanel([
            AutocompletePanel('redirect_to_page',
                              page_type='ask_cfpb.AnswerPage')
        ],
                        heading="Redirect to another answer",
                        classname="collapsible"),
        MultiFieldPanel([StreamFieldPanel('user_feedback')],
                        heading="User feedback",
                        classname="collapsible collapsed"),
    ]

    sidebar = StreamField([
        ('call_to_action', molecules.CallToAction()),
        ('related_links', molecules.RelatedLinks()),
        ('related_metadata', molecules.RelatedMetadata()),
        ('email_signup', organisms.EmailSignUp()),
        ('sidebar_contact', organisms.SidebarContactInfo()),
        ('rss_feed', molecules.RSSFeed()),
        ('social_media', molecules.SocialMedia()),
        ('reusable_text', v1_blocks.ReusableTextChooserBlock(ReusableText)),
    ],
                          blank=True)

    sidebar_panels = [
        StreamFieldPanel('sidebar'),
    ]

    search_fields = Page.search_fields + [
        index.SearchField('answer_content'),
        index.SearchField('short_answer')
    ]

    edit_handler = TabbedInterface([
        ObjectList(content_panels, heading='Content'),
        ObjectList(sidebar_panels, heading='Sidebar'),
        ObjectList(CFGOVPage.settings_panels, heading='Configuration'),
    ])

    template = 'ask-cfpb/answer-page.html'

    objects = CFGOVPageManager()

    def get_sibling_url(self):
        if self.answer_base:
            if self.language == 'es':
                sibling = self.answer_base.english_page
            else:
                sibling = self.answer_base.spanish_page
            if sibling and sibling.live and not sibling.redirect_to_page:
                return sibling.url

    def get_context(self, request, *args, **kwargs):
        portal_topic = self.primary_portal_topic or self.portal_topic.first()
        context = super(AnswerPage, self).get_context(request)
        context['related_questions'] = self.related_questions.all()
        context['description'] = (self.short_answer if self.short_answer else
                                  Truncator(self.answer_content).words(
                                      40, truncate=' ...'))
        context['last_edited'] = self.last_edited
        context['portal_page'] = get_portal_or_portal_search_page(
            portal_topic, language=self.language)
        context['breadcrumb_items'] = get_ask_breadcrumbs(
            language=self.language,
            portal_topic=portal_topic,
        )
        context['about_us'] = get_standard_text(self.language, 'about_us')
        context['disclaimer'] = get_standard_text(self.language, 'disclaimer')
        context['sibling_url'] = self.get_sibling_url()
        return context

    def __str__(self):
        if self.answer_base:
            return '{}: {}'.format(self.answer_base.id, self.title)
        else:
            return self.title

    @property
    def clean_search_tags(self):
        return [tag.strip() for tag in self.search_tags.split(',')]

    @property
    def status_string(self):
        if self.redirect_to_page:
            if not self.live:
                return ("redirected but not live")
            else:
                return ("redirected")
        else:
            return super(AnswerPage, self).status_string

    # Returns an image for the page's meta Open Graph tag
    @property
    def meta_image(self):
        if self.social_sharing_image:
            return self.social_sharing_image

        if not self.category.exists():
            return None

        return self.category.first().category_image

    # Overrides the default of page.id for comparing against split testing
    # clusters. See: core.feature_flags.in_split_testing_cluster
    @property
    def split_test_id(self):
        return self.answer_base.id
Beispiel #22
0
class MemberShipPage(Page, ContactFields):
    intro_en = RichTextField(blank=True)
    intro_tet = RichTextField(blank=True)
    intro = TranslatedField("intro_en", "intro_tet")
    body_en = RichTextField(blank=True)
    body_tet = RichTextField(blank=True)
    body = TranslatedField("body_en", "body_tet")
    thank_you_text_en = RichTextField(blank=True)
    thank_you_text_tet = RichTextField(blank=True)
    thank_you_text = TranslatedField("thank_you_text_en", "thank_you_text_tet")
    to_address = models.CharField(
        max_length=255,
        blank=True,
        help_text=_(
            "Optional - form submissions will be emailed to this address"),
    )
    from_address = models.CharField(max_length=255, blank=True)
    subject = models.CharField(max_length=255, blank=True)

    content_panels = Page.content_panels + [
        FieldPanel("title", classname="full title"),
        FieldPanel("intro_en", classname="full"),
        FieldPanel("intro_tet", classname="full"),
        FieldPanel("body_en", classname="full"),
        FieldPanel("body_tet", classname="full"),
        FieldPanel("thank_you_text_en", classname="full"),
        FieldPanel("thank_you_text_tet", classname="full"),
        InlinePanel("carousel_items", label="Carousel items"),
        MultiFieldPanel(
            [
                FieldPanel("to_address", classname="full"),
                FieldPanel("from_address", classname="full"),
                FieldPanel("subject", classname="full"),
            ],
            "Email",
        ),
    ]

    def serve(self, request):
        if request.method == "POST":
            form = MembershipForm(request.POST)

            if form.is_valid():
                m_fields = form.save()

                if self.to_address:
                    to_address = [
                        address.strip()
                        for address in self.to_address.split(",")
                    ]
                    from_address = self.from_address
                    if "%s" not in self.from_address:
                        from_address = from_address.replace("@", "-%s@")
                    try:
                        from_address = from_address % get_random_string(15)
                    except Exception:
                        pass
                    from_address = "New Contact <%s>" % from_address
                    content = render_to_string("home/email/new_contact.html",
                                               {"fields": m_fields})
                    msg = EmailMultiAlternatives(self.subject,
                                                 strip_tags(content),
                                                 from_address, to_address)
                    # msg.attach_alternative(content, "text/html")
                    msg.send(fail_silently=True)

                return render(
                    request,
                    "home/member_ship_page.html",
                    {
                        "self": self,
                        "form": MembershipForm()
                    },
                )
        else:
            form = MembershipForm(initial={"page": self})

        return render(request, self.template, {"self": self, "form": form})
Beispiel #23
0
class ContentIndexPage(Page):
    intro = RichTextField(blank=True)

    content_panels = Page.content_panels + [
        FieldPanel('intro', classname="full"),
    ]
Beispiel #24
0
class Face(Page):
    image = models.ForeignKey("core.AffixImage",
                              related_name="face_for_image",
                              null=True,
                              on_delete=models.SET_NULL)
    location = models.ForeignKey("location.Location",
                                 null=True,
                                 on_delete=models.SET_NULL,
                                 verbose_name="Place of Origin")
    additional_info = RichTextField(blank=True)
    language = models.CharField(max_length=7, choices=settings.LANGUAGES)

    occupation = models.CharField(
        max_length=100,
        null=True,
        blank=True,
        help_text="Enter the occupation of the person")
    occupation_of_parent = models.CharField(max_length=50,
                                            null=True,
                                            blank=True)
    adivasi = models.CharField(max_length=100, null=True, blank=True)
    quote = RichTextField(blank=True)
    child = models.BooleanField(default=False)
    age = models.IntegerField(null=True, blank=True)

    GENDER_CHOICES = (
        ('F', 'Female'),
        ('M', 'Male'),
        ('T', 'Transgender'),
    )
    gender = models.CharField(max_length=1,
                              choices=GENDER_CHOICES,
                              null=True,
                              blank=True)

    def __str__(self):
        return "{0} {1}".format(self.title, self.location.district)

    @property
    def featured_image(self):
        return self.image

    @property
    def title_to_share(self):
        title = "Meet " + self.title
        title += ", " + self.occupation if self.occupation else ""
        title += " from " + self.location.district
        title += ", " + self.location.state
        return title

    @property
    def locations(self):
        return [self.location]

    @property
    def photographers(self):
        return self.image.photographers.all()

    def get_authors_or_photographers(self):
        return [photographer.name for photographer in self.photographers]

    search_fields = Page.search_fields + [
        index.SearchField('title', partial_match=True,
                          boost=SearchBoost.TITLE),
        index.FilterField('image'),
        index.SearchField(
            'additional_info', partial_match=True, boost=SearchBoost.CONTENT),
        index.FilterField('location'),
        index.RelatedFields('location', [
            index.SearchField('name'),
            index.SearchField('block'),
            index.SearchField('district'),
            index.SearchField('state'),
            index.SearchField('panchayat'),
        ]),
        index.SearchField('occupation'),
        index.SearchField('occupation_of_parent'),
        index.SearchField('quote'),
        index.SearchField('get_locations_index',
                          partial_match=True,
                          boost=SearchBoost.LOCATION),
        index.SearchField('get_photographers_index',
                          partial_match=True,
                          boost=SearchBoost.AUTHOR),
        index.SearchField('adivasi'),
        index.SearchField('language'),
        index.FilterField('get_search_type'),
        index.FilterField('language'),
        index.FilterField('get_minimal_locations'),
        index.FilterField('get_authors_or_photographers')
    ]

    def get_locations_index(self):
        return self.image.get_locations_index()

    def get_minimal_locations(self):
        return [self.location.minimal_address]

    def get_photographers_index(self):
        return self.image.get_all_photographers()

    def get_search_type(self):
        return self.__class__.__name__.lower()

    content_panels = Page.content_panels + [
        ImageChooserPanel('image'),
        M2MFieldPanel('location'),
        FieldPanel('adivasi'),
        MultiFieldPanel([
            FieldPanel('child'),
            FieldPanel('occupation'),
            FieldPanel('occupation_of_parent'),
            FieldPanel('age'),
            FieldPanel('gender'),
        ],
                        heading="Personal details",
                        classname="collapsible "),
        MultiFieldPanel([
            FieldPanel('additional_info'),
            FieldPanel('language'),
            FieldPanel('quote'),
        ],
                        heading="Additional details",
                        classname="collapsible"),
    ]

    def get_absolute_url(self):
        name = "face-detail-single"
        return reverse(name,
                       kwargs={
                           "alphabet": self.location.district[0].lower(),
                           "slug": self.slug
                       })

    def get_context(self, request, *args, **kwargs):
        return {
            'faces': [self],
            'alphabet': self.location.district[0].lower(),
            'request': request
        }
Beispiel #25
0
class ContactPage(Page):
    # ---- General Page information ------
    title_sv = models.CharField(max_length=255)
    translated_title = TranslatedField('title', 'title_sv')

    contact_point = StreamField(
        [('person', ContactCardBlock())],
    )

    other_contacts = StreamField(
        [('contact', blocks.StructBlock([
            ('person', ContactCardBlock()),
            ('english_groups', blocks.CharBlock(
                help_text=_('Comma separated list of English group names.'),
                required=False,
            )),
            ('swedish_groups', blocks.CharBlock(
                help_text=_('Comma separated list of Swedish group names.'),
                required=False,
            )),
        ], icon='user'))],
    )

    map_location = models.CharField(
        max_length=255,
        verbose_name=_('Map Location'),
        help_text=_('Enter comma separated coordinates'),
        blank=True,
    )

    location_description = RichTextField(
        verbose_name=_('Location Description'),
        help_text=_('Enter the text to show on the map'),
        blank=True,
    )

    def get_context(self, request, *args, **kwargs):
        contacts = {}
        for contact in self.other_contacts:
            if request.LANGUAGE_CODE == 'sv':
                groups = contact.value.get('swedish_groups', '')
            else:
                groups = contact.value.get('english_groups', '')
            groups = groups.split(',')

            for group in groups:
                group = group.strip()
                l = contacts.get(group, [])
                l.append(contact.value['person'])
                contacts[group] = l

        context = super(ContactPage, self).get_context(
            request, *args, **kwargs
        )
        context['contacts'] = contacts
        return context

    content_panels = Page.content_panels + [
        FieldPanel('title_sv', classname="full title"),
        FieldPanel('map_location'),
        FieldPanel('location_description'),
        StreamFieldPanel('contact_point'),
        StreamFieldPanel('other_contacts'),
    ]
class FeedbackPage(AbstractForm):
    hero_image = models.ForeignKey('wagtailimages.Image',
                                   null=True,
                                   blank=True,
                                   on_delete=models.SET_NULL,
                                   related_name='+',
                                   help_text="""
            Max file size: 10MB. Choose from: JPEG, PNG
        """)

    alphatext = RichTextField(blank=True,
                              help_text="Why to take part in the alpha")

    content_panels = AbstractForm.content_panels + [
        InlinePanel('form_fields', label="Form fields"),
        ImageChooserPanel('hero_image'),
        FieldPanel('alphatext', classname="full"),
    ]

    def serve(self, request, *args, **kwargs):
        if request.method == 'POST':
            form = self.get_form(request.POST, page=self, user=request.user)

            if form.is_valid():
                self.process_form_submission(form)

                # render the landing_page
                # TODO: It is much better to redirect to it
                return render(request, self.get_landing_page_template(request),
                              self.get_context(request))
        else:
            form = self.get_form(page=self, user=request.user)

        # custom
        custom_form = []

        vals = FormField.objects.all().filter(page_id=form.page.id)

        for val in vals:
            dict = {}
            dict['before_input'] = val.before_input
            dict['after_input'] = val.after_input
            dict['field_type'] = val.field_type
            dict['default_value'] = val.default_value
            dict['label'] = val.label

            # TODO: look at a nicer way to fetch errors and submitted_val

            request_dict = parse_qs(request.body.decode('utf-8'))

            if val.field_type == 'radio':
                choices_list = []
                choices = val.choices.split(",")
                for choice in choices:
                    try:
                        submitted_val = request_dict[val.label][0]
                    except:
                        submitted_val = False
                    checked = 'checked' if choice == submitted_val else ''
                    choices_list.append({'val': choice, 'checked': checked})
                dict['choices'] = choices_list
            else:
                try:
                    dict['submitted_val'] = request_dict[val.label][0]
                except:
                    dict['submitted_val'] = ''

            dict['required'] = 'required' if val.required else ''

            if form.errors:
                try:
                    dict['errors'] = form.errors.as_data()[val.label][0]
                except:
                    pass

            custom_form.append(dict)

        context = self.get_context(request)
        context['form'] = form
        context['custom_form'] = custom_form  # custom

        return render(request, self.get_template(request),
                      base_context(context, self))
Beispiel #27
0
class Resource(ClusterableModel):
    title = models.CharField(max_length=255)
    desc = RichTextField(verbose_name='Description', blank=True)

    thumbnail = models.ForeignKey('v1.CFGOVImage',
                                  null=True,
                                  blank=True,
                                  on_delete=models.SET_NULL,
                                  related_name='+')

    related_file = models.ForeignKey('wagtaildocs.Document',
                                     null=True,
                                     blank=True,
                                     on_delete=models.SET_NULL,
                                     related_name='+')

    alternate_file = models.ForeignKey('wagtaildocs.Document',
                                       null=True,
                                       blank=True,
                                       on_delete=models.SET_NULL,
                                       related_name='+')

    link = models.URLField(
        blank=True,
        help_text='Example: URL to order a few copies of a printed piece.',
        validators=[URLValidator])

    alternate_link = models.URLField(
        blank=True,
        help_text='Example: a URL to for ordering bulk copies.',
        validators=[URLValidator])

    order = models.PositiveSmallIntegerField(
        null=True,
        blank=True,
        help_text='Snippets will be listed alphabetically by title in a '
        'Snippet List module, unless any in the list have a number in this '
        'field; those with an order value will appear at the bottom of the '
        'list, in ascending order.')

    tags = TaggableManager(
        through=ResourceTag,
        blank=True,
        help_text='Tags can be used to filter snippets in a Snippet List.')

    objects = TaggableSnippetManager()

    panels = [
        FieldPanel('title'),
        FieldPanel('desc'),
        ImageChooserPanel('thumbnail'),
        DocumentChooserPanel('related_file'),
        DocumentChooserPanel('alternate_file'),
        FieldPanel('link'),
        FieldPanel('alternate_link'),
        FieldPanel('order'),
        FieldPanel('tags'),
    ]

    # Makes fields available to the Actions chooser in a Snippet List module
    snippet_list_field_choices = [
        ('related_file', 'Related file'),
        ('alternate_file', 'Alternate file'),
        ('link', 'Link'),
        ('alternate_link', 'Alternate link'),
    ]

    def __str__(self):
        return self.title

    class Meta:
        ordering = ('order', 'title')
Beispiel #28
0
class HomePage(Page, IndexPage):
    """
    HomePage class, inheriting from wagtailcore.Page straight away
    """
    subpage_types = [
        'core.WagtailPage', 'core.CompanyIndex', 'core.WagtailCompanyPage',
        'core.WagtailSitePage', 'core.SubmitFormPage'
    ]
    feed_image = models.ForeignKey('wagtailimages.Image',
                                   null=True,
                                   blank=True,
                                   on_delete=models.SET_NULL,
                                   related_name='+')
    search_fields = ()

    body = RichTextField(blank=True)

    @property
    def og_image(self):
        # Returns image and image type of feed_image, if exists
        image = {'image': None, 'type': None}
        if self.feed_image:
            image['image'] = self.feed_image
        name, extension = os.path.splitext(image['image'].file.url)
        image['type'] = extension[1:]
        return image

    def children(self):
        return self.get_children().live()

    def get_context(self, request):
        # Get pages
        pages = WagtailSitePage.objects.live().descendant_of(self).order_by(
            '-is_featured', '-latest_revision_created_at')

        # Filter by tag
        tag = request.GET.get('tag')
        if tag:
            pages = pages.filter(tags__name=tag)

        # Pagination
        page = request.GET.get('page')
        paginator = Paginator(pages, 12)  # Show 12 pages per page
        try:
            pages = paginator.page(page)
        except PageNotAnInteger:
            pages = paginator.page(1)
        except EmptyPage:
            pages = paginator.page(paginator.num_pages)

        # Update template context
        context = super(HomePage, self).get_context(request)
        context['pages'] = pages
        # Only tags used by live pages
        context['tags'] = Tag.objects.filter(
            core_pagetag_items__isnull=False,
            core_pagetag_items__content_object__live=True).distinct().order_by(
                'name')

        return context

    def __unicode__(self):
        return self.title

    class Meta:
        verbose_name = "Home page"
        description = "Where the good stuff happens!"
Beispiel #29
0
class WagtailPage(Page):
    """
    Our main custom Page class. All content pages should inherit from this one.
    """

    parent_types = ['core.HomePage']
    subpage_types = ['core.WagtailPage']

    is_creatable = False

    feed_image = models.ForeignKey('wagtailimages.Image',
                                   null=True,
                                   blank=True,
                                   on_delete=models.SET_NULL,
                                   related_name='+')
    body = RichTextField(
        blank=True,
        features=['bold', 'italic', 'ol', 'ul', 'link', 'cleanhtml'])
    tags = ClusterTaggableManager(through=PageTag, blank=True)
    search_fields = []

    @property
    def parent(self):
        try:
            return self.get_ancestors().reverse()[0]
        except IndexError:
            return None

    @property
    def child(self):
        for related_object in self._meta.get_all_related_objects():
            if not issubclass(related_object.model, self.__class__):
                continue
            try:
                return getattr(self, related_object.get_accessor_name())
            except ObjectDoesNotExist:
                pass

    @property
    def body_text(self):
        return BeautifulSoup(self.body, "html5lib").get_text()

    @property
    def body_excerpt(self):
        """
        Return body text replacing end of lines (. ? ! chars) with a blank space
        """
        return re.sub(r'([\.?!])([a-zA-Z])', r'\1 \2', self.body_text)

    @property
    def og_image(self):
        # Returns image and image type of feed_image or image as fallback, if exists
        image = {'image': None, 'type': None}
        if self.feed_image:
            image['image'] = self.feed_image
        name, extension = os.path.splitext(image['image'].file.url)
        image['type'] = extension[1:]
        return image

    class Meta:
        verbose_name = "Content Page"

    content_panels = panels.WAGTAIL_PAGE_CONTENT_PANELS
    promote_panels = panels.WAGTAIL_PAGE_PROMOTE_PANELS
class CalendarPage(Page):
    subpage_types = []
    intro = RichTextField(blank=True)
    search_fields = Page.search_fields

    @property
    def events(self):
        return []

    def route(self, request, components):
        # see http://docs.wagtail.io/en/latest/reference/pages/model_recipes.html
        if components:
            # tell Wagtail to call self.serve() with an additional kwargs
            return RouteResult(self, kwargs=self._parsePath(components))
        else:
            if self.live:
                # tell Wagtail to call self.serve() with no further args
                return RouteResult(self)
            else:
                raise Http404

    def _parsePath(self, components):
        kwargs = {}
        with suppress(ValueError, TypeError):
            value = int(components[0])
            if 1900 <= value <= 2115:
                kwargs['year'] = value
        if len(components) > 1:
            try:
                kwargs['month'] = MonthAbbrs.index(components[1])
            except (ValueError, TypeError):
                with suppress(ValueError, TypeError):
                    value = int(components[1])
                    if 1 <= value <= 12:
                        kwargs['month'] = value
        if len(components) > 2:
            with suppress(ValueError, TypeError):
                value = int(components[2])
                if 1 <= value <= 31:
                    kwargs['day'] = value
        return kwargs

    def serve(self, request, year=None, month=None, day=None):
        today = dt.date.today()
        yesterday = today - dt.timedelta(1)
        lastWeek = today - dt.timedelta(7)
        if year is None:
            year = today.year
        if month is None:
            month = today.month
        eventsByWeek = getAllEventsByWeek(year, month)
        prevMonth = month - 1
        prevMonthYear = year
        if prevMonth == 0:
            prevMonth = 12
            prevMonthYear = year - 1
        nextMonth = month + 1
        nextMonthYear = year
        if nextMonth == 13:
            nextMonth = 1
            nextMonthYear = year + 1
        return render(
            request, self.template, {
                'self':
                self,
                'year':
                year,
                'month':
                month,
                'today':
                today,
                'yesterday':
                yesterday,
                'lastweek':
                lastWeek,
                'prevMonthUrl':
                "{}{}/{}/".format(self.url, prevMonthYear, prevMonth),
                'nextMonthUrl':
                "{}{}/{}/".format(self.url, nextMonthYear, nextMonth),
                'prevYearUrl':
                "{}{}/{}/".format(self.url, year - 1, month),
                'nextYearUrl':
                "{}{}/{}/".format(self.url, year + 1, month),
                'monthName':
                calendar.month_name[month],
                'events':
                eventsByWeek
            })

    content_panels = Page.content_panels + [
        FieldPanel('intro', classname="full"),
    ]

    promote_panels = [
        MultiFieldPanel(Page.promote_panels, "Common page configuration")
    ]