Example #1
0
class HomePage(RoutablePageMixin, Page):
    templates = 'home/home_page.html'

    subpage_types = [
        'blog.BlogListingPage',
        'contact.ContactPage',
        'flex.FlexPage',    
    ]
    parent_page_type = [
        'wagtailcore.Page'
    ]
    #max_count = 1

    banner_title = models.CharField(max_length=100, blank=False, null=True)
    banner_subtitle = RichTextField(features=['bold', 'italic'])
    banner_image = models.ForeignKey(
        "wagtailimages.Image",
        null=True,
        blank=False,
        on_delete=models.SET_NULL,
        related_name='+'
    )
    banner_cta = models.ForeignKey(
        'wagtailcore.Page',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+'
    )

    content = StreamField(
        [
            ('cta', blocks.CTABlock()),
        ],
        null=True,
        blank=True
    )

    api_fields = [
        APIField('banner_title'),
        APIField('banner_subtitle'),
        APIField('banner_image'),
        APIField('banner_cta'),
        APIField('carousel_images'),
        APIField('content'),
    ]

    max_count = 1

    content_panels = Page.content_panels + [
        MultiFieldPanel(
            [InlinePanel("carousel_images", max_num=5, min_num=1, label="Image")],
            heading="Carousel Images",
        ),
        StreamFieldPanel("content"),
    ]

    banner_panels = [
        MultiFieldPanel(
            [
                FieldPanel("banner_title"),
                FieldPanel("banner_subtitle"),
                ImageChooserPanel("banner_image"),
                PageChooserPanel("banner_cta"),
            ],
            heading="Banner Options",
        ),
    ]

    edit_handler = TabbedInterface(
    [
        ObjectList(content_panels, heading='Content'),
        ObjectList(banner_panels, heading="Banner Settings"),
        ObjectList(Page.promote_panels, heading='Promotional Stuff'),
        ObjectList(Page.settings_panels, heading='Settings Stuff'),
    ]
    )

    @route(r'^subscribe/$')
    def the_subscribe_page(self, request, *args, **kwargs):
        context = self.get_context(request, *args, **kwargs)
        return render(request, 'home/subscribe.html', context)
class ContactPage(Page, MenuPageMixin):
    """A custom contact page with contact form."""
    body = RichTextField(blank=True)
    TEMPLATE_CHOICES = [
        ('base_dark.html', 'Dark'),
        ('base_light.html', 'Light')
    ]
    template_theme = models.CharField(
        max_length = 250,
        choices = TEMPLATE_CHOICES,
        default = 'Dark', 
        help_text = """
        Choose dark theme to match main site and light theme
        to match light site."""
    )
    content_panels = Page.content_panels + [
        FieldPanel('body', classname="full"),
        FieldPanel('template_theme'),
        menupage_panel,
    ]
    subpage_types = ['contact.ContactSuccessPage', 'contact.MailChimpPage']
    parent_page_types = ['home.HomePage', 'home.FanSiteHomePage']

    def serve(self, request):
        if request.method == 'GET':
            form = ContactForm()
        else:
            form = ContactForm(request.POST)
            if form.is_valid():

                # Clean the data
                senders_name = form.cleaned_data['your_name']
                senders_email = form.cleaned_data['your_email']
                message_subject = form.cleaned_data['subject']
                message = form.cleaned_data['your_message']
                site_name = settings.WAGTAIL_SITE_NAME
                to_email = settings.CONTACT_EMAIL
                #Process the form info to get it ready for send_mail
                subject_line = f"""New message from {site_name} contact form: {message_subject}"""
                message_body = f"""You have received the following message from your website, {site_name}:
                \n\n 
                Sender's Name: {senders_name}
                \n\n
                Sender's Email: {senders_email} 
                \n\n
                Subject: {message_subject} 
                \n\n
                Message Body: {message}"""
                # And send                
                try:
                    send_mail(subject_line, message_body, to_email, [to_email], fail_silently=False)
                except BadHeaderError:
                    return HttpResponse('Invalid header found.')
                success_pages = self.get_specific().get_children()
                for success in success_pages:
                    return redirect(success.specific.url) 
        return render(request, 'contact/contact_page.html', {
            'page':self,
            'form':form,
            'template_theme':self.template_theme,
            })
Example #3
0
class HomePage(Page):
    body = RichTextField(blank=True)

    content_panels = Page.content_panels + [
        FieldPanel('body', classname="full"),
    ]
Example #4
0
class StevePage(Page):
    city = models.CharField(null=True, blank=False, max_length=255)
    zip_code = models.CharField(null=True, blank=False, max_length=255)
    address = models.CharField(null=True, blank=False, max_length=255)
    telephone = models.CharField(null=True, blank=False, max_length=255)
    telefax = models.CharField(null=True, blank=False, max_length=255)
    vat_number = models.CharField(null=True, blank=False, max_length=255)
    whatsapp_telephone = models.CharField(null=True, blank=True, max_length=255)
    whatsapp_contactline = models.CharField(null=True, blank=True, max_length=255)
    tax_id = models.CharField(null=True, blank=False, max_length=255)
    trade_register_number = models.CharField(null=True, blank=False, max_length=255)
    court_of_registry = models.CharField(null=True, blank=False, max_length=255)
    place_of_registry = models.CharField(null=True, blank=False, max_length=255)
    trade_register_number = models.CharField(null=True, blank=False, max_length=255)
    ownership = models.CharField(null=True, blank=False, max_length=255)
    email = models.CharField(null=True, blank=False, max_length=255)

    copyrightholder = models.CharField(null=True, blank=False, max_length=255)

    about = RichTextField(null=True, blank=False)
    privacy = RichTextField(null=True, blank=False)

    sociallinks = StreamField([
        ('link', blocks.URLBlock(help_text="Important! Format https://www.domain.tld/xyz"))
    ])

    array = []
    def sociallink_company(self):
        for link in self.sociallinks:
            self.array.append(str(link).split(".")[1])
        return self.array

    sections = StreamField([
        ('s_news', _S_NewsBlock(null=True, blank=False, icon='group')),
        ('code', blocks.RawHTMLBlock(null=True, blank=True, classname="full", icon='code'))
    ], null=True, blank=False)

    token = models.CharField(null=True, blank=True, max_length=255)

    #graphql_fields = [
    #    GraphQLStreamfield("headers"),
    #    GraphQLStreamfield("sections"),
    #]

    main_content_panels = [
        StreamFieldPanel('sections')
    ]

    imprint_panels = [
        MultiFieldPanel(
            [
            FieldPanel('city'),
            FieldPanel('zip_code'),
            FieldPanel('address'),
            FieldPanel('telephone'),
            FieldPanel('telefax'),
            FieldPanel('whatsapp_telephone'),
            FieldPanel('whatsapp_contactline'),
            FieldPanel('email'),
            FieldPanel('copyrightholder')
            ],
            heading="contact",
        ),
        MultiFieldPanel(
            [
            FieldPanel('vat_number'),
            FieldPanel('tax_id'),
            FieldPanel('trade_register_number'),
            FieldPanel('court_of_registry'),
            FieldPanel('place_of_registry'),
            FieldPanel('trade_register_number'),
            FieldPanel('ownership')
            ],
            heading="legal",
        ),
        StreamFieldPanel('sociallinks'),
        MultiFieldPanel(
            [
            FieldPanel('about'),
            FieldPanel('privacy')
            ],
            heading="privacy",
        )
    ]

    token_panel = [
        FieldPanel('token')
    ]

    edit_handler = TabbedInterface([
        ObjectList(Page.content_panels + main_content_panels, heading='Main'),
        ObjectList(imprint_panels, heading='Imprint'),
        ObjectList(Page.promote_panels + token_panel + Page.settings_panels, heading='Settings', classname="settings")
    ])
Example #5
0
class SeriesPage(ThemeablePage, FeatureStyleFields, Promotable,
                 ShareLinksMixin, PageLayoutOptions, VideoDocumentMixin):
    subtitle = RichTextField(blank=True, default="")
    short_description = RichTextField(blank=True, default="")
    body = article_fields.BodyField(blank=True, default="")

    main_image = models.ForeignKey('images.AttributedImage',
                                   null=True,
                                   blank=True,
                                   on_delete=models.SET_NULL,
                                   related_name='+')
    feature_image = models.ForeignKey('images.AttributedImage',
                                      null=True,
                                      blank=True,
                                      on_delete=models.SET_NULL,
                                      related_name='+')
    primary_topic = models.ForeignKey('articles.Topic',
                                      null=True,
                                      blank=True,
                                      on_delete=models.SET_NULL,
                                      related_name='series')
    project = models.ForeignKey(
        "projects.ProjectPage",
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
    )

    search_fields = Page.search_fields + [
        index.SearchField('subtitle', partial_match=True),
        index.SearchField('body', partial_match=True),
        index.SearchField('get_primary_topic_name', partial_match=True),
        index.SearchField('get_topic_names', partial_match=True),
    ]

    number_of_related_articles = models.PositiveSmallIntegerField(
        default=6, verbose_name="Number of Related Articles to Show")

    def get_primary_topic_name(self):
        if self.primary_topic:
            return self.primary_topic.name
        else:
            ""

    def get_topic_names(self):
        return '\n'.join(
            [topic.name if topic else "" for topic in self.topics])

    def get_author_names(self):
        return '\n'.join(
            [author.full_name if author else "" for author in self.authors])

    @property
    def articles(self):
        article_list = []
        for article_link in self.related_article_links.all():
            if article_link.article:
                article_link.article.override_text = article_link.override_text
                article_link.article.override_image = article_link.override_image
                article_list.append(article_link.article)
        return article_list

    @property
    def authors(self):
        author_list = []
        for article_link in self.related_article_links.all():
            if article_link.article:
                if article_link.article:
                    for author_link in article_link.article.author_links.all():
                        if author_link.author:
                            if author_link.author not in author_list:
                                author_list.append(author_link.author)
        author_list.sort(key=attrgetter('last_name'))
        return author_list

    @property
    def topics(self):
        all_topics = []
        if self.primary_topic:
            all_topics.append(self.primary_topic)
        for article_link in self.related_article_links.all():
            if article_link.article:
                all_topics.extend(article_link.article.topics)

        all_topics = list(set(all_topics))
        if all_topics:
            all_topics.sort(key=attrgetter('name'))
        return all_topics

    @property
    def related_series(self):
        related_series_list = []
        if self.project:
            related_series_list = self.project.get_related_series(self)
        return related_series_list

    def get_content(self):
        '''
        A generic and generative interface for getting all the content block for an article,
        including advanced content such as chapters.
        '''
        for block in self.body:
            yield block

    def related_articles(self, number):
        articles = []
        if self.primary_topic:
            articles = list(ArticlePage.objects.live().filter(
                primary_topic=self.primary_topic).distinct().order_by(
                    '-first_published_at')[:number])

        current_total = len(articles)
        if current_total < number:
            for article in self.articles:
                articles.extend(list(article.related_articles(number)))
                articles = list(set(articles))[:number]
                current_total = len(articles)

                if current_total >= number:
                    return articles

        return articles

    content_panels = Page.content_panels + [
        FieldPanel('subtitle'),
        FieldPanel('short_description'),
        PageChooserPanel('project'),
        ImageChooserPanel('main_image'),
        ImageChooserPanel('feature_image'),
        DocumentChooserPanel('video_document'),
        StreamFieldPanel('body'),
        InlinePanel('related_article_links', label="Articles"),
        SnippetChooserPanel('primary_topic'),
    ]

    promote_panels = Page.promote_panels + [
        MultiFieldPanel([
            FieldPanel('sticky'),
            FieldPanel('sticky_for_type_section'),
            FieldPanel('slippery'),
            FieldPanel('slippery_for_type_section'),
            FieldPanel('editors_pick'),
            FieldPanel('feature_style'),
            FieldPanel('title_size'),
            FieldPanel('fullbleed_feature'),
            FieldPanel('image_overlay_opacity'),
        ],
                        heading="Featuring Settings")
    ]

    style_panels = ThemeablePage.style_panels + [
        MultiFieldPanel([
            FieldPanel('include_main_image'),
            FieldPanel('include_main_image_overlay'),
            FieldPanel('full_bleed_image_size'),
            FieldPanel('include_caption_in_footer'),
        ],
                        heading="Main Image"),
        MultiFieldPanel([
            FieldPanel('number_of_related_articles'),
        ],
                        heading="Sections")
    ]

    edit_handler = TabbedInterface([
        ObjectList(content_panels, heading='Content'),
        ObjectList(style_panels, heading='Page Style Options'),
        ObjectList(promote_panels, heading='Promote'),
        ObjectList(Page.settings_panels,
                   heading='Settings',
                   classname="settings"),
    ])
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'),
            FieldPanel('preview_subheading'),
            FieldPanel('preview_description'),
            FieldPanel('secondary_link_url'),
            FieldPanel('secondary_link_text'),
            ImageChooserPanel('preview_image'),
        ],
                        heading='Page Preview Fields',
                        classname='collapsible'),
        FieldPanel('schema_json', 'Structured Data'),
        FieldPanel('authors', 'Authors'),
        MultiFieldPanel([
            FieldPanel('date_published'),
            FieldPanel('date_filed'),
            FieldPanel('comments_close_by'),
        ],
                        'Relevant Dates',
                        classname='collapsible'),
        MultiFieldPanel(Page.settings_panels, 'Scheduled Publishing'),
        FieldPanel('language', 'Language'),
    ]

    # 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
Example #7
0
class MetaTerm(index.Indexed, MP_Node):
    """ Hierarchal "Meta" terms """
    name = models.CharField(
        max_length=50, unique=True, help_text='Keep the name short, ideally one word.'
    )
    is_archived = models.BooleanField(
        default=False, verbose_name=_("Archived"),
        help_text='Archived terms can be viewed but not set on content.'
    )
    filter_on_dashboard = models.BooleanField(
        default=True, help_text='Make available to filter on dashboard'
    )
    available_to_applicants = models.BooleanField(
        default=False, help_text='Make available to applicants'
    )
    help_text = RichTextField(features=[
        'h2', 'h3', 'bold', 'italic', 'link', 'hr', 'ol', 'ul'], blank=True)

    # node tree specific fields and attributes
    node_order_index = models.IntegerField(blank=True, default=0, editable=False)
    node_child_verbose_name = 'child'

    # important: node_order_by should NOT be changed after first Node created
    node_order_by = ['node_order_index', 'name']

    panels = [
        FieldPanel('name'),
        FieldPanel('parent'),
        MultiFieldPanel(
            [
                FieldPanel('is_archived'),
                FieldPanel('filter_on_dashboard'),
                FieldPanel('available_to_applicants'),
                FieldPanel('help_text'),
            ],
            heading="Options",
        ),
    ]

    def get_as_listing_header(self):
        depth = self.get_depth()
        rendered = render_to_string(
            'categories/admin/includes/meta_term_list_header.html',
            {
                'depth': depth,
                'depth_minus_1': depth - 1,
                'is_root': self.is_root(),
                'name': self.name,
                'is_archived': self.is_archived,
            }
        )
        return rendered
    get_as_listing_header.short_description = 'Name'
    get_as_listing_header.admin_order_field = 'name'

    def get_parent(self, *args, **kwargs):
        return super().get_parent(*args, **kwargs)
    get_parent.short_description = 'Parent'

    search_fields = [
        index.SearchField('name', partial_match=True),
    ]

    def delete(self):
        if self.is_root():
            raise PermissionDenied('Cannot delete root term.')
        else:
            super().delete()

    @classmethod
    def get_root_descendants(cls):
        # Meta terms queryset without Root node
        root_node = cls.get_first_root_node()
        if root_node:
            return root_node.get_descendants()
        return cls.objects.none()

    def __str__(self):
        return self.name

    class Meta:
        verbose_name = 'Meta Term'
        verbose_name_plural = 'Meta Terms'
Example #8
0
class Answer(Page):
    template = 'cms/answer_detail.html'

    # Determines type and whether its highlighted in overview list
    type = models.CharField(
        choices=[('answer', 'Antwoord'), ('column', 'Column')],
        max_length=100,
        default='answer',
        help_text=
        _('Choose between answer or discussion piece with a more prominent look'
          ))
    featured = models.BooleanField(default=False)

    content = RichTextField(blank=True)
    excerpt = models.CharField(
        verbose_name=_('Short description'),
        max_length=255,
        blank=False,
        null=True,
        help_text=_(
            'This helps with search engines and when sharing on social media'),
    )
    introduction = TextField(
        verbose_name=_('Introduction'),
        default='',
        blank=True,
        null=True,
        help_text=_(
            'This text is displayed above the tags, useful as a TLDR section'),
    )
    tags = ClusterTaggableManager(through=AnswerTag, blank=True)

    social_image = models.ForeignKey(
        'wagtailimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
        help_text=
        _('This is the image that will be displayed when sharing on social media'
          ),
    )

    # Freeform content of answer
    page_content = StreamField([
        ('richtext', AnswerRichTextBlock()),
        ('image', AnswerImageBlock()),
        ('quote', QuoteBlock()),
    ])

    # Which experts and how was this answered?
    answer_origin = StreamField([('origin', AnswerOriginBlock())], blank=True)

    # Related items
    related_items = StreamField([('related_items', RelatedItemsBlock())],
                                blank=True)

    parent_page_types = ['AnswerIndexPage']

    content_panels = Page.content_panels + [
        FieldPanel('type'),
        FieldPanel('featured', heading=_("Show this answer on the home page")),
        FieldPanel(
            'excerpt',
            classname='full',
        ),
        FieldPanel('introduction', classname='full'),
        MultiFieldPanel([
            InlinePanel('answer_category_relationship',
                        label=_('Categorie(n)'),
                        panels=None,
                        min_num=1)
        ],
                        heading=_('Categorie(s)')),
        FieldPanel(
            'tags',
            heading=
            "Please use tags with a maximum length of 16 characters per single word to avoid overlap in the mobile view."
        ),
        MultiFieldPanel([
            InlinePanel('answer_expert_relationship',
                        label=_('Expert(s)'),
                        panels=None,
                        min_num=1)
        ],
                        heading=_('Expert(s)')),
        StreamFieldPanel('page_content'),
        StreamFieldPanel('answer_origin'),
        StreamFieldPanel('related_items'),
        ImageChooserPanel(
            'social_image',
            help_text=_('Image to be used when sharing on social media')),
    ]

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

    @property
    def experts(self):
        experts = [n.expert for n in self.answer_expert_relationship.all()]
        return experts

    @property
    def categories(self):
        categories = [
            n.category for n in self.answer_category_relationship.all()
        ]
        return categories

    @property
    def get_tags(self):
        tags = self.tags.all()
        for tag in tags:
            tag.url = '/' + '/'.join(
                s.strip('/')
                for s in [self.get_parent().url, 'tags', tag.slug])
        return tags

    def get_references(self):
        """
        Build reference list, in the order Wagtail returns them.  ### , alphabetically to sort of comply with standards

        TODO: References for articles can be separated from the origin and make them a proper ListBlock that can be
            handled by editors as they see fit. Having the references within a StreamField of 'origins' seems counter
            intuitive.
            
        """
        ref_list = []
        try:
            component = self.answer_origin[0]
        except IndexError:
            return ref_list

        # Access streamfield elements
        for element in component.value['sources']:
            ref_list.append({
                'text': element['reference_text'],
                'url': element['url_or_doi'],
            })

        # Sort by text starting letter, best we can do for now
        # ref_list.sort(key=lambda e: e['text'])
        return ref_list

    def get_primary_expert(self):
        """
        Gets the first expert associated with this answer if it exists.
        """
        try:
            first = self.experts[0]
        except IndexError:
            return _('Unknown')
        else:
            return first

    def get_all_categories(self):
        return [{
            'title': c.name,
            'url': c.get_prefiltered_search_params()
        } for c in self.categories]

    def get_card_data(self):
        return {
            'title': self.title,
            'url': self.url,
            'author': self.get_primary_expert(),
            'categories': self.get_all_categories(),
            'type': 'answer'
        }

    def get_as_overview_row_card(self):
        if self.type == 'answer':
            return render_to_string('core/includes/answer_block.html',
                                    context=self.get_card_data())
        else:  # It's a column
            return render_to_string('core/includes/column_block.html',
                                    context=self.get_card_data())

    def get_as_home_row_card(self):
        return render_to_string('core/includes/answer_home_block.html',
                                context=self.get_card_data())

    def get_as_related_row_card(self):
        return render_to_string('core/includes/related_item_block.html',
                                context=self.get_card_data())

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

        categories = AnswerCategory.objects.all()

        context.update({
            'categories': categories,
            'answers_page': AnswerIndexPage.objects.first().url,
            'experts_page': ExpertIndexPage.objects.first(),
        })
        return context

    class Meta:
        ordering = [
            '-first_published_at',
        ]
Example #9
0
class CompositionPage(Page):
    composition_title = RichTextField(features=['bold', 'italic'])
    description = StreamField([('rich_text', RichTextBlock()),
                               ('image', ImageChooserBlock())],
                              blank=True)
    location = RichTextField(
        blank=True,
        features=['bold', 'italic', 'link', 'document-link'],
    )
    genre = ParentalManyToManyField(Genre,
                                    blank=True,
                                    related_name='compositions')
    instrumentation = ParentalManyToManyField(
        'Instrument',
        blank=True,
    )
    orchestration = RichTextField(
        blank=True,
        features=['bold', 'italic'],
        help_text=(
            'If the composition is for an ensemble, use this field to enter '
            'the orchestration of the work.'))
    duration = DurationField(null=True,
                             blank=True,
                             help_text='Expects data in the format "HH:MM:SS"')
    dedicatee = RichTextField(
        blank=True,
        features=['bold', 'italic', 'link', 'document-link'],
    )
    text_source = RichTextField(
        blank=True,
        features=['bold', 'italic', 'link', 'document-link'],
        help_text='The source of the text used in the compostition.')
    collaborator = RichTextField(
        blank=True,
        features=['bold', 'italic', 'link', 'document-link'],
        help_text='Others that Decruck collaborated with.')
    manuscript_status = RichTextField(
        blank=True,
        features=['bold', 'italic', 'link', 'document-link'],
        help_text='Notes about the location and condition of the manuscript.')
    recording = StreamField([('rich_text', RichTextBlock()),
                             ('image', ImageChooserBlock())],
                            blank=True)
    information_up_to_date = BooleanField(default=False)
    scanned = BooleanField(default=False)
    premiere = RichTextField(
        blank=True,
        features=['bold', 'italic', 'link', 'document-link'],
    )

    # For preview score
    preview_score = FileField(
        upload_to='composition_preview_scores/',
        blank=True,
        null=True,
        validators=[FileExtensionValidator(allowed_extensions=['pdf'])])
    preview_score_checksum = CharField(editable=False,
                                       max_length=256,
                                       blank=True)
    preview_score_checked = False
    preview_score_updated = False

    # Extended Date Time Format
    nat_lang_edtf_string = CharField(
        verbose_name='Natural Language Date',
        help_text=('The EDTF date in natural language. This field is help '
                   'users who aren\'t familiar with the EDTF. It does not '
                   'change how the date is represented.'),
        blank=True,
        max_length=256)
    edtf_string = CharField(
        verbose_name='EDTF Date',
        help_text=mark_safe(
            'A date in the <a href="https://www.loc.gov/standards/datetime/" '
            'target="_blank"><strong>Extended Date Time Format</strong></a>'),
        blank=True,
        max_length=256)
    lower_fuzzy = DateField(editable=False, null=True, blank=True)
    upper_fuzzy = DateField(editable=False, null=True, blank=True)
    lower_strict = DateField(editable=False, null=True, blank=True)
    upper_strict = DateField(editable=False, null=True, blank=True)
    nat_lang_year = CharField(editable=False, max_length=9, blank=True)

    def instrumentation_list(self):
        return ', '.join([str(i) for i in self.instrumentation.all()])

    class Meta:
        verbose_name = "Composition"

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

        try:
            search_idx = request.session['comp_search_index']
            if search_idx:
                idx = search_idx.index(self.pk)
                prev_url = None
                next_url = None
                if idx > 0:
                    pk = search_idx[idx - 1]
                    prev_url = CompositionPage.objects.get(pk=pk).url

                if idx < len(search_idx) - 1:
                    pk = search_idx[idx + 1]
                    next_url = CompositionPage.objects.get(pk=pk).url

                ctx['prev_url'] = prev_url
                ctx['next_url'] = next_url
                ctx['comp_search_qs'] = request.\
                    session.get('comp_search_qs', '')
        except (KeyError, ValueError):
            pass

        return ctx

    def clean(self):
        super().clean()
        # Per Django docs: validate and modify values in Model.clean()
        # https://docs.djangoproject.com/en/3.1/ref/models/instances/#django.db.models.Model.clean

        # Check that nat_lang_edtf_string and edtf_string are either both set, or both unset
        if (self.nat_lang_edtf_string
                and not self.edtf_string) or (not self.nat_lang_edtf_string
                                              and self.edtf_string):
            raise ValidationError(
                'If setting a date on a composition, an EDTF string and a natural language EDTF string must be provided.'
            )

        # Validate edtf_string
        if self.edtf_string and self.nat_lang_edtf_string:
            try:
                e = parse_edtf(self.edtf_string)
            except EDTFParseException:
                raise ValidationError({
                    'edtf_string':
                    '{} is not a valid EDTF string'.format(self.edtf_string)
                })

            self.lower_fuzzy = struct_time_to_date(e.lower_fuzzy())
            self.upper_fuzzy = struct_time_to_date(e.upper_fuzzy())
            self.lower_strict = struct_time_to_date(e.lower_strict())
            self.upper_strict = struct_time_to_date(e.upper_strict())

            if self.lower_strict.year != self.upper_strict.year:
                self.nat_lang_year = '{}-{}'.format(self.lower_strict.year,
                                                    self.upper_strict.year)
            else:
                self.nat_lang_year = str(self.lower_strict.year)

    def save(self, *args, **kwargs):
        # If there's no preview score file, then just save the model
        if not self.preview_score:
            return super().save(*args, **kwargs)

        if self.preview_score_checked:
            # This was the cause of a subtle bug. Because this method can be
            # called multiple times during model creation, leaving this flag
            # set would cause the post save hook to fire multiple times.
            self.preview_score_updated = False
            return super().save(*args, **kwargs)

        h = hashlib.md5()
        for chunk in iter(lambda: self.preview_score.read(8192), b''):
            h.update(chunk)

        self.preview_score.seek(0)
        checksum = h.hexdigest()
        if not self.preview_score_checksum == checksum:
            self.preview_score_checksum = checksum
            self.preview_score_updated = True

        self.preview_score_checked = True
        return super().save(*args, **kwargs)

    content_panels = Page.content_panels + [
        FieldPanel('composition_title'),
        StreamFieldPanel('description'),
        MultiFieldPanel(
            [FieldPanel('edtf_string'),
             FieldPanel('nat_lang_edtf_string')],
            help_text='Enter a date in the LOC Extended Date Time Format',
            heading='Date'),
        FieldPanel('location'),
        FieldPanel('instrumentation'),
        FieldPanel('orchestration'),
        FieldPanel('duration'),
        FieldPanel('dedicatee'),
        FieldPanel('premiere'),
        FieldPanel('genre'),
        FieldPanel('text_source'),
        FieldPanel('collaborator'),
        FieldPanel('manuscript_status'),
        FieldPanel('information_up_to_date'),
        FieldPanel('scanned'),
        FieldPanel('preview_score'),
        StreamFieldPanel('recording'),
    ]

    search_fields = Page.search_fields + [
        index.SearchField('description', partial_match=True),
        index.SearchField('location', partial_match=True),
        index.SearchField('dedicatee', partial_match=True),
        index.SearchField('premiere', partial_match=True),
        index.SearchField('text_source', partial_match=True),
        index.SearchField('collaborator', partial_match=True),
        index.SearchField('manuscript_status', partial_match=True),
        index.SearchField('recording', partial_match=True),
        index.RelatedFields('genre', [
            index.SearchField('genre_en', partial_match=True),
            index.SearchField('genre_fr', partial_match=True),
        ]),
        index.RelatedFields('instrumentation', [
            index.SearchField('instrument_en', partial_match=True),
            index.SearchField('instrument_fr', partial_match=True),
        ]),
    ]

    parent_page_types = ['CompositionListingPage']
Example #10
0
class HomePage(RoutablePageMixin, Page):
    """Home page model."""

    template = "home/home_page.html"
    max_count = 1

    banner_title = models.CharField(max_length=100, blank=False, null=True)
    banner_subtitle = RichTextField(features=["bold", "italic"])
    banner_image = models.ForeignKey(
        "wagtailimages.Image",
        null=True,
        blank=False,
        on_delete=models.SET_NULL,
        related_name="+",
    )
    banner_cta = models.ForeignKey(
        "wagtailcore.Page",
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="+",
    )

    content = StreamField([("cta", blocks.CTABlock())], null=True, blank=True)

    api_fields = [
        APIField("banner_title"),
        APIField("banner_subtitle"),
        APIField("banner_image"),
        APIField("banner_cta"),
    ]

    content_panels = Page.content_panels + [
        MultiFieldPanel(
            [
                FieldPanel("banner_title"),
                FieldPanel("banner_subtitle"),
                ImageChooserPanel("banner_image"),
                PageChooserPanel("banner_cta"),
            ],
            heading="Banner Options",
        ),
        MultiFieldPanel(
            [
                InlinePanel(
                    "carousel_images", max_num=5, min_num=1, label="Image")
            ],
            heading="Carousel Images",
        ),
        StreamFieldPanel("content"),
    ]

    class Meta:

        verbose_name = "Home Page"
        verbose_name_plural = "Home Pages"

    @route(r'^subscribe/$')
    def the_subscribe_page(self, request, *args, **kwargs):
        context = self.get_context(request, *args, **kwargs)
        return render(request, "home/subscribe.html", context)
class Topic(BasePage):
    resource_type = "topic"
    parent_page_types = ["Topics"]
    subpage_types = ["Topic"]
    template = "topic.html"

    # Content fields
    description = RichTextField(
        blank=True,
        default="",
        features=RICH_TEXT_FEATURES_SIMPLE,
        help_text="Optional short text description, max. 400 characters",
        max_length=400,
    )
    featured = StreamField(
        StreamBlock(
            [
                (
                    "post",
                    PageChooserBlock(target_model=(
                        "articles.Article",
                        "externalcontent.ExternalArticle",
                    )),
                ),
                ("external_page", FeaturedExternalBlock()),
            ],
            max_num=4,
            required=False,
        ),
        null=True,
        blank=True,
        help_text="Optional space for featured posts, max. 4",
    )
    tabbed_panels = StreamField(
        StreamBlock([("panel", TabbedPanelBlock())], max_num=3,
                    required=False),
        null=True,
        blank=True,
        help_text=
        "Optional tabbed panels for linking out to other resources, max. 3",
        verbose_name="Tabbed panels",
    )
    latest_articles_count = IntegerField(
        choices=RESOURCE_COUNT_CHOICES,
        default=3,
        help_text="The number of posts to display for this topic.",
    )

    # Card fields
    card_title = CharField("Title", max_length=140, blank=True, default="")
    card_description = TextField("Description",
                                 max_length=400,
                                 blank=True,
                                 default="")
    card_image = ForeignKey(
        "mozimages.MozImage",
        null=True,
        blank=True,
        on_delete=SET_NULL,
        related_name="+",
        verbose_name="Image",
    )

    # Meta
    icon = FileField(
        upload_to="topics/icons",
        blank=True,
        default="",
        help_text=("MUST be a black-on-transparent SVG icon ONLY, "
                   "with no bitmap embedded in it."),
        validators=[check_for_svg_file],
    )
    color = CharField(max_length=14, choices=COLOR_CHOICES, default="blue-40")
    keywords = ClusterTaggableManager(through=TopicTag, blank=True)

    # Content panels
    content_panels = BasePage.content_panels + [
        FieldPanel("description"),
        StreamFieldPanel("featured"),
        StreamFieldPanel("tabbed_panels"),
        FieldPanel("latest_articles_count"),
        MultiFieldPanel(
            [InlinePanel("people")],
            heading="People",
            help_text=
            "Optional list of people associated with this topic as experts",
        ),
    ]

    # Card panels
    card_panels = [
        FieldPanel("card_title"),
        FieldPanel("card_description"),
        ImageChooserPanel("card_image"),
    ]

    # Meta panels
    meta_panels = [
        MultiFieldPanel(
            [
                InlinePanel("parent_topics", label="Parent topic(s)"),
                InlinePanel("child_topics", label="Child topic(s)"),
            ],
            heading="Parent/child topic(s)",
            classname="collapsible collapsed",
            help_text=("Topics with no parent (i.e. top-level topics) will be "
                       "listed on the home page. Child topics are listed "
                       "on the parent topic’s page."),
        ),
        MultiFieldPanel(
            [FieldPanel("icon"), FieldPanel("color")],
            heading="Theme",
            help_text=(
                "Theme settings used on topic page and any tagged content. "
                "For example, a post tagged with this topic "
                "will use the color specified here as its accent color."),
        ),
        MultiFieldPanel(
            [
                FieldPanel("seo_title"),
                FieldPanel("search_description"),
                ImageChooserPanel("social_image"),
                FieldPanel("keywords"),
            ],
            heading="SEO",
            help_text=("Optional fields to override the default "
                       "title and description for SEO purposes"),
        ),
    ]

    # Settings panels
    settings_panels = [FieldPanel("slug"), FieldPanel("show_in_menus")]

    # Tabs
    edit_handler = TabbedInterface([
        ObjectList(content_panels, heading="Content"),
        ObjectList(card_panels, heading="Card"),
        ObjectList(meta_panels, heading="Meta"),
        ObjectList(settings_panels, heading="Settings", classname="settings"),
    ])

    @property
    def articles(self):
        return get_combined_articles(self, topics__topic__pk=self.pk)

    @property
    def events(self):
        """Return upcoming events for this topic,
        ignoring events in the past, ordered by start date"""
        return get_combined_events(self,
                                   topics__topic__pk=self.pk,
                                   start_date__gte=datetime.datetime.now())

    @property
    def experts(self):
        """Return Person instances for topic experts"""
        return [person.person for person in self.people.all()]

    @property
    def videos(self):
        """Return the latest videos and external videos for this topic. """
        return get_combined_videos(self, topics__topic__pk=self.pk)

    @property
    def color_value(self):
        return dict(COLOR_VALUES)[self.color]

    @property
    def subtopics(self):
        return [topic.child for topic in self.child_topics.all()]
Example #12
0
class Event(BasePage):
    resource_type = "event"
    parent_page_types = ["events.Events"]
    subpage_types = []
    template = "event.html"

    # Content fields
    description = RichTextField(
        blank=True,
        default="",
        features=RICH_TEXT_FEATURES_SIMPLE,
        help_text="Optional short text description, max. 400 characters",
        max_length=400,
    )
    image = ForeignKey(
        "mozimages.MozImage",
        null=True,
        blank=True,
        on_delete=SET_NULL,
        related_name="+",
    )
    start_date = DateField(default=datetime.date.today)
    end_date = DateField(blank=True, null=True)
    latitude = FloatField(blank=True, null=True)
    longitude = FloatField(blank=True, null=True)
    register_url = URLField("Register URL", blank=True, null=True)
    body = CustomStreamField(
        blank=True,
        null=True,
        help_text=(
            "Optional body content. Supports rich text, images, embed via URL, "
            "embed via HTML, and inline code snippets"
        ),
    )
    venue_name = CharField(max_length=100, blank=True, default="")
    venue_url = URLField("Venue URL", max_length=100, blank=True, default="")
    address_line_1 = CharField(max_length=100, blank=True, default="")
    address_line_2 = CharField(max_length=100, blank=True, default="")
    address_line_3 = CharField(max_length=100, blank=True, default="")
    city = CharField(max_length=100, blank=True, default="")
    state = CharField("State/Province/Region", max_length=100, blank=True, default="")
    zip_code = CharField("Zip/Postal code", max_length=100, blank=True, default="")
    country = CountryField(blank=True, default="")
    agenda = StreamField(
        StreamBlock([("agenda_item", AgendaItemBlock())], required=False),
        blank=True,
        null=True,
        help_text="Optional list of agenda items for this event",
    )
    speakers = StreamField(
        StreamBlock(
            [
                ("speaker", PageChooserBlock(target_model="people.Person")),
                ("external_speaker", ExternalSpeakerBlock()),
            ],
            required=False,
        ),
        blank=True,
        null=True,
        help_text="Optional list of speakers for this event",
    )

    # Card fields
    card_title = CharField("Title", max_length=140, blank=True, default="")
    card_description = TextField("Description", max_length=400, blank=True, default="")
    card_image = ForeignKey(
        "mozimages.MozImage",
        null=True,
        blank=True,
        on_delete=SET_NULL,
        related_name="+",
        verbose_name="Image",
    )

    # Meta fields
    keywords = ClusterTaggableManager(through=EventTag, blank=True)

    # Content panels
    content_panels = BasePage.content_panels + [
        FieldPanel("description"),
        MultiFieldPanel(
            [ImageChooserPanel("image")],
            heading="Image",
            help_text=(
                "Optional header image. If not specified a fallback will be used. "
                "This image is also shown when sharing this page via social media"
            ),
        ),
        MultiFieldPanel(
            [
                FieldPanel("start_date"),
                FieldPanel("end_date"),
                FieldPanel("latitude"),
                FieldPanel("longitude"),
                FieldPanel("register_url"),
            ],
            heading="Event details",
            classname="collapsible",
            help_text=mark_safe(
                "Optional time and location information for this event. Latitude and "
                "longitude are used to show a map of the event’s location. For more "
                "information on finding these values for a given location, "
                "'<a href='https://support.google.com/maps/answer/18539'>"
                "see this article</a>"
            ),
        ),
        StreamFieldPanel("body"),
        MultiFieldPanel(
            [
                FieldPanel("venue_name"),
                FieldPanel("venue_url"),
                FieldPanel("address_line_1"),
                FieldPanel("address_line_2"),
                FieldPanel("address_line_3"),
                FieldPanel("city"),
                FieldPanel("state"),
                FieldPanel("zip_code"),
                FieldPanel("country"),
            ],
            heading="Event address",
            classname="collapsible",
            help_text=(
                "Optional address fields. The city and country are also shown "
                "on event cards"
            ),
        ),
        StreamFieldPanel("agenda"),
        StreamFieldPanel("speakers"),
    ]

    # Card panels
    card_panels = [
        FieldPanel("card_title"),
        FieldPanel("card_description"),
        ImageChooserPanel("card_image"),
    ]

    # Meta panels
    meta_panels = [
        MultiFieldPanel(
            [InlinePanel("topics")],
            heading="Topics",
            help_text=(
                "These are the topic pages the event will appear on. The first topic "
                "in the list will be treated as the primary topic and will be shown "
                "in the page’s related content."
            ),
        ),
        MultiFieldPanel(
            [
                FieldPanel("seo_title"),
                FieldPanel("search_description"),
                ImageChooserPanel("social_image"),
                FieldPanel("keywords"),
            ],
            heading="SEO",
            help_text=(
                "Optional fields to override the default title and description "
                "for SEO purposes"
            ),
        ),
    ]

    # Settings panels
    settings_panels = [FieldPanel("slug")]

    edit_handler = TabbedInterface(
        [
            ObjectList(content_panels, heading="Content"),
            ObjectList(card_panels, heading="Card"),
            ObjectList(meta_panels, heading="Meta"),
            ObjectList(settings_panels, heading="Settings", classname="settings"),
        ]
    )

    @property
    def is_upcoming(self):
        """Returns whether an event is in the future."""
        return self.start_date >= datetime.date.today()

    @property
    def primary_topic(self):
        """Return the first (primary) topic specified for the event."""
        article_topic = self.topics.first()
        return article_topic.topic if article_topic else None

    @property
    def month_group(self):
        return self.start_date.replace(day=1)

    @property
    def country_group(self):
        return (
            {"slug": self.country.code.lower(), "title": self.country.name}
            if self.country
            else {"slug": ""}
        )

    @property
    def event_dates(self):
        """Return a formatted string of the event start and end dates"""
        event_dates = self.start_date.strftime("%b %-d")
        if self.end_date and self.end_date != self.start_date:
            event_dates += " &ndash; "
            start_month = self.start_date.strftime("%m")
            if self.end_date.strftime("%m") == start_month:
                event_dates += self.end_date.strftime("%-d")
            else:
                event_dates += self.end_date.strftime("%b %-d")
        return event_dates

    @property
    def event_dates_full(self):
        """Return a formatted string of the event start and end dates,
        including the year"""
        return self.event_dates + self.start_date.strftime(", %Y")

    def has_speaker(self, person):
        for speaker in self.speakers:  # pylint: disable=not-an-iterable
            if speaker.block_type == "speaker" and str(speaker.value) == str(
                person.title
            ):
                return True
        return False
Example #13
0
class BlogIndexPage(Page):
    intro = RichTextField(blank=True)

    content_panels = Page.content_panels + [
        FieldPanel('intro', classname="full")
    ]
Example #14
0
class PartnerPage(BasePage):
    STATUS = [('active', 'Active'), ('inactive', 'Inactive')]

    class Meta:
        verbose_name = _('Partner Page')

    parent_page_types = ['partner.PartnerIndexPage']
    subpage_types = []

    status = models.CharField(choices=STATUS,
                              default='current_partner',
                              max_length=20)
    public = models.BooleanField(default=True)
    description = RichTextField(blank=True)
    web_url = models.URLField(blank=True)
    logo = models.OneToOneField('images.CustomImage',
                                null=True,
                                blank=True,
                                related_name='+',
                                on_delete=models.SET_NULL)

    content_panels = Page.content_panels + [
        FieldPanel('status'),
        FieldPanel('public'),
        FieldPanel('description'),
        FieldPanel('web_url'),
        ImageChooserPanel('logo'),
    ]

    def __str__(self):
        return self.title

    def get_context(self, request):
        context = super(PartnerPage, self).get_context(request)
        context['total_investments'] = sum(
            investment.amount_committed
            for investment in self.investments.all())
        return context

    def get_absolute_url(self):
        return self.url

    @property
    def category_questions(self):
        category_questions = {}
        if not self.investments.exists():
            return
        for investment in self.investments.all():
            for category in investment.categories.all():
                if category.name in category_questions.keys():
                    if category.value not in category_questions[category.name]:
                        category_questions[category.name].append(
                            category.value)
                else:
                    category_questions[category.name] = [category.value]
        return category_questions

    def serve(self, request, *args, **kwargs):
        if not self.public:
            raise Http404
        return super(PartnerPage, self).serve(request, *args, **kwargs)
Example #15
0
class BlogPageAbstract(Page):
    body = RichTextField(verbose_name=_('body'), blank=True)
    tags = ClusterTaggableManager(through='BlogPageTag', blank=True)
    date = models.DateField(
        _("Post date"),
        default=datetime.datetime.today,
        help_text=_("This date may be displayed on the blog post. It is not "
                    "used to schedule posts to go live at a later date."))
    header_image = models.ForeignKey(get_image_model_string(),
                                     null=True,
                                     blank=True,
                                     on_delete=models.SET_NULL,
                                     related_name='+',
                                     verbose_name=_('Header image'))
    author = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        blank=True,
        null=True,
        limit_choices_to=limit_author_choices,
        verbose_name=_('Author'),
        on_delete=models.SET_NULL,
        related_name='author_pages',
    )

    search_fields = Page.search_fields + [
        index.SearchField('body'),
    ]
    blog_categories = models.ManyToManyField('BlogCategory',
                                             through='BlogCategoryBlogPage',
                                             blank=True)

    settings_panels = [
        MultiFieldPanel([
            FieldRowPanel([
                FieldPanel('go_live_at'),
                FieldPanel('expire_at'),
            ],
                          classname="label-above"),
        ],
                        'Scheduled publishing',
                        classname="publishing"),
        FieldPanel('date'),
        FieldPanel('author'),
    ]

    def save_revision(self, *args, **kwargs):
        if not self.author:
            self.author = self.owner
        return super().save_revision(*args, **kwargs)

    def get_absolute_url(self):
        return self.url

    class Meta:
        abstract = True
        verbose_name = _('Blog page')
        verbose_name_plural = _('Blog pages')

    api_fields = [APIField('body')]
    content_panels = [
        FieldPanel('title', classname="full title"),
        MultiFieldPanel([
            FieldPanel('tags'),
            InlinePanel('categories', label=_("Categories")),
        ],
                        heading="Tags and Categories"),
        ImageChooserPanel('header_image'),
        FieldPanel('body', classname="full"),
    ]
Example #16
0
class ScorePage(RoutablePageMixin, Page):
    cover_image = ForeignKey('wagtailimages.Image',
                             null=True,
                             blank=True,
                             on_delete=PROTECT,
                             related_name='cover_image')
    description = StreamField([('rich_text', RichTextBlock()),
                               ('image', ImageChooserBlock())])
    duration = DurationField(null=True,
                             blank=True,
                             help_text='Expects data in the format "HH:MM:SS"')
    file = FileField(
        upload_to='scores/',
        validators=[FileExtensionValidator(allowed_extensions=['pdf', 'zip'])])
    preview_score = FileField(
        upload_to='preview_scores/',
        validators=[FileExtensionValidator(allowed_extensions=['pdf'])])
    preview_score_checksum = CharField(editable=False,
                                       max_length=256,
                                       blank=True)
    preview_score_checked = False
    preview_score_updated = False
    genre = ParentalManyToManyField(Genre, blank=True, related_name='scores')
    date = CharField(max_length=256, blank=True)
    instrumentation = ParentalManyToManyField(
        'Instrument',
        blank=True,
        help_text='The instrumentation of the compostition.')
    price = DecimalField(max_digits=6, decimal_places=2)
    materials = RichTextField(
        blank=True,
        features=['bold', 'italic', 'link', 'document-link'],
        help_text='The materials sent in the PDF file.')

    def save(self, *args, **kwargs):
        if self.preview_score_checked:
            # This was the cause of a subtle bug. Because this method can be
            # called multiple times during model creation, leaving this flag
            # set would cause the post save hook to fire multiple times.
            self.preview_score_updated = False
            return super().save(*args, **kwargs)

        h = hashlib.md5()
        for chunk in iter(lambda: self.preview_score.read(8192), b''):
            h.update(chunk)

        self.preview_score.seek(0)
        checksum = h.hexdigest()
        if not self.preview_score_checksum == checksum:
            self.preview_score_checksum = checksum
            self.preview_score_updated = True

        self.preview_score_checked = True
        return super().save(*args, **kwargs)

    @route(r'^([\w-]+)/$')
    def get_score_file(self, request, score_link_slug):
        if request.method == 'GET':
            item_link = get_object_or_404(OrderItemLink, slug=score_link_slug)

            if item_link.is_expired():
                raise Http404()

            item_link.access_ip = request.META.get('REMOTE_ADDR', '0.0.0.0')
            item_link.save()

            return render(request, "main/score_page_download.html", {
                'self': self,
                'page': self,
            })
        else:
            raise Http404()

    @route(r'^$')
    def score(self, request):
        cart_page = ShoppingCartPage.objects.first()
        if request.method == 'POST':
            in_cart = toggle_score_in_cart(request, self.pk)
            return render(
                request, "main/score_page.html", {
                    'self': self,
                    'page': self,
                    'in_cart': in_cart,
                    'cart_page': cart_page
                })
        else:
            return render(
                request, "main/score_page.html", {
                    'self': self,
                    'page': self,
                    'in_cart': score_in_cart(request, self.pk),
                    'cart_page': cart_page
                })

    class Meta:
        verbose_name = "Score Page"

    content_panels = Page.content_panels + [
        FieldPanel('date'),
        FieldPanel('duration'),
        FieldPanel('genre'),
        FieldPanel('instrumentation'),
        FieldPanel('price'),
        StreamFieldPanel('description'),
        FieldPanel('materials'),
        FieldPanel('file'),
        FieldPanel('preview_score'),
        ImageChooserPanel('cover_image')
    ]
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)
    persistent_body = StreamField([
        ('content', blocks.RichTextBlock(icon='edit')),
        ('content_with_anchor', molecules.ContentWithAnchor()),
        ('heading', v1_blocks.HeadingBlock(required=False)),
        ('image', molecules.ContentImage()),
        ('table_block',
         organisms.AtomicTableBlock(table_options={'renderer': 'html'})),
        ('reusable_text',
         v1_blocks.ReusableTextChooserBlock('v1.ReusableText')),
    ],
                                  blank=True)
    start_dt = models.DateTimeField("Start")
    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)
    archive_video_id = models.CharField(
        'YouTube video ID (archive)',
        null=True,
        blank=True,
        max_length=11,
        # This is a reasonable but not official regex for YouTube video IDs.
        # https://webapps.stackexchange.com/a/54448
        validators=[RegexValidator(regex=r'^[\w-]{11}$')],
        help_text=organisms.VideoPlayer.YOUTUBE_ID_HELP_TEXT)
    live_stream_availability = models.BooleanField(
        "Streaming?",
        default=False,
        blank=True,
        help_text='Check if this event will be streamed live. This causes the '
        'event page to show the parts necessary for live streaming.')
    live_video_id = models.CharField(
        'YouTube video ID (live)',
        null=True,
        blank=True,
        max_length=11,
        # This is a reasonable but not official regex for YouTube video IDs.
        # https://webapps.stackexchange.com/a/54448
        validators=[RegexValidator(regex=r'^[\w-]{11}$')],
        help_text=organisms.VideoPlayer.YOUTUBE_ID_HELP_TEXT)
    live_stream_date = models.DateTimeField(
        "Go Live Date",
        blank=True,
        null=True,
        help_text='Enter the date and time that the page should switch from '
        'showing the venue image to showing the live video feed. '
        'This is typically 15 minutes prior to the event start time.')

    # Venue content fields
    venue_coords = models.CharField(max_length=100, blank=True)
    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_zipcode = models.CharField(max_length=12, blank=True)
    venue_image_type = models.CharField(
        max_length=8,
        choices=(
            ('map', 'Map'),
            ('image', 'Image (selected below)'),
            ('none', 'No map or image'),
        ),
        default='map',
        help_text='If "Image" is chosen here, you must select the image you '
        'want below. It should be sized to 1416x796.',
    )
    venue_image = models.ForeignKey('v1.CFGOVImage',
                                    null=True,
                                    blank=True,
                                    on_delete=models.SET_NULL,
                                    related_name='+')
    post_event_image_type = models.CharField(
        max_length=16,
        choices=(
            ('placeholder', 'Placeholder image'),
            ('image', 'Unique image (selected below)'),
        ),
        default='placeholder',
        verbose_name='Post-event image type',
        help_text='Choose what to display after an event concludes. This will '
        'be overridden by embedded video if the "YouTube video ID '
        '(archive)" field on the previous tab is populated. If '
        '"Unique image" is chosen here, you must select the image '
        'you want below. It should be sized to 1416x796.',
    )
    post_event_image = models.ForeignKey('v1.CFGOVImage',
                                         null=True,
                                         blank=True,
                                         on_delete=models.SET_NULL,
                                         related_name='+')

    # Agenda content fields
    agenda_items = StreamField([('item', AgendaItemBlock())], blank=True)

    objects = CFGOVPageManager()

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

    # General content tab
    content_panels = CFGOVPage.content_panels + [
        FieldPanel('body'),
        FieldRowPanel([
            FieldPanel('start_dt', classname="col6"),
            FieldPanel('end_dt', classname="col6"),
        ]),
        MultiFieldPanel([
            FieldPanel('archive_body'),
            ImageChooserPanel('archive_image'),
            DocumentChooserPanel('video_transcript'),
            DocumentChooserPanel('speech_transcript'),
            FieldPanel('flickr_url'),
            FieldPanel('archive_video_id'),
        ],
                        heading='Archive Information'),
        FieldPanel('live_body'),
        FieldPanel('future_body'),
        StreamFieldPanel('persistent_body'),
        MultiFieldPanel([
            FieldPanel('live_stream_availability'),
            FieldPanel('live_video_id'),
            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_zipcode'),
        ],
                        heading='Venue Address'),
        MultiFieldPanel([
            FieldPanel('venue_image_type'),
            ImageChooserPanel('venue_image'),
        ],
                        heading='Venue Image'),
        MultiFieldPanel([
            FieldPanel('post_event_image_type'),
            ImageChooserPanel('post_event_image'),
        ],
                        heading='Post-event Image')
    ]
    # 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 event_state(self):
        if self.end_dt:
            end = convert_date(self.end_dt, 'America/New_York')
            if end < datetime.now(timezone('America/New_York')):
                return 'past'

        if self.live_stream_date:
            start = convert_date(self.live_stream_date, 'America/New_York')
        else:
            start = convert_date(self.start_dt, 'America/New_York')

        if datetime.now(timezone('America/New_York')) > start:
            return 'present'

        return 'future'

    @property
    def page_js(self):
        if ((self.live_stream_date and self.event_state == 'present')
                or (self.archive_video_id and self.event_state == 'past')):
            return super(EventPage, self).page_js + ['video-player.js']

        return super(EventPage, self).page_js

    def location_image_url(self, scale='2', size='276x155', zoom='12'):
        if not self.venue_coords:
            self.venue_coords = get_venue_coords(self.venue_city,
                                                 self.venue_state)
        api_url = 'https://api.mapbox.com/styles/v1/mapbox/streets-v11/static'
        static_map_image_url = '{}/{},{}/{}?access_token={}'.format(
            api_url, self.venue_coords, zoom, size,
            settings.MAPBOX_ACCESS_TOKEN)

        return static_map_image_url

    def clean(self):
        super(EventPage, self).clean()
        if self.venue_image_type == 'image' and not self.venue_image:
            raise ValidationError(
                {'venue_image': 'Required if "Venue image type" is "Image".'})
        if self.post_event_image_type == 'image' and not self.post_event_image:
            raise ValidationError({
                'post_event_image':
                'Required if "Post-event image type" is '
                '"Image".'
            })

    def save(self, *args, **kwargs):
        self.venue_coords = get_venue_coords(self.venue_city, self.venue_state)
        return super(EventPage, self).save(*args, **kwargs)

    def get_context(self, request):
        context = super(EventPage, self).get_context(request)
        context['event_state'] = self.event_state
        return context
Example #18
0
class ParticipatePage2(PrimaryPage):
    parent_page_types = ['Homepage']
    template = 'wagtailpages/static/participate_page2.html'

    ctaHero = models.ForeignKey(
        'wagtailimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='primary_hero_participate',
        verbose_name='Primary Hero Image',
    )

    ctaHeroHeader = models.TextField(blank=True, )

    ctaHeroSubhead = RichTextField(
        features=[
            'bold',
            'italic',
            'link',
        ],
        blank=True,
    )

    ctaCommitment = models.TextField(blank=True, )

    ctaButtonTitle = models.CharField(
        verbose_name='Button Text',
        max_length=250,
        blank=True,
    )

    ctaButtonURL = models.TextField(
        verbose_name='Button URL',
        blank=True,
    )

    ctaHero2 = models.ForeignKey(
        'wagtailimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='primary_hero_participate2',
        verbose_name='Primary Hero Image',
    )

    ctaHeroHeader2 = models.TextField(blank=True, )

    ctaHeroSubhead2 = RichTextField(
        features=[
            'bold',
            'italic',
            'link',
        ],
        blank=True,
    )

    ctaCommitment2 = models.TextField(blank=True, )

    ctaButtonTitle2 = models.CharField(
        verbose_name='Button Text',
        max_length=250,
        blank=True,
    )

    ctaButtonURL2 = models.TextField(
        verbose_name='Button URL',
        blank=True,
    )

    ctaHero3 = models.ForeignKey(
        'wagtailimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='primary_hero_participate3',
        verbose_name='Primary Hero Image',
    )

    ctaHeroHeader3 = models.TextField(blank=True, )

    ctaHeroSubhead3 = RichTextField(
        features=[
            'bold',
            'italic',
            'link',
        ],
        blank=True,
    )

    ctaCommitment3 = models.TextField(blank=True, )

    ctaFacebook3 = models.TextField(blank=True, )

    ctaTwitter3 = models.TextField(blank=True, )

    ctaEmailShareBody3 = models.TextField(blank=True, )

    ctaEmailShareSubject3 = models.TextField(blank=True, )

    h2 = models.TextField(blank=True, )

    h2Subheader = models.TextField(
        blank=True,
        verbose_name='H2 Subheader',
    )

    content_panels = Page.content_panels + [
        MultiFieldPanel([
            ImageChooserPanel('ctaHero'),
            FieldPanel('ctaHeroHeader'),
            FieldPanel('ctaHeroSubhead'),
            FieldPanel('ctaCommitment'),
            FieldPanel('ctaButtonTitle'),
            FieldPanel('ctaButtonURL'),
        ],
                        heading="Primary CTA"),
        FieldPanel('h2'),
        FieldPanel('h2Subheader'),
        InlinePanel(
            'featured_highlights', label='Highlights Group 1', max_num=3),
        MultiFieldPanel([
            ImageChooserPanel('ctaHero2'),
            FieldPanel('ctaHeroHeader2'),
            FieldPanel('ctaHeroSubhead2'),
            FieldPanel('ctaCommitment2'),
            FieldPanel('ctaButtonTitle2'),
            FieldPanel('ctaButtonURL2'),
        ],
                        heading="CTA 2"),
        InlinePanel(
            'featured_highlights2', label='Highlights Group 2', max_num=6),
        MultiFieldPanel([
            ImageChooserPanel('ctaHero3'),
            FieldPanel('ctaHeroHeader3'),
            FieldPanel('ctaHeroSubhead3'),
            FieldPanel('ctaCommitment3'),
            FieldPanel('ctaFacebook3'),
            FieldPanel('ctaTwitter3'),
            FieldPanel('ctaEmailShareSubject3'),
            FieldPanel('ctaEmailShareBody3'),
        ],
                        heading="CTA 3"),
        InlinePanel('cta4', label='CTA Group 4', max_num=3),
    ]
class AnswerPage(CFGOVPage):
    """Page type for Ask CFPB answers."""

    from ask_cfpb.models.django 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)

    share_and_print = models.BooleanField(
        default=False,
        help_text="Include share and print buttons above answer.")

    content_panels = CFGOVPage.content_panels + [
        MultiFieldPanel([
            FieldPanel('last_edited'),
            FieldPanel('question'),
            FieldPanel('statement'),
            FieldPanel('short_answer')
        ],
                        heading="Page content",
                        classname="collapsible"),
        FieldPanel('share_and_print'),
        StreamFieldPanel('answer_content'),
        MultiFieldPanel([
            SnippetChooserPanel('related_resource'),
            AutocompletePanel('related_questions',
                              target_model='ask_cfpb.AnswerPage')
        ],
                        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',
                              target_model='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_meta_description(self):
        """Determine what the page's meta and OpenGraph description should be

        Checks several different possible fields in order of preference.
        If none are found, returns an empty string, which is preferable to a
        generic description repeated on many pages.

        This method is overriding the standard one on CFGOVPage to factor in
        Ask CFPB AnswerPage-specific fields.
        """

        preference_order = [
            'search_description',
            'short_answer',
            'first_text',
        ]
        candidates = {}

        if self.search_description:
            candidates['search_description'] = self.search_description
        if self.short_answer:
            candidates['short_answer'] = strip_tags(self.short_answer)
        if hasattr(self, 'answer_content'):
            for block in self.answer_content:
                if block.block_type == 'text':
                    candidates['first_text'] = truncate_by_words_and_chars(
                        strip_tags(block.value['content'].source),
                        word_limit=35,
                        char_limit=160)
                    break

        for entry in preference_order:
            if candidates.get(entry):
                return candidates[entry]

        return ''

    def get_context(self, request, *args, **kwargs):
        # self.get_meta_description() is not called here because it is called
        # and added to the context by CFGOVPage's get_context() method.
        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['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 answer_content_text(self):
        raw_text = extract_raw_text(self.answer_content.stream_data)
        return strip_tags(" ".join([self.short_answer, raw_text]))

    def answer_content_data(self):
        return truncate_by_words_and_chars(self.answer_content_text())

    def short_answer_data(self):
        return ' '.join(
            RichTextField.get_searchable_content(self, self.short_answer))

    def text(self):
        short_answer = self.short_answer_data()
        answer_text = self.answer_content_text()
        full_text = f"{short_answer}\n\n{answer_text}\n\n{self.question}"
        return full_text

    def __str__(self):
        if self.answer_base:
            return f"{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
Example #20
0
class PostPage(Page):
    body = RichTextField(blank=True)
    content_panels = Page.content_panels + [
        FieldPanel('body', classname='full'),
    ]
Example #21
0
class CustomMedia(AbstractMedia):
    fancy_caption = RichTextField(blank=True)

    admin_form_fields = Media.admin_form_fields + ("fancy_caption", )
Example #22
0
class ArticlePage(
        BasicPageAbstract,
        ContentPage,
        FeatureablePageAbstract,
        FromTheArchivesPageAbstract,
        ShareablePageAbstract,
        ThemeablePageAbstract,
):
    class ArticleTypes(models.TextChoices):
        CIGI_IN_THE_NEWS = ('cigi_in_the_news', 'CIGI in the News')
        INTERVIEW = ('interview', 'Interview')
        NEWS_RELEASE = ('news_release', 'News Release')
        OP_ED = ('op_ed', 'Op-Ed')
        OPINION = ('opinion', 'Opinion')

    class Languages(models.TextChoices):
        DA = ('da', 'Danish')
        DE = ('de', 'German')
        EL = ('el', 'Greek')
        EN = ('en', 'English')
        ES = ('es', 'Spanish')
        FR = ('fr', 'French')
        ID = ('id', 'Indonesian')
        IT = ('it', 'Italian')
        NL = ('nl', 'Dutch')
        PL = ('pl', 'Polish')
        PT = ('pt', 'Portugese')
        RO = ('ro', 'Romanian')
        SK = ('sk', 'Slovak')
        SV = ('sv', 'Swedish')
        TR = ('tr', 'Turkish')
        ZH = ('zh', 'Chinese')

    class HeroTitlePlacements(models.TextChoices):
        BOTTOM = ('bottom', 'Bottom')
        TOP = ('top', 'Top')

    article_series = models.ForeignKey(
        'wagtailcore.Page',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
        verbose_name='Opinion series',
    )
    article_type = models.ForeignKey(
        'articles.ArticleTypePage',
        null=True,
        blank=False,
        on_delete=models.SET_NULL,
        related_name='articles',
    )
    body = StreamField(
        BasicPageAbstract.body_default_blocks + [
            BasicPageAbstract.body_accordion_block,
            BasicPageAbstract.body_autoplay_video_block,
            BasicPageAbstract.body_chart_block,
            BasicPageAbstract.body_embedded_tiktok_block,
            BasicPageAbstract.body_external_quote_block,
            BasicPageAbstract.body_external_video_block,
            BasicPageAbstract.body_extract_block,
            BasicPageAbstract.body_highlight_title_block,
            BasicPageAbstract.body_image_full_bleed_block,
            BasicPageAbstract.body_image_scroll_block,
            BasicPageAbstract.body_poster_block,
            BasicPageAbstract.body_pull_quote_left_block,
            BasicPageAbstract.body_pull_quote_right_block,
            BasicPageAbstract.body_recommended_block,
            BasicPageAbstract.body_text_border_block,
            BasicPageAbstract.body_tool_tip_block,
            BasicPageAbstract.body_tweet_block,
        ],
        blank=True,
    )
    embed_youtube = models.URLField(
        blank=True,
        verbose_name='YouTube Embed',
        help_text=
        'Enter the YouTube URL (https://www.youtube.com/watch?v=4-Xkn1U1DkA) or short URL (https://youtu.be/o5acQ2GxKbQ) to add an embedded video.',
    )
    embed_youtube_label = models.CharField(
        max_length=255,
        blank=True,
        help_text='Add a label to appear below the embedded video.',
    )
    footnotes = RichTextField(
        blank=True,
        features=[
            'bold',
            'endofarticle',
            'h3',
            'h4',
            'italic',
            'link',
            'ol',
            'ul',
            'subscript',
            'superscript',
            'anchor',
        ],
    )
    hero_title_placement = models.CharField(
        blank=True,
        max_length=16,
        choices=HeroTitlePlacements.choices,
        verbose_name='Hero Title Placement',
        help_text=
        'Placement of the title within the hero section. Currently only works on the Longform 2 theme.',
    )
    hide_excerpt = models.BooleanField(
        default=False,
        verbose_name='Hide Excerpt',
        help_text=
        'For "CIGI in the News" only: when enabled, hide excerpt and display full article instead',
    )
    image_banner = models.ForeignKey(
        'images.CigionlineImage',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
        verbose_name='Banner Image',
    )
    image_banner_small = models.ForeignKey('images.CigionlineImage',
                                           null=True,
                                           blank=True,
                                           on_delete=models.SET_NULL,
                                           related_name='+',
                                           verbose_name='Banner Image Small')
    image_poster = models.ForeignKey(
        'images.CigionlineImage',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
        verbose_name='Poster Image',
        help_text='A poster image used in feature sections',
    )
    interviewers = StreamField(
        [
            ('interviewer',
             PageChooserBlock(required=True, page_type='people.PersonPage')),
        ],
        blank=True,
    )
    language = models.CharField(
        blank=True,
        max_length=2,
        choices=Languages.choices,
        verbose_name='Language',
        help_text=
        'If this content is in a language other than English, please select the language from the list.',
    )
    multimedia_series = models.ForeignKey(
        'wagtailcore.Page',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
    )
    related_files = StreamField(
        [
            ('file', DocumentChooserBlock()),
        ],
        blank=True,
    )
    short_description = RichTextField(
        blank=True,
        null=False,
        features=['bold', 'italic', 'link'],
    )
    video_banner = models.ForeignKey(
        'wagtailmedia.Media',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
        verbose_name='Banner Video',
    )
    website_button_text = models.CharField(
        blank=True,
        max_length=64,
        help_text=
        'Override the button text for the article website. If empty, the button will read "View Full Article".'
    )
    website_url = models.URLField(blank=True, max_length=512)
    works_cited = RichTextField(
        blank=True,
        features=[
            'bold',
            'endofarticle',
            'h3',
            'h4',
            'italic',
            'link',
            'ol',
            'ul',
            'subscript',
            'superscript',
        ],
    )

    # Reference field for the Drupal-Wagtail migrator. Can be removed after.
    drupal_node_id = models.IntegerField(blank=True, null=True)

    @property
    def cigi_people_mentioned_ids(self):
        return [item.person.id for item in self.cigi_people_mentioned.all()]

    @property
    def expired_image(self):
        if self.publishing_date:
            return self.publishing_date < datetime.datetime(
                2017, 1, 1).astimezone(pytz.timezone('America/Toronto'))
        return False

    @property
    def article_series_description(self):
        if self.article_series:
            return self.article_series.specific.series_items_description
        return None

    @property
    def article_series_disclaimer(self):
        if self.article_series:
            for series_item in self.article_series.specific.article_series_items:
                if series_item.content_page.specific == self and not series_item.hide_series_disclaimer:
                    return self.article_series.specific.series_items_disclaimer
        return None

    def is_opinion(self):
        return self.article_type.title in [
            'Op-Eds',
            'Opinion',
        ]

    def get_template(self, request, *args, **kwargs):
        standard_template = super(ArticlePage,
                                  self).get_template(request, *args, **kwargs)
        if self.theme:
            return f'themes/{self.get_theme_dir()}/article_page.html'
        return standard_template

    content_panels = [
        BasicPageAbstract.title_panel,
        MultiFieldPanel([
            FieldPanel('short_description'),
            StreamFieldPanel('body'),
            FieldPanel('footnotes'),
            FieldPanel('works_cited'),
        ],
                        heading='Body',
                        classname='collapsible collapsed'),
        MultiFieldPanel(
            [
                PageChooserPanel(
                    'article_type',
                    ['articles.ArticleTypePage'],
                ),
                FieldPanel('hide_excerpt'),
                FieldPanel('publishing_date'),
                FieldPanel('website_url'),
                FieldPanel('website_button_text'),
                FieldPanel('language'),
            ],
            heading='General Information',
            classname='collapsible collapsed',
        ),
        ContentPage.authors_panel,
        MultiFieldPanel(
            [
                ImageChooserPanel('image_hero'),
                ImageChooserPanel('image_poster'),
                ImageChooserPanel('image_banner'),
                ImageChooserPanel('image_banner_small'),
            ],
            heading='Images',
            classname='collapsible collapsed',
        ),
        MultiFieldPanel(
            [
                FieldPanel('embed_youtube'),
                FieldPanel('embed_youtube_label'),
                MediaChooserPanel('video_banner'),
            ],
            heading='Media',
            classname='collapsible collapsed',
        ),
        ContentPage.recommended_panel,
        MultiFieldPanel(
            [
                FieldPanel('topics'),
                FieldPanel('projects'),
                PageChooserPanel(
                    'article_series',
                    ['articles.ArticleSeriesPage'],
                ),
                PageChooserPanel(
                    'multimedia_series',
                    ['multimedia.MultimediaSeriesPage'],
                ),
                InlinePanel('cigi_people_mentioned', label='People Mentioned'),
                StreamFieldPanel('interviewers'),
                StreamFieldPanel('related_files'),
            ],
            heading='Related',
            classname='collapsible collapsed',
        ),
        FromTheArchivesPageAbstract.from_the_archives_panel,
    ]
    promote_panels = Page.promote_panels + [
        FeatureablePageAbstract.feature_panel,
        ShareablePageAbstract.social_panel,
        SearchablePageAbstract.search_panel,
    ]
    settings_panels = Page.settings_panels + [
        ThemeablePageAbstract.theme_panel,
    ]

    search_fields = BasicPageAbstract.search_fields \
        + ContentPage.search_fields \
        + [
            index.FilterField('article_type'),
            index.FilterField('cigi_people_mentioned_ids'),
            index.FilterField('publishing_date'),
        ]

    parent_page_types = ['articles.ArticleListPage']
    subpage_types = []
    templates = 'articles/article_page.html'

    @property
    def is_title_bottom(self):
        return self.title in [
            'Can the G20 Save Globalization\'s Waning Reputation?',
            'Shoshana Zuboff on the Undetectable, Indecipherable World of Surveillance Capitalism'
        ]

    @property
    def article_series_category(self):
        category = ''
        for series_item in self.article_series.specific.article_series_items:
            if series_item.category_title:
                category = series_item.category_title
            if series_item.content_page.id == self.id:
                return category

    class Meta:
        verbose_name = 'Opinion'
        verbose_name_plural = 'Opinions'
Example #23
0
class ArticlePage(ThemeablePage, FeatureStyleFields, Promotable,
                  ShareLinksMixin, PageLayoutOptions, VideoDocumentMixin):
    excerpt = RichTextField(blank=True, default="")
    body = article_fields.BodyField()
    chapters = article_fields.ChapterField(blank=True, null=True)
    table_of_contents_heading = models.TextField(blank=True,
                                                 default="Table of Contents")
    citations_heading = models.TextField(blank=True, default="Works Cited")
    endnotes_heading = models.TextField(blank=True, default="End Notes")
    endnote_identifier_style = models.CharField(
        max_length=20,
        default="roman-lower",
        choices=(('roman-lower', 'Roman Numerals - Lowercase'),
                 ('roman-upper', 'Roman Numerals - Uppercase'), ('numbers',
                                                                 'Numbers')))

    main_image = models.ForeignKey('images.AttributedImage',
                                   null=True,
                                   blank=True,
                                   on_delete=models.SET_NULL,
                                   related_name='+')
    feature_image = models.ForeignKey('images.AttributedImage',
                                      null=True,
                                      blank=True,
                                      on_delete=models.SET_NULL,
                                      related_name='+')
    primary_topic = models.ForeignKey('articles.Topic',
                                      null=True,
                                      blank=True,
                                      on_delete=models.SET_NULL,
                                      related_name='articles')
    category = models.ForeignKey('articles.ArticleCategory',
                                 related_name='%(class)s',
                                 on_delete=models.SET_NULL,
                                 null=True,
                                 default=1)

    include_author_block = models.BooleanField(default=True)

    visualization = models.BooleanField(default=False)
    interview = models.BooleanField(default=False)
    video = models.BooleanField(default=False)
    number_of_related_articles = models.PositiveSmallIntegerField(
        default=6, verbose_name="Number of Related Articles to Show")
    json_file = article_fields.WagtailFileField(
        max_length=255,
        blank=True,
        null=True,
        verbose_name='JSON file',
        help_text=
        "Only provide if you know your template will be filled with the contents of a JSON data file."
    )

    project = models.ForeignKey(
        "projects.ProjectPage",
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
    )

    _response_to = False

    search_fields = Page.search_fields + [
        index.SearchField('excerpt', partial_match=True),
        index.SearchField('body', partial_match=True),
        index.SearchField('chapters', partial_match=True),
        index.SearchField('get_primary_topic_name', partial_match=True),
        index.SearchField('get_category_name', partial_match=True),
        index.SearchField('get_topic_names', partial_match=True),
        index.SearchField('get_author_names', partial_match=True),
    ]

    def get_primary_topic_name(self):
        if self.primary_topic:
            return self.primary_topic.name
        return ""

    def get_category_name(self):
        if self.category:
            return self.category.name
        return ""

    def get_topic_names(self):
        return '\n'.join([
            link.topic.name if link.topic else ""
            for link in self.topic_links.all()
        ])

    def get_author_names(self):
        return '\n'.join([
            author_link.author.full_name if author_link.author else ""
            for author_link in self.author_links.all()
        ])

    @property
    def authors(self):
        author_list = []
        for link in self.author_links.all():
            if link.author:
                author_list.append((link.author))
        return author_list

    @property
    def series_articles(self):
        related_series_data = []
        for link in self.series_links.all():
            series_page = link.series
            series_articles = series_page.articles
            series_articles.remove(self)
            related_series_data.append((series_page, series_articles))
        return related_series_data

    @property
    def topics(self):
        primary_topic = self.primary_topic
        all_topics = [link.topic for link in self.topic_links.all()]
        if primary_topic:
            all_topics.append(primary_topic)
        all_topics = list(set(all_topics))
        if len(all_topics) > 0:
            all_topics.sort(key=attrgetter('name'))
        return all_topics

    @property
    def response_to(self):
        if self._response_to is False:
            response_to_count = self.response_to_links.count()
            if response_to_count > 1:
                logger.warning(
                    'ArticlePage(pk={0}) appears to be a response to multiple articles. Only the first one is being returned.'
                    .format(self.pk))
            if response_to_count != 0:
                self._response_to = self.response_to_links.first().response_to
            else:
                self._response_to = None

        return self._response_to

    @property
    def is_response(self):
        return self.response_to is not None

    def responses(self):
        return [link.response for link in self.response_links.all()]

    def get_content(self):
        '''
        A generic and generative interface for getting all the content block for an article,
        including advanced content such as chapters.
        '''
        for block in self.body:
            yield block
        for chapter in self.chapters:
            for block in chapter.value['body']:
                yield block

    def related_articles(self, number):
        included = [self.id]
        article_list = []
        if self.primary_topic:
            articles = ArticlePage.objects.live().filter(
                primary_topic=self.primary_topic).exclude(id=self.id).distinct(
                ).order_by('-first_published_at')[:number]
            article_list.extend(articles.all())
            included.extend([article.id for article in articles.all()])

        current_total = len(article_list)

        if current_total < number:
            # still don't have enough, so pick using secondary topics
            topics = Topic.objects.filter(article_links__article=self)
            if topics:
                additional_articles = ArticlePage.objects.live().filter(
                    primary_topic__in=topics).exclude(
                        id__in=included).distinct().order_by(
                            '-first_published_at')[:number - current_total]
                article_list.extend(additional_articles.all())
                current_total = len(article_list)
                included.extend(
                    [article.id for article in additional_articles.all()])

        if current_total < number:
            authors = ContributorPage.objects.live().filter(
                article_links__article=self)
            if authors:
                additional_articles = ArticlePage.objects.live().filter(
                    author_links__author__in=authors).exclude(
                        id__in=included).distinct().order_by(
                            '-first_published_at')[:number - current_total]
                article_list.extend(additional_articles.all())
                current_total = len(article_list)
                included.extend(
                    [article.id for article in additional_articles.all()])

        if current_total < number:
            # still don't have enough, so just pick the most recent
            additional_articles = ArticlePage.objects.live().exclude(
                id__in=included).order_by('-first_published_at')[:number -
                                                                 current_total]
            article_list.extend(additional_articles.all())

        return article_list

    content_panels = Page.content_panels + [
        FieldPanel('excerpt'),
        InlinePanel('author_links', label="Authors"),
        PageChooserPanel('project'),
        ImageChooserPanel('main_image'),
        ImageChooserPanel('feature_image'),
        DocumentChooserPanel('video_document'),
        StreamFieldPanel('body'),
        SnippetChooserPanel('primary_topic'),
        InlinePanel('topic_links', label="Secondary Topics"),
        InlinePanel('response_links', label="Responses"),
    ]

    advanced_content_panels = [
        FieldPanel('json_file'),
        MultiFieldPanel([
            FieldPanel('table_of_contents_heading'),
            StreamFieldPanel('chapters'),
        ],
                        heading="Chapters Section"),
        MultiFieldPanel([
            FieldPanel('endnotes_heading'),
            FieldPanel('endnote_identifier_style'),
            InlinePanel('endnote_links', label="End Notes"),
        ],
                        heading="End Notes Section"),
        MultiFieldPanel([
            FieldPanel('citations_heading'),
            InlinePanel('citation_links', label="Citations"),
        ],
                        heading="Citations Section"),
    ]

    promote_panels = Page.promote_panels + [
        MultiFieldPanel([
            FieldPanel('sticky'),
            FieldPanel('sticky_for_type_section'),
            FieldPanel('slippery'),
            FieldPanel('slippery_for_type_section'),
            FieldPanel('editors_pick'),
            FieldPanel('feature_style'),
            FieldPanel('title_size'),
            FieldPanel('fullbleed_feature'),
            FieldPanel('image_overlay_opacity'),
        ],
                        heading="Featuring Settings"),
    ]

    style_panels = ThemeablePage.style_panels + [
        MultiFieldPanel([
            FieldPanel('include_main_image'),
            FieldPanel('include_main_image_overlay'),
            FieldPanel('full_bleed_image_size'),
            FieldPanel('include_caption_in_footer'),
        ],
                        heading="Main Image"),
        MultiFieldPanel([
            InlinePanel('background_image_links', label="Background Images"),
        ],
                        heading="Background Images"),
        MultiFieldPanel([
            FieldPanel('include_author_block'),
            FieldPanel('number_of_related_articles')
        ],
                        heading="Sections"),
        MultiFieldPanel([
            FieldPanel('interview'),
            FieldPanel('video'),
            FieldPanel('visualization'),
        ],
                        heading="Categorization")
    ]

    edit_handler = TabbedInterface([
        ObjectList(content_panels, heading='Content'),
        ObjectList(advanced_content_panels, heading='Advanced Content'),
        ObjectList(style_panels, heading='Page Style Options'),
        ObjectList(promote_panels, heading='Promote'),
        ObjectList(Page.settings_panels,
                   heading='Settings',
                   classname="settings"),
    ])
Example #24
0
class ArticleSeriesPage(
        BasicPageAbstract,
        ContentPage,
        FeatureablePageAbstract,
        FromTheArchivesPageAbstract,
        ShareablePageAbstract,
        ThemeablePageAbstract,
):
    credits = RichTextField(
        blank=True,
        features=[
            'bold',
            'italic',
            'link',
            'name',
        ],
    )
    credits_stream_field = StreamField(
        [('title',
          StructBlock([
              ('title', CharBlock()),
              ('people', StreamBlock([('name', CharBlock())])),
          ]))],
        blank=True,
    )
    credits_artwork = models.CharField(
        max_length=255,
        blank=True,
    )
    featured_items = StreamField(
        [
            ('featured_item',
             PageChooserBlock(
                 required=True,
                 page_type=[
                     'articles.ArticlePage', 'multimedia.MultimediaPage'
                 ],
             )),
        ],
        blank=True,
    )
    image_banner = models.ForeignKey(
        'images.CigionlineImage',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
        verbose_name='Banner Image',
    )
    image_banner_small = models.ForeignKey('images.CigionlineImage',
                                           null=True,
                                           blank=True,
                                           on_delete=models.SET_NULL,
                                           related_name='+',
                                           verbose_name='Banner Image Small')
    image_poster = models.ForeignKey(
        'images.CigionlineImage',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
        verbose_name='Poster image',
        help_text=
        'A poster image which will be used in the highlights section of the homepage.',
    )
    short_description = RichTextField(
        blank=True,
        null=False,
        features=['bold', 'italic', 'link'],
    )
    series_items_description = RichTextField(
        blank=True,
        null=True,
        features=['bold', 'italic', 'link'],
    )
    series_videos_description = RichTextField(
        blank=True,
        null=True,
        features=['bold', 'italic', 'link'],
        help_text=
        'To be displayed on video/multimedia pages of the series in place of Series Items Description'
    )
    series_items_disclaimer = RichTextField(
        blank=True,
        null=True,
        features=['bold', 'italic', 'link'],
    )
    video_banner = models.ForeignKey(
        'wagtailmedia.Media',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
        verbose_name='Banner Video',
    )

    @property
    def image_poster_caption(self):
        return self.image_poster.caption

    @property
    def image_poster_url(self):
        return self.image_poster.get_rendition('fill-672x895').url

    @property
    def article_series_items(self):
        return self.series_items.prefetch_related(
            'content_page',
            'content_page__authors__author',
        ).all()

    # Reference field for the Drupal-Wagtail migrator. Can be removed after.
    drupal_node_id = models.IntegerField(blank=True, null=True)

    def get_template(self, request, *args, **kwargs):
        standard_template = super(ArticleSeriesPage,
                                  self).get_template(request, *args, **kwargs)
        if self.theme:
            return f'themes/{self.get_theme_dir()}/article_series_page.html'
        return standard_template

    content_panels = [
        BasicPageAbstract.title_panel,
        MultiFieldPanel(
            [
                FieldPanel('short_description'),
                StreamFieldPanel('body'),
            ],
            heading='Body',
            classname='collapsible collapsed',
        ),
        MultiFieldPanel(
            [
                FieldPanel('publishing_date'),
            ],
            heading='General Information',
            classname='collapsible collapsed',
        ),
        MultiFieldPanel(
            [
                FieldPanel('series_items_description'),
                FieldPanel('series_videos_description'),
                FieldPanel('series_items_disclaimer'),
                InlinePanel('series_items'),
            ],
            heading='Series Items',
            classname='collapsible collapsed',
        ),
        MultiFieldPanel(
            [
                FieldPanel('credits'),
                FieldPanel('credits_artwork'),
                StreamFieldPanel('credits_stream_field'),
            ],
            heading='Credits',
            classname='collapsible collapsed',
        ),
        MultiFieldPanel(
            [
                ImageChooserPanel('image_hero'),
                ImageChooserPanel('image_banner'),
                ImageChooserPanel('image_banner_small'),
                ImageChooserPanel('image_poster'),
            ],
            heading='Image',
            classname='collapsible collapsed',
        ),
        MultiFieldPanel(
            [
                MediaChooserPanel('video_banner'),
            ],
            heading='Media',
            classname='collapsible collapsed',
        ),
        MultiFieldPanel(
            [
                StreamFieldPanel('featured_items'),
            ],
            heading='Featured Series Items',
            classname='collapsible collapsed',
        ),
        MultiFieldPanel(
            [
                FieldPanel('topics'),
            ],
            heading='Related',
            classname='collapsible collapsed',
        ),
    ]

    promote_panels = Page.promote_panels + [
        FeatureablePageAbstract.feature_panel,
        ShareablePageAbstract.social_panel,
        SearchablePageAbstract.search_panel,
    ]

    settings_panels = Page.settings_panels + [
        ThemeablePageAbstract.theme_panel,
    ]

    search_fields = Page.search_fields \
        + BasicPageAbstract.search_fields \
        + ContentPage.search_fields

    parent_page_types = ['home.HomePage']
    subpage_types = []
    templates = 'articles/article_series_page.html'

    @property
    def series_contributors_by_article(self):
        series_contributors = []
        item_people = set()

        for series_item in self.article_series_items:
            people = series_item.content_page.authors.all()
            people_string = ''

            for person in people:
                person_string = person.author.title
                people_string += person_string

                # Add each person as well so if there's an article with just
                # a single author who's already been in another article in
                # collaboration, then we won't add their name to the list
                # again.
                if len(people) > 1:
                    item_people.add(person_string)

            if people_string not in item_people:
                series_contributors.append({
                    'item': series_item.content_page,
                    'contributors': people
                })
                item_people.add(people_string)

        return series_contributors

    @property
    def series_contributors(self):
        series_contributors = []
        item_people = set()

        for series_item in self.article_series_items:
            people = series_item.content_page.authors.all()
            for person in people:
                if person.author.title not in item_people:
                    series_contributors.append({
                        'id': person.author.id,
                        'title': person.author.title,
                        'url': person.author.url,
                    })
                    item_people.add(person.author.title)
        return series_contributors

    @property
    def series_contributors_by_person(self):
        # Series contributors ordered by last name
        series_contributors = []
        item_people = set()

        for series_item in self.article_series_items:
            people = series_item.content_page.authors.all()

            # Skip items that have more than 2 authors/speakers. For
            # example, in the After COVID series, there is an introductory
            # video with many authors.
            if len(people) > 2:
                continue
            else:
                for person in people:
                    if person.author.title not in item_people:
                        series_contributors.append({
                            'item':
                            series_item.content_page,
                            'contributors': [person.author],
                            'last_name':
                            person.author.last_name,
                        })
                        item_people.add(person.author.title)

        series_contributors.sort(key=lambda x: x['last_name'])
        return series_contributors

    @property
    def series_authors(self):
        series_authors = []
        series_people = set()
        for series_item in self.article_series_items:
            people = series_item.content_page.authors.all()
            for person in people:
                if person.author.title not in series_people:
                    series_authors.append(person.author)
                    series_people.add(person.author.title)
        return series_authors

    class Meta:
        verbose_name = 'Opinion Series'
        verbose_name_plural = 'Opinion Series'
Example #25
0
class AtlasCaseStudyIndexPage(Page):
    # title already in the Page class
    # slug already in the Page class
    subpage_types = ["atlascasestudies.AtlasCaseStudy"]
    body = RichTextField(blank=True)

    # so we can filter available categories based on the sub site as well as the
    # sub_site_categories = models.ForeignKey(
    #     CategorySubSite,
    #     on_delete=models.PROTECT,
    #     related_name='category_blog_site',
    #     null=True,
    # )

    content_panels = Page.content_panels + [
        # FieldPanel('sub_site_categories'),
        FieldPanel("body"),
    ]

    def get_latest_atlas_case_studies(self, num):
        return AtlasCaseStudy.objects.all().order_by(
            "-first_published_at")[:num]

    def get_context(self, request, *args, **kwargs):
        atlas_case_study_ordering = "-first_published_at"
        context = super().get_context(request, *args, **kwargs)

        if request.GET.get("setting"):
            context["chosen_setting_id"] = int(request.GET.get("setting"))
            atlas_case_studies = (AtlasCaseStudy.objects.live(
            ).order_by(atlas_case_study_ordering).filter(
                atlas_case_study_setting_relationship__setting=request.GET.get(
                    "setting")))
        elif request.GET.get("region"):
            context["chosen_region_id"] = int(request.GET.get("region"))
            atlas_case_studies = (AtlasCaseStudy.objects.live(
            ).order_by(atlas_case_study_ordering).filter(
                atlas_case_study_region_relationship__region=request.GET.get(
                    "region")))
        elif request.GET.get("category"):
            context["chosen_category_id"] = int(request.GET.get("category"))
            atlas_case_studies = (AtlasCaseStudy.objects.live().order_by(
                atlas_case_study_ordering).filter(
                    atlas_case_study_category_relationship__category=request.
                    GET.get("category")))
        else:
            atlas_case_studies = AtlasCaseStudy.objects.live().order_by(
                atlas_case_study_ordering)

        paginator = Paginator(atlas_case_studies, 16)

        try:
            items = paginator.page(request.GET.get("page"))
        except PageNotAnInteger:
            items = paginator.page(1)
        except EmptyPage:
            items = paginator.page(paginator.num_pages)

        context["atlas_case_studies"] = items

        category_sub_site = CategorySubSite.objects.get(source="categories")
        context["categories"] = Category.objects.filter(
            sub_site=category_sub_site)

        context["setting"] = Setting.objects.all()

        context["regions"] = Region.objects.all()

        # an experiment to get only categories that are used by blogs
        # blog_pages_ids = [x.id for x in Blog.objects.all()]
        # print(blog_pages_ids)
        # print(Category.objects.filter(blog_categories__in=blog_pages_ids))
        # context['categories'] = Category.objects.filter(blog_categories__in=blog_pages_ids)
        return context
class Article(BasePage):
    # IMPORTANT: EACH ARTICLE is NOW LABELLED "POST" IN THE FRONT END

    resource_type = "article"  # If you change this, CSS will need updating, too
    parent_page_types = ["Articles"]
    subpage_types = []
    template = "article.html"

    class Meta:
        verbose_name = "post"  # NB
        verbose_name_plural = "posts"  # NB

    # Content fields
    description = RichTextField(
        blank=True,
        default="",
        features=RICH_TEXT_FEATURES_SIMPLE,
        help_text="Optional short text description, max. 400 characters",
        max_length=400,
    )
    body = CustomStreamField(help_text=(
        "The main post content. Supports rich text, images, embed via URL, "
        "embed via HTML, and inline code snippets"))
    related_links = StreamField(
        StreamBlock([("link", ExternalLinkBlock())], required=False),
        blank=True,
        null=True,
        help_text="Optional links further reading",
        verbose_name="Related links",
    )

    # Card fields
    card_title = CharField("Title", max_length=140, blank=True, default="")
    card_description = TextField("Description",
                                 max_length=400,
                                 blank=True,
                                 default="")
    card_image = ForeignKey(
        "mozimages.MozImage",
        null=True,
        blank=True,
        on_delete=SET_NULL,
        related_name="+",
        verbose_name="Image",
        help_text="An image in 16:9 aspect ratio",
    )
    card_image_3_2 = ForeignKey(
        "mozimages.MozImage",
        null=True,
        blank=True,
        on_delete=SET_NULL,
        related_name="+",
        verbose_name="Image",
        help_text="An image in 3:2 aspect ratio",
    )

    # Meta fields
    date = DateField(
        "Post date",
        default=datetime.date.today,
        help_text="The date the post was published",
    )
    authors = StreamField(
        StreamBlock(
            [
                ("author", PageChooserBlock(target_model="people.Person")),
                ("external_author", ExternalAuthorBlock()),
            ],
            required=False,
        ),
        blank=True,
        null=True,
        help_text=(
            "Optional list of the post's authors. Use ‘External author’ to add "
            "guest authors without creating a profile on the system"),
    )
    keywords = ClusterTaggableManager(through=ArticleTag, blank=True)

    # Content panels
    content_panels = BasePage.content_panels + [
        FieldPanel("description"),
        StreamFieldPanel("body"),
        StreamFieldPanel("related_links"),
    ]

    # Card panels
    card_panels = [
        FieldPanel("card_title"),
        FieldPanel("card_description"),
        MultiFieldPanel(
            [ImageChooserPanel("card_image")],
            heading="16:9 Image",
            help_text=(
                "Image used for representing this page as a Card. "
                "Should be 16:9 aspect ratio. "
                "If not specified a fallback will be used. "
                "This image is also shown when sharing this page via social "
                "media unless a social image is specified."),
        ),
        MultiFieldPanel(
            [ImageChooserPanel("card_image_3_2")],
            heading="3:2 Image",
            help_text=("Image used for representing this page as a Card. "
                       "Should be 3:2 aspect ratio. "
                       "If not specified a fallback will be used. "),
        ),
    ]

    # Meta panels
    meta_panels = [
        FieldPanel("date"),
        StreamFieldPanel("authors"),
        MultiFieldPanel(
            [InlinePanel("topics")],
            heading="Topics",
            help_text=
            ("The topic pages this post will appear on. The first topic in the "
             "list will be treated as the primary topic and will be shown in the "
             "page’s related content."),
        ),
        MultiFieldPanel(
            [
                FieldPanel("seo_title"),
                FieldPanel("search_description"),
                ImageChooserPanel("social_image"),
                FieldPanel("keywords"),
            ],
            heading="SEO",
            help_text=(
                "Optional fields to override the default title and description "
                "for SEO purposes"),
        ),
    ]

    # Settings panels
    settings_panels = BasePage.settings_panels + [FieldPanel("slug")]

    # Tabs
    edit_handler = TabbedInterface([
        ObjectList(content_panels, heading="Content"),
        ObjectList(card_panels, heading="Card"),
        ObjectList(meta_panels, heading="Meta"),
        ObjectList(settings_panels, heading="Settings", classname="settings"),
    ])

    def get_absolute_url(self):
        # For the RSS feed
        return self.full_url

    @property
    def verbose_standfirst(self):
        """Return a marked-safe HTML snippet that can be used as a verbose standfirst"""

        template = "partials/verbose_standfirst.html"
        rendered = render_to_string(template, context={"page": self})
        return rendered

    @property
    def primary_topic(self):
        """Return the first (primary) topic specified for the Article."""
        article_topic = self.topics.first()
        return article_topic.topic if article_topic else None

    @property
    def read_time(self):
        return str(readtime.of_html(str(self.body)))

    @property
    def related_resources(self):
        """Returns resources that are related to the current resource, i.e.
        live, public Articles and Videos which have the same Topics."""
        topic_pks = [topic.topic.pk for topic in self.topics.all()]
        return get_combined_articles_and_videos(
            self, topics__topic__pk__in=topic_pks)

    @property
    def month_group(self):
        return self.date.replace(day=1)

    def has_author(self, person):
        for author in self.authors:  # pylint: disable=not-an-iterable
            if author.block_type == "author" and str(author.value) == str(
                    person.title):
                return True
        return False
Example #27
0
class HomePage(Page):

    carouselimage1 = models.ForeignKey(
        'wagtailimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+'
    ) 

    carousellogo = models.ForeignKey(
        'wagtailimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+'
    ) 
    
    carouseltext1 = RichTextField(blank=True, null=True)

    carouselimage2 = models.ForeignKey(
        'wagtailimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+'
    ) 

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

    cardimage1 = models.ForeignKey(
        'wagtailimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+'
    ) 

    cardtext1 = RichTextField()

    cardimage2 = models.ForeignKey(
        'wagtailimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+'
    ) 

    cardtext2 = RichTextField()

    cardimage3 = models.ForeignKey(
        'wagtailimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+'
    ) 

    cardtext3 = RichTextField()

    invitation = RichTextField()



    # Editor panels configuration

    content_panels = Page.content_panels + [
        ImageChooserPanel('carousellogo'),
        FieldPanel('carouseltext1', classname="full"),
        ImageChooserPanel('carouselimage1'),
        FieldPanel('carouseltext2', classname="full"),
        ImageChooserPanel('carouselimage2'),
        FieldPanel('cardtext1', classname="full"),
        ImageChooserPanel('cardimage1'),
        FieldPanel('cardtext2', classname="full"),
        ImageChooserPanel('cardimage2'),
        FieldPanel('cardtext3', classname="full"),
        ImageChooserPanel('cardimage3'),
        FieldPanel('invitation', classname="full"),
    ]

    def get_context(self, request):
        context = super().get_context(request)
        next_event = EventCalPage.objects.filter(start_dt__gte=timezone.now(), categories__slug='public').order_by('start_dt').first()
        context['nextevent'] = next_event
        return context
class Articles(BasePage):

    RESOURCES_PER_PAGE = 6

    # IMPORTANT: ARTICLES ARE NOW LABELLED "POSTS" IN THE FRONT END
    parent_page_types = ["home.HomePage"]
    subpage_types = ["Article"]
    template = "articles.html"

    class Meta:
        verbose_name = "posts"
        verbose_name_plural = "posts"

    # Content fields
    description = RichTextField(
        blank=True,
        default="",
        features=RICH_TEXT_FEATURES_SIMPLE,
        help_text="Optional short text description, max. 400 characters",
        max_length=400,
    )

    # Meta fields
    keywords = ClusterTaggableManager(through=ArticlesTag, blank=True)

    # Content panels
    content_panels = BasePage.content_panels + [FieldPanel("description")]

    # Meta panels
    meta_panels = [
        MultiFieldPanel(
            [
                FieldPanel("seo_title"),
                FieldPanel("search_description"),
                ImageChooserPanel("social_image"),
                FieldPanel("keywords"),
            ],
            heading="SEO",
            help_text=("Optional fields to override the default title "
                       "and description for SEO purposes"),
        )
    ]

    # Settings panels
    settings_panels = BasePage.settings_panels + [
        FieldPanel("slug"),
        FieldPanel("show_in_menus"),
    ]

    edit_handler = TabbedInterface([
        ObjectList(content_panels, heading="Content"),
        ObjectList(meta_panels, heading="Meta"),
        ObjectList(settings_panels, heading="Settings", classname="settings"),
    ])

    @classmethod
    def can_create_at(cls, parent):
        # Allow only one instance of this page type
        return super().can_create_at(parent) and not cls.objects.exists()

    def get_context(self, request):
        context = super().get_context(request)
        context["filters"] = self.get_filters()
        context["resources"] = self.get_resources(request)
        return context

    def get_resources(self, request):
        # This Page class will show both Articles/Posts and Videos in its listing

        # We can't use __in in this deeply related query, so we have to make
        # a custom Q object instead and pass is in as a filter, then deal with
        # it later
        topics = request.GET.getlist(TOPIC_QUERYSTRING_KEY)
        topics_q = Q(topics__topic__slug__in=topics) if topics else Q()
        resources = get_combined_articles_and_videos(self, q_object=topics_q)
        resources = paginate_resources(
            resources,
            page_ref=request.GET.get(PAGINATION_QUERYSTRING_KEY),
            per_page=self.RESOURCES_PER_PAGE,
        )

        return resources

    def get_filters(self):
        from ..topics.models import Topic

        return {"topics": Topic.published_objects.order_by("title")}
Example #29
0
class Course(index.Indexed, ClusterableModel):
    """A Django model to create courses."""
    type = models.CharField(_("Type"), max_length=15, choices=COURSE)
    name = models.CharField(_("Name"), max_length=254)
    introduction = models.CharField(_("Introduction"), max_length=254)
    start_date = models.DateField(_("Start date"), blank=True, null=True)
    end_date = models.DateField(_("End date"), blank=True, null=True)
    start_time = models.TimeField(_("Start time"), blank=True, null=True)
    end_time = models.TimeField(_("End time"), blank=True, null=True)
    price = models.DecimalField(_("Price"),
                                max_digits=10,
                                decimal_places=2,
                                blank=True,
                                null=True)
    vacancies = models.IntegerField(_("Vacancies"), blank=True, null=True)
    registered = models.IntegerField(_("Registered"), blank=True, default=0)
    pre_booking = models.IntegerField(_("Pre-booking"), blank=True, default=0)
    description = RichTextField(_("Description"),
                                features=RICHTEXT_FEATURES,
                                blank=True)
    image = models.ForeignKey('wagtailimages.Image',
                              null=True,
                              blank=True,
                              on_delete=models.SET_NULL,
                              related_name='+')
    tags = ClusterTaggableManager(through=CourseTag, blank=True)

    panels = [
        FieldPanel('type'),
        FieldPanel('name'),
        FieldPanel('introduction'),
        MultiFieldPanel([
            FieldRowPanel([
                FieldPanel('start_date', classname="col6"),
                FieldPanel('end_date', classname="col6"),
            ])
        ],
                        heading=_("Dates")),
        MultiFieldPanel([
            FieldRowPanel([
                FieldPanel('start_time', classname="col6"),
                FieldPanel('end_time', classname="col6"),
            ])
        ],
                        heading=_("Schedule")),
        FieldPanel('price'),
        FieldPanel('vacancies'),
        FieldPanel('registered'),
        FieldPanel('description'),
        ImageChooserPanel('image'),
        FieldPanel('tags'),
    ]

    search_fields = [
        index.SearchField('name'),
    ]

    def __str__(self):
        return '{}'.format(self.name)

    @property
    def get_tags(self):
        """
        Return all the tags that are related to the course into a list we can access on the template.
        We're additionally adding a URL to access Course objects with that tag
        """
        tags = self.tags.all()
        for tag in tags:
            tag.url = '/' + '/'.join(
                s.strip('/')
                for s in [self.get_parent().url, 'tags', tag.slug])
        return tags

    def show_course(self):
        return self.start_date >= datetime.datetime.now().date(
        ) if self.start_date else False

    class Meta:
        verbose_name = _("Course")
        verbose_name_plural = _("Courses")
Example #30
0
class BankPage(Page):
    bank_name = models.CharField(max_length=100, default="string")
    deposit_name = models.CharField(max_length=100, default="string")
    income = models.BooleanField(default=False)
    dolar = models.FloatField(default=0.0)
    euro = models.FloatField(default=0.0)
    hryvna = models.FloatField(default=0.0)
    capitalization = models.PositiveIntegerField(default=0, validators=[MinValueValidator(0), MaxValueValidator(3)])
    bank_image = models.ImageField(default=None)
    body = RichTextField(blank=True)
    full_body = RichTextField(blank=True)
    page_description = models.CharField(max_length=200, default="string")
    deposit_path = models.CharField(max_length=100, default="string")
    external_link = models.CharField(max_length=200, default="link")

    search_fields = Page.search_fields + [
        index.SearchField('bank_name'),
        index.SearchField('deposit_name'),
        index.SearchField('income'),
        index.SearchField('dolar'),
        index.SearchField('euro'),
        index.SearchField('hryvna'),
        index.SearchField('capitalization'),
        index.SearchField('bank_image'),
        index.SearchField('body'),
        index.SearchField('full_body'),
        index.SearchField('page_description'),
        index.SearchField('deposit_path'),
        index.SearchField('external_link'),
    ]

    content_panels = Page.content_panels + [
        FieldPanel('bank_name'),
        FieldPanel('deposit_name'),
        FieldPanel('income'),
        FieldPanel('dolar'),
        FieldPanel('euro'),
        FieldPanel('hryvna'),
        FieldPanel('capitalization'),
        FieldPanel('bank_image'),
        FieldPanel('body', classname="full"),
        FieldPanel('full_body', classname="full"),
        FieldPanel('page_description'),
        FieldPanel('deposit_path'),
        FieldPanel('external_link'),
    ]

    api_fields = [
        APIField('bank_name'),
        APIField('deposit_name'),
        APIField('income'),
        APIField('dolar'),
        APIField('euro'),
        APIField('hryvna'),
        APIField('capitalization'),
        APIField('bank_image'),
        APIField('body'),
        APIField('full_body'),
        APIField('page_description'),
        APIField('deposit_path'),
        APIField('external_link'),
    ]