예제 #1
0
class ArticlePage(Page):
    image = models.ForeignKey('wagtailimages.Image',
                              null=True,
                              blank=True,
                              on_delete=models.SET_NULL,
                              related_name='+')

    body = StreamField(
        [('paragraph', blocks.RichTextBlock(label='Editor de texto')),
         ('html', blocks.RawHTMLBlock(label='HTML')),
         ('md', MarkdownBlock(label='Markdown')),
         ('notebook', NotebookBlock())],
        null=True,
        blank=True)

    content_panels = Page.content_panels + [
        ImageChooserPanel('image'),
        StreamFieldPanel('body'),
    ]

    parent_page_types = ['article.IndexPage']
    subpage_types = []

    class Meta:
        verbose_name = 'Articulo'
        verbose_name_plural = 'Articulos'

    def get_context(self, request):
        context = super().get_context(request)
        site = Site.find_for_request(request)
        context['related_articles'] = ArticlePage.objects.in_site(
            site).live().exact_type(ArticlePage).order_by('?')[:3]
        return context
예제 #2
0
class StandardPage(Page):
    TEMPLATE_CHOICES = [
        ('pages/standard_page.html', 'Default Template'),
        ('pages/standard_page_full.html', 'Standard Page Full'),
    ]
    subtitle = models.CharField(max_length=255, blank=True)
    intro = RichTextField(blank=True)
    body = StreamField([
        ('paragraph', blocks.RichTextBlock()),
        ('image', ImageChooserBlock()),
        ('markdown', MarkdownBlock(icon="code")),
        ('html', blocks.RawHTMLBlock()),
    ])
    template_string = models.CharField(max_length=255,
                                       choices=TEMPLATE_CHOICES,
                                       default='pages/standard_page.html')
    feed_image = models.ForeignKey(Image,
                                   null=True,
                                   blank=True,
                                   on_delete=models.SET_NULL,
                                   related_name='+')

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

    @property
    def template(self):
        return self.template_string
예제 #3
0
class PulloutBlock(StructBlock):
    text = MarkdownBlock()
    stat_header = CharBlock()
    stat_text = CharBlock()

    class Meta:
        template = "industry/blocks/pullout.html"
예제 #4
0
class WorkExperienceBlock(blocks.StructBlock):
    class Meta:
        template = "wagtail_resume/blocks/work_experience_block.html"
        icon = "doc-full-inverse"

    heading = blocks.CharBlock(default="Work experience")
    fa_icon = blocks.CharBlock(default="fas fa-tools")
    experiences = blocks.ListBlock(
        blocks.StructBlock(
            [
                ("role", blocks.CharBlock()),
                ("company", blocks.CharBlock()),
                ("url", blocks.URLBlock()),
                ("from_date", blocks.DateBlock()),
                ("to_date", blocks.DateBlock(required=False)),
                (
                    "currently_working_here",
                    blocks.BooleanBlock(
                        help_text=
                        "Check this box if you are currently working here and it will indicate so on the resume.",
                        required=False,
                    ),
                ),
                ("text", MarkdownBlock()),
            ],
            icon="folder-open-inverse",
        ))
예제 #5
0
파일: markdown.py 프로젝트: uktrade/invest
class MarkdownAccordionItemBlock(StructBlock):
    class Meta:
        template = 'blocks/accordion_item_markdown.html'

    # accordion section
    title = CharBlock(max_length=255)
    content = MarkdownBlock()
예제 #6
0
파일: location.py 프로젝트: uktrade/invest
class LocationAccordionItemBlock(StructBlock):
    class Meta:
        template = 'blocks/accordion_item_location.html'

    # accordion section
    title = CharBlock(max_length=255)
    info = MarkdownBlock()
    map = ImageChooserBlock()
예제 #7
0
class BlogPage(RoutablePageMixin, Page):
    intro = RichTextField()
    body = StreamField([
        ('paragraph', blocks.RichTextBlock()),
        ('markdown', MarkdownBlock(icon="code")),
        ('image', ImageChooserBlock()),
        ('html', blocks.RawHTMLBlock()),
    ])
    tags = ClusterTaggableManager(through=BlogPageTag, blank=True)
    date = models.DateField("Post date")
    feed_image = models.ForeignKey('wagtailimages.Image',
                                   null=True,
                                   blank=True,
                                   on_delete=models.SET_NULL,
                                   related_name='+')

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

    parent_page_types = ['blog.BlogIndexPage']

    @property
    def blog_index(self):
        # Find closest ancestor which is a blog index
        return self.get_ancestors().type(BlogIndexPage).last()

    @route(r'^$')
    def normal_page(self, request):
        return Page.serve(self, request)

    @route(r'^amp/$')
    def amp(self, request):
        context = self.get_context(request)
        body_html = self.body.__html__()
        soup = BeautifulSoup(body_html, 'html.parser')
        # Remove style attribute to remove large bottom padding
        for div in soup.find_all("div", {'class': 'responsive-object'}):
            del div['style']

        # Change img tags to amp-img
        for img_tag in soup.findAll('img'):
            img_tag.name = 'amp-img'
            img_tag.append(BeautifulSoup('</amp-img>', 'html.parser'))
            img_tag['layout'] = 'responsive'

        # Change iframe tags to amp-iframe
        for iframe in soup.findAll('iframe'):
            iframe.name = 'amp-iframe'
            iframe['sandbox'] = 'allow-scripts allow-same-origin'
            iframe['layout'] = 'responsive'

        context['body_html'] = mark_safe(soup.prettify(formatter="html"))
        context['is_amp'] = True
        context['base_template'] = 'amp_base.html'
        response = TemplateResponse(request, 'blog/blog_page_amp.html',
                                    context)
        return response
예제 #8
0
class BodyBlock(StreamBlock):
    h1 = CharBlock()
    h2 = CharBlock()
    paragraph = RichTextBlock()
    markdown = MarkdownBlock(icon="code")

    image_text = ImageText()
    image_carousel = ListBlock(ImageChooserBlock())
    thumbnail_gallery = ListBlock(ImageChooserBlock())
예제 #9
0
class BlogBodyBlock(StructBlock):
    """
    Custom `StructBlock` to organize blog articles' bodies into sections, each with a title, used to create a TOC
    """
    title = CharBlock(required=False)
    content = MarkdownBlock(required=False)

    class Meta:
        icon = 'fa-paragraph'
        templates = 'blocks/blog_body_block.html'
예제 #10
0
    def test_that_article_intro_should_strip_markdown_syntax(self):
        paragraph_string = "###Lorem ipsum `dolor` sit amet, **consectetur** adipiscing [elit](http://www.google.com). \r\n* Nullam *laoreet* venenatis enim, non luctus nisi finibus ut.\r\n> Mauris porta eleifend massa, nec maximus lacus luctus a. Aenean libero felis, placerat non malesuada a, maximus id erat. Nulla ut"
        expected_string = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam laoreet venenatis enim, non luctus nisi finibus ut. Mauris porta eleifend massa, nec maximus lacus luctus a. Aenean libero felis, placerat non malesuada a, maximus id erat. Nulla ut"

        body_block = StreamBlock([(ArticleBodyBlockNames.MARKDOWN.value,
                                   MarkdownBlock())])
        body = StreamValue(
            body_block,
            [(ArticleBodyBlockNames.MARKDOWN.value, paragraph_string)])
        blog_article = self._create_blog_article_page(body=body)

        self.assertEqual(blog_article.intro, expected_string + INTRO_ELLIPSIS)
예제 #11
0
    def test_that_article_intro_should_support_markdown_blocks(self):
        paragraph_string = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam laoreet venenatis enim, non luctus nisi finibus ut. Mauris porta eleifend massa, nec maximus lacus luctus a. Aenean libero felis, placerat non malesuada a, maximus id erat. Nulla ut purus elementum, auctor orci eget, facilisis est."
        expected_string = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam laoreet venenatis enim, non luctus nisi finibus ut. Mauris porta eleifend massa, nec maximus lacus luctus a. Aenean libero felis, placerat non malesuada a, maximus id erat. Nulla ut"

        body_block = StreamBlock([(ArticleBodyBlockNames.MARKDOWN.value,
                                   MarkdownBlock())])
        body = StreamValue(
            body_block,
            [(ArticleBodyBlockNames.MARKDOWN.value, paragraph_string)])
        blog_article = self._create_blog_article_page(body=body)

        self.assertEqual(blog_article.intro, expected_string + INTRO_ELLIPSIS)
예제 #12
0
class StoryBlock(StreamBlock):
    h2 = CharBlock(
        form_classname="title",
        icon="title",
        template="patterns/molecules/streamfield/blocks/heading2_block.html",
    )
    h3 = CharBlock(
        form_classname="title",
        icon="title",
        template="patterns/molecules/streamfield/blocks/heading3_block.html",
    )
    h4 = CharBlock(
        form_classname="title",
        icon="title",
        template="patterns/molecules/streamfield/blocks/heading4_block.html",
    )
    intro = RichTextBlock(
        icon="pilcrow",
        template="patterns/molecules/streamfield/blocks/intro_block.html",
    )
    paragraph = RichTextBlock(
        icon="pilcrow",
        template="patterns/molecules/streamfield/blocks/paragraph_block.html",
    )
    aligned_image = ImageBlock(
        label="Aligned image",
        template=
        "patterns/molecules/streamfield/blocks/aligned_image_block.html",
    )
    wide_image = WideImage(
        label="Wide image",
        template="patterns/molecules/streamfield/blocks/wide_image_block.html",
    )
    bustout = BustoutBlock(
        template="patterns/molecules/streamfield/blocks/bustout_block.html")
    pullquote = PullQuoteBlock(
        template="patterns/molecules/streamfield/blocks/pullquote_block.html")
    raw_html = RawHTMLBlock(
        label="Raw HTML",
        icon="code",
        template="patterns/molecules/streamfield/blocks/raw_html_block.html",
    )
    embed = EmbedBlock(
        icon="code",
        template="patterns/molecules/streamfield/blocks/embed_block.html",
    )
    markdown = MarkdownBlock(
        icon="code",
        template="patterns/molecules/streamfield/blocks/markdown_block.html",
    )

    class Meta:
        template = "patterns/molecules/streamfield/stream_block.html"
예제 #13
0
class StoryBlock(StreamBlock):
    h2 = CharBlock(icon="title", classname="title")
    h3 = CharBlock(icon="title", classname="title")
    h4 = CharBlock(icon="title", classname="title")
    intro = RichTextBlock(icon="pilcrow")
    paragraph = RichTextBlock(icon="pilcrow")
    aligned_image = ImageBlock(label="Aligned image")
    wide_image = WideImage(label="Wide image")
    bustout = BustoutBlock()
    pullquote = PullQuoteBlock()
    raw_html = RawHTMLBlock(label='Raw HTML', icon="code")
    embed = EmbedBlock(icon="code")
    markdown = MarkdownBlock(icon="code")
예제 #14
0
class PostPage(Page):
    excerpt = MarkdownField()
    featured = models.BooleanField(default=False)
    body = StreamField([
        ('image', ImageChooserBlock()),
        ('html', blocks.RawHTMLBlock()),
        ('embed', EmbedBlock()),
        ('paragraph', MarkdownBlock(icon="code")),
    ])
    date = models.DateTimeField(verbose_name="Post date",
                                default=datetime.datetime.today)

    header_image = models.ForeignKey(
        'wagtailimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
    )
    header_image_link = models.CharField(max_length=255, blank=True)

    categories = ParentalManyToManyField('blog.BlogCategory', blank=True)
    tags = ClusterTaggableManager(through='blog.BlogPageTag', blank=True)

    content_panels = Page.content_panels + [
        StreamFieldPanel("body"),
        ImageChooserPanel('header_image'),
        FieldPanel('header_image_link'),
        FieldPanel('excerpt'),
        FieldPanel('categories', widget=forms.CheckboxSelectMultiple),
        #FieldPanel('tags'),
        FieldPanel('featured'),
    ]

    settings_panels = Page.settings_panels + [
        FieldPanel('date'),
    ]

    @property
    def blog_page(self):
        return self.get_parent().specific

    def get_context(self, request, *args, **kwargs):
        context = super(PostPage, self).get_context(request, *args, **kwargs)
        context['blog_page'] = self.blog_page
        context['post'] = self
        return context

    def get_absolute_url(self):
        return "/news/%s/" % self.slug
예제 #15
0
파일: models.py 프로젝트: uktrade/invest
class SectorPage(Page):
    # Related sector are implemented as subpages
    subpage_types = ['sector.sectorPage']

    featured = models.BooleanField(default=False)
    description = models.TextField()  # appears in card on external pages

    # page fields
    heading = models.CharField(max_length=255)

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

    pullout = StreamField([('content',
                            StructBlock([('text', MarkdownBlock()),
                                         ('stat', CharBlock()),
                                         ('stat_text', CharBlock())],
                                        max_num=1,
                                        min_num=0))],
                          blank=True)

    # accordion
    subsections = StreamField([
        ('markdown', MarkdownAccordionItemBlock()),
        ('location', LocationAccordionItemBlock()),
    ])

    content_panels = Page.content_panels + [
        FieldPanel('description'),
        FieldPanel('featured'),
        ImageChooserPanel('hero_image'),
        FieldPanel('heading'),
        StreamFieldPanel('pullout'),
        StreamFieldPanel('subsections')
    ]

    def get_context(self, request):
        context = super().get_context(request)
        context['sector_cards'] = self.get_children().type(SectorPage) \
            .live() \
            .order_by('sectorpage__heading')
        # pages will return as Page type, use .specific to get sectorPage
        return context
예제 #16
0
class BaseStreamBlock(StreamBlock):
    """
    Define the custom blocks that `StreamField` will utilize
    """
    markdown_block = MarkdownBlock(icon="code")
    heading_block = HeadingBlock()
    paragraph_block = RichTextBlock(
        icon="fa-paragraph",
        template="blocks/paragraph_block.html",
    )
    image_block = ImageBlock()
    block_quote = BlockQuote()
    embed_block = EmbedBlock(
        help_text=
        'Insert an embed URL e.g https://www.youtube.com/embed/SGJFWirQ3ks',
        icon="fa-s15",
        template="blocks/embed_block.html")
예제 #17
0
class StandardPage(Page):
    TEMPLATE_CHOICES = [
        ('pages/standard_page_full.html', 'Optional custom sidebar'),
        ('pages/standard_page.html', 'Newsfeed sidebar'),
    ]
    subtitle = models.CharField(
        max_length=255,
        blank=True,
        help_text="This will override the title of the page.")
    intro = RichTextField(blank=True)
    midpage_subtitle = models.CharField(
        max_length=255,
        blank=True,
        help_text="This will override the title of the page.")
    body = StreamField([
        ('paragraph', blocks.RichTextBlock()),
        ('image', ImageChooserBlock()),
        ('markdown', MarkdownBlock(icon="code")),
        ('html', blocks.RawHTMLBlock()),
    ])
    template_string = models.CharField(max_length=255,
                                       choices=TEMPLATE_CHOICES,
                                       default=TEMPLATE_CHOICES[0][0],
                                       verbose_name='Page Layout')
    feed_image = models.ForeignKey(Image,
                                   null=True,
                                   blank=True,
                                   on_delete=models.SET_NULL,
                                   related_name='+')
    sidebar_text = RichTextField(
        blank=True,
        help_text=
        "only include text/images in here if you want the side bar, otherwise it will render full page"
    )

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

    @property
    def template(self):
        return self.template_string
예제 #18
0
class IndustryPage(Page):
    """
    header:
       - lockup text
       - hero image


    intro
       - pullout text
       - pullout star

    content
       made up of sections

    next steps
        this is basically a snippet
        but has formatting
    """
    # header
    # heading lockup
    # hero image

    body = StreamField([
        ('heading', HeaderBlock()),
        ('pullout', PulloutBlock()),
        ('content', MarkdownBlock()),
        ('common_content', IndustrySnippetBlock(target_model=StaticContent)),
    ])

    report_problem = models.ForeignKey(StaticContent,
                                       on_delete=CASCADE,
                                       null=True,
                                       related_name='report_problem')
    sharing_text = models.ForeignKey(StaticContent,
                                     on_delete=CASCADE,
                                     null=True,
                                     related_name='sharing_text')

    content_panels = Page.content_panels + [
        StreamFieldPanel('body'),
        SnippetChooserPanel('report_problem'),
        SnippetChooserPanel('sharing_text'),
    ]
예제 #19
0
 def _create_blog_article_page(
     self,
     blog_index_page=None,
     title="Simple Article Title",
     page_title="Simple Article Title",
     date=datetime.now(),
     body=None,
     author=None,
     read_time=7,
     table_of_contents=False,
     recommended_articles=None,
     views=0,
     cover_photo=None,
     article_photo=None,
     is_main_article=False,
 ):
     if body is None:
         block = StreamBlock([(ArticleBodyBlockNames.MARKDOWN.value,
                               MarkdownBlock())])
         body = StreamValue(
             block,
             [(ArticleBodyBlockNames.MARKDOWN.value, "Hello, World")])
     if author is None:
         author = BossFactory()
     blog_article_page = BlogArticlePage(
         title=title,
         page_title=page_title,
         date=date,
         body=body,
         author=author,
         read_time=read_time,
         table_of_contents=table_of_contents,
         recommended_articles=recommended_articles,
         views=views,
         cover_photo=cover_photo,
         article_photo=article_photo,
         is_main_article=is_main_article,
     )
     if blog_index_page is None:
         blog_index_page = self._get_newest_blog_index_page()
     blog_index_page.add_child(instance=blog_article_page)
     blog_article_page.save()
     return blog_article_page
예제 #20
0
class WorkExperienceBlock(blocks.StructBlock):
    class Meta:
        template = "wagtail_resume/blocks/work_experience_block.html"
        icon = "doc-full-inverse"

    heading = blocks.CharBlock(default="Work experience")
    fa_icon = blocks.CharBlock(default="fas fa-tools")
    experiences = blocks.ListBlock(
        blocks.StructBlock(
            [
                ("role", blocks.CharBlock()),
                ("company", blocks.CharBlock()),
                ("url", blocks.URLBlock()),
                ("from_date", blocks.DateBlock()),
                ("to_date", blocks.DateBlock()),
                ("text", MarkdownBlock()),
            ],
            icon="folder-open-inverse",
        )
    )
예제 #21
0
class BlogPage(Page):
    """ This is the core of the Blog app. BlogPage are individual articles
    """
    subtitle = models.CharField(blank=True, max_length=255)

    body = MarkdownField(blank=True)

    extended_body = StreamField([
        ('content', MarkdownBlock(template='blog/markdown_block.html')),
        ('newsletter', NewsletterSubscribe()),
        ('book', BookInline()),
    ],
                                null=True)

    image = models.ForeignKey(
        'wagtailimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
        help_text='Header Image, used also for social sharing')

    image_data = RichTextField(
        blank=True,
        null=True,
        help_text=
        'Information about the header image, to appear after the article')

    tags = ClusterTaggableManager(through=BlogPageTag, blank=True)
    date_published = models.DateField("Date article published",
                                      blank=True,
                                      null=True)

    allow_comments = models.BooleanField(default=True)

    content_panels = Page.content_panels + [
        FieldPanel('subtitle'),
        StreamFieldPanel('extended_body'),
        MarkdownPanel('body'),
        ImageChooserPanel('image'),
        FieldPanel('image_data'),
        FieldPanel('date_published'),
        InlinePanel('blog_person_relationship',
                    label="Author(s)",
                    panels=None,
                    min_num=1),
        FieldPanel('tags'),
    ]

    promote_panels = Page.promote_panels + [
        FieldPanel('allow_comments'),
    ]

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

    def authors(self):
        """
        Returns the BlogPage's related People. Again note that we are using
        the ParentalKey's related_name from the BlogPeopleRelationship model
        to access these objects. This allows us to access the People objects
        with a loop on the template. If we tried to access the blog_person_
        relationship directly we'd print `blog.BlogPeopleRelationship.None`
        """
        authors = [n.people for n in self.blog_person_relationship.all()]

        return authors

    @property
    def first_author(self):
        return self.authors()[-1]

    @property
    def get_tags(self):
        """
        Similar to the authors function above we're returning all the tags that
        are related to the blog post into a list we can access on the template.
        We're additionally adding a URL to access BlogPage 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

    # Specifies parent to BlogPage as being BlogIndexPages
    parent_page_types = ['BlogIndexPage']

    # Specifies what content types can exist as children of BlogPage.
    # Empty list means that no child content types are allowed.
    subpage_types = []

    def get_absolute_url(self):
        return self.specific.url

    def get_context(self, request):
        context = super(BlogPage, self).get_context(request)
        context['latest_articles'] = BlogPage.objects.live().order_by(
            '-date_published')[:5]
        context['intro_class'] = 'blog article'
        return context

    @property
    def introduction(self):
        html = render_markdown(self.body)
        soup = BeautifulSoup(html, "html.parser")
        try:
            introduction = soup.find('p').text
            return introduction
        except AttributeError:
            return None

    class Meta:
        ordering = ['-date_published']
예제 #22
0
파일: models.py 프로젝트: elfzweik/blogsite
class BlogDetailPage(Page):
    template = "blog/blog_detail_page.html"

    custom_title = models.CharField('Title', max_length=80, help_text='文章标题')
    author = models.CharField("Author",
                              max_length=255,
                              default="Wang Zhenxuan")
    create_date = models.DateField("Create date", auto_now_add=True)
    update_date = models.DateField("Update date", auto_now=True)
    intro = RichTextField(max_length=250, help_text='文章简介')
    tags = ClusterTaggableManager(through=BlogPageTag, blank=True)

    #categories = ParentalManyToManyField('blog.BlogCategory', blank=True)

    content = StreamField(
        [
            ('heading', blocks.CharBlock(classname="full title")),
            ('paragraph', blocks.RichTextBlock()),
            ('image', ImageChooserBlock()),
            ('blockquote', blocks.BlockQuoteBlock(label='Block Quote')),
            ('documentchooser',
             DocumentChooserBlock(label='Document Chooser')),
            ('url', blocks.URLBlock(label='URL')),
            ('embed', EmbedBlock(label='Embed')),
            #('snippetchooser', SnippetChooserBlock(label='Snippet Chooser')),
            ('rawhtml', blocks.RawHTMLBlock(label='Raw HTML')),
            ('table', TableBlock(label='Table')),
            ('markdown', MarkdownBlock(label='Markdown')),
            ('code', CodeBlock(label='Code')),
            ('imagedeck', CardBlock(label='Imagedeck')),
        ],
        null=True,
        blank=True,
    )

    def main_image(self):
        gallery_item = self.gallery_images.first()
        if gallery_item:
            return gallery_item.image
        else:
            return None

    def prev(self):
        try:
            previous = self.get_next_sibling()
            return (previous)
        except self.DoesNotExist:
            return (None)

    def next(self):
        try:
            return (self.get_prev_sibling())
        except self.DoesNotExist:
            return (None)

    search_fields = Page.search_fields + [
        index.SearchField('custom_title'),
        index.SearchField('intro'),
        index.SearchField('content'),
        index.SearchField('create_date'),
    ]

    content_panels = Page.content_panels + [
        MultiFieldPanel(
            [
                FieldPanel('custom_title'),
                FieldPanel('intro'),
                FieldPanel('author'),
                #FieldPanel('create_date'),
                #FieldPanel('update_date'),
                FieldPanel('tags'),
                #FieldPanel('categories', widget=forms.CheckboxSelectMultiple),
            ],
            heading="Blog information"),
        InlinePanel('gallery_images', label="Gallery images"),
        # FieldPanel('body'),
        StreamFieldPanel('content'),
    ]

    class Meta:
        ordering = ['-create_date']
예제 #23
0
 def test_markdown_block(self):
     block = MarkdownBlock()
     self.assertEqual(block.render_basic("# hello"), "<h1>hello</h1>")
예제 #24
0
class BlogDetailPage(Page):
    template = "blog/blog_detail_page.html"

    def get_context(self, request):

        authorname = self.author.get_fullname_or_username()
        data = count_visits(request, self)

        context = super().get_context(request)

        context['client_ip'] = data['client_ip']
        context['location'] = data['location']
        context['total_hits'] = data['total_hits']
        context['total_visitors'] = data['total_vistors']
        context['cookie'] = data['cookie']
        context['author'] = authorname
        return context

    def serve(self, request):
        context = self.get_context(request)
        template = self.get_template(request)

        response = render(request, template, context)
        response.set_cookie(context['cookie'], 'true', max_age=300)
        return response

    custom_title = models.CharField('Title', max_length=60, help_text='文章标题')
    author = models.ForeignKey(User, on_delete=models.PROTECT)
    create_date = models.DateField("Create date", auto_now_add=True)
    update_date = models.DateField("Update date", auto_now=True)
    intro = models.CharField('Introduction', max_length=500, help_text='文章简介')
    tags = ClusterTaggableManager(through=BlogPageTag, blank=True)

    categories = ParentalManyToManyField('blog.BlogCategory', blank=True)

    content = CustomStreamField(
        [
            ('heading', blocks.CharBlock(form_classname="full title")),
            ('paragraph', blocks.RichTextBlock()),
            ('image', ImageChooserBlock()),
            ('blockquote', blocks.BlockQuoteBlock(label='Block Quote')),
            ('documentchooser',
             DocumentChooserBlock(label='Document Chooser')),
            ('url', blocks.URLBlock(label='URL')),
            ('embed', EmbedBlock(label='Embed', width=800)),
            #('snippetchooser', SnippetChooserBlock(label='Snippet Chooser')),
            ('rawhtml', blocks.RawHTMLBlock(label='Raw HTML')),
            ('table', TableBlock(label='Table')),
            ('markdown', MarkdownBlock(label='Markdown')),
            ('equation', MathBlock(label='Equation')),
            ('code', CodeBlock(label='Code')),
            ('imagedeck', CardBlock(label='Imagedeck')),
        ],
        null=True,
        blank=True,
    )

    def main_image(self):
        gallery_item = self.gallery_images.first()
        if gallery_item:
            return gallery_item.image
        else:
            return None

    def prev(self):
        try:
            previous = self.get_next_sibling()
            return (previous)
        except self.DoesNotExist:
            return (None)

    def next(self):
        try:
            return (self.get_prev_sibling())
        except self.DoesNotExist:
            return (None)

    def get_url(self):
        return self.url

    def get_user(self):
        return self.author

    def get_email(self):
        return self.author.email

    search_fields = Page.search_fields + [
        index.SearchField('custom_title'),
        index.SearchField('intro'),
        index.SearchField('content'),
        index.SearchField('create_date'),
        index.SearchField('tags'),
    ]

    content_panels = Page.content_panels + [
        InlinePanel('gallery_images', label="Gallery images"),
        MultiFieldPanel(
            [
                FieldPanel('custom_title'),
                FieldPanel('intro'),
                FieldPanel('author'),
                #FieldPanel('create_date'),
                #FieldPanel('update_date'),
                FieldPanel('tags'),
                FieldPanel('categories', widget=forms.CheckboxSelectMultiple),
            ],
            heading="Blog information"),

        # FieldPanel('body'),
        StreamFieldPanel('content'),
    ]

    class Meta:
        ordering = ['-create_date']
예제 #25
0
class MyStreamBlock(StreamBlock):
    markdown = MarkdownBlock(icon="code")
예제 #26
0
class StoryPage(Page):
    MAX_RELATED_STORIES = 10

    # This is a leaf page
    subpage_types = []
    parent_page_types = [
        'news.StoryIndexPage'
    ]

    author = models.ForeignKey(
        'common.User', 
        null=True, 
        blank=True, 
        on_delete=models.SET_NULL, 
        related_name="+"
    )
    date = models.DateField("post date")
    lede = models.CharField(
        max_length=1024, 
        help_text="A short intro that appears in the story index page"
    )
    tags = ClusterTaggableManager(through=StoryTag, blank=True)
    categories = ParentalManyToManyField(
        'news.StoryCategory', 
        blank=True, 
        help_text="The set of categories this page will be served"
    )

    # Add allowed block types to StreamPanel
    content = StreamField(
        [
            ('paragraph', RichTextBlock(features=[
                'h2', 'h3', 'bold', 'italic', 'link', 'ol', 'ul'
            ])),
            ('markdown', MarkdownBlock(icon='code')),
            ('image', StructBlock([
                ('image', ImageChooserBlock()),
                ('caption', CharBlock(required=False))
            ])),
            ('carousel', ImageCarouselBlock()),
            ('quote', BlockQuoteBlock()),
            ('embedded_content', EmbedContentBlock()),
        ]
    )

    def get_context(self, request):
        
        context = super().get_context(request)
        tags_list = list(self.tags.all())

        # Collects set of stories that has the same tags as this story.
        related = StoryPage.objects.live().order_by('-first_published_at')
        related = related.filter(tags__in=tags_list)
        related = related.exclude(id=self.id)
        if related.count() > 0:
            related = related.distinct()[0:self.MAX_RELATED_STORIES]
            context["related"] = related

        try:
            context['prevstory'] = self.get_previous_by_date(tags__in=tags_list)
        except (StoryPage.DoesNotExist, ValueError):
            pass

        try:
            context['nextstory'] = self.get_next_by_date(tags__in=tags_list)
        except (StoryPage.DoesNotExist, ValueError):
            pass

        return context

    def hero_image(self):
        gallery_item = self.gallery_images.first()
        if gallery_item:
            return gallery_item.image
        else:
            return None

    def media_thumbnail_url(self):
        for child in self.content:
            if child.block.name == 'embedded_content':
                return child.value.thumbnail_url
        return None

    def hero_image_url(self):
        hero_image = self.hero_image()
        if hero_image:
            return hero_image.url
        return self.media_thumbnail_url()

    def author_name(self):
        if not self.author:
            return ANONYMOUS_AUTHOR_NAME
        else:
            return self.owner.username
        
    search_fields = Page.search_fields = [
        index.SearchField('lede'),
        index.SearchField('body'),
    ]

    content_panels = Page.content_panels + [
        MultiFieldPanel([
            StoryAuthorFieldPanel('author', widget=forms.Select),
            FieldPanel('date'),
            FieldPanel('tags'),
            FieldPanel('categories', widget=forms.CheckboxSelectMultiple),
        ]),
        FieldPanel('lede'),
        InlinePanel('gallery_images', label="Gallery Images"),
        StreamFieldPanel('content')
    ]
예제 #27
0
class BlogArticlePage(MixinSeoFields, Page, MixinPageMethods, GoogleAdsMixin):
    template = "blog_post.haml"
    date = models.DateField("Post date")
    page_title = models.CharField(
        max_length=MAX_BLOG_ARTICLE_TITLE_LENGTH,
        help_text=ArticleBlogPageHelpTexts.ARTICLE_PAGE_TITLE.value)
    body = StreamField([
        (ArticleBodyBlockNames.MARKDOWN.value, MarkdownBlock(icon="code")),
        (ArticleBodyBlockNames.HEADER.value, CharBlock()),
        (ArticleBodyBlockNames.PARAGRAPH.value,
         RichTextBlock(features=RICH_TEXT_BLOCK_FEATURES)),
        (ArticleBodyBlockNames.TABLE.value, TableBlock()),
        (ArticleBodyBlockNames.IMAGE.value, CaptionedImageBlock()),
    ], )

    search_fields = Page.search_fields + [
        index.SearchField("intro"),
        index.SearchField("body")
    ]

    author = models.ForeignKey(Employees, on_delete=models.DO_NOTHING)

    read_time = models.PositiveIntegerField()

    table_of_contents = models.BooleanField(default=False)

    recommended_articles = StreamField(
        [("page",
          PageChooserBlock(can_choose_root=False,
                           page_type="blog.BlogArticlePage"))],
        null=True,
        blank=True,
    )

    views = models.PositiveIntegerField(default=0)

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

    cover_photo_alt_description = models.CharField(max_length=125,
                                                   blank=True,
                                                   default="Open the article")

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

    article_photo_alt_description = models.CharField(max_length=125,
                                                     blank=True,
                                                     default="")

    is_main_article = models.BooleanField(default=False)

    Page._meta.get_field(
        "title").help_text = ArticleBlogPageHelpTexts.PAGE_TITLE.value

    content_panels = Page.content_panels + [
        FieldPanel("page_title", classname="title full"),
        FieldPanel("date"),
        FieldPanel("author"),
        FieldPanel("read_time"),
        StreamFieldPanel("recommended_articles"),
        FieldPanel("views"),
        FieldPanel("is_main_article"),
        ImageChooserPanel("cover_photo"),
        FieldPanel("cover_photo_alt_description"),
        ImageChooserPanel("article_photo"),
        FieldPanel("article_photo_alt_description"),
        FieldPanel("table_of_contents"),
        StreamFieldPanel("body"),
    ]

    @cached_property
    def headers_list(self) -> List[str]:
        list_of_headers = []
        for stream_child in self.body:  # pylint: disable=not-an-iterable
            if stream_child.block.name == ArticleBodyBlockNames.HEADER.value:
                list_of_headers.append(stream_child.value)
        return list_of_headers

    def get_header_id(self, title: str) -> int:
        return self.headers_list.index(title)

    @cached_property
    def intro(self) -> str:
        paragraph_text = self._get_text_for_intro(
            MAX_BLOG_ARTICLE_INTRO_LENGTH)
        if len(paragraph_text) == 0:
            return "Article intro not available."
        words_cycle = cycle(paragraph_text.split())
        intro_text = self._concatenate_intro_text_from_paragraphs_text(
            words_cycle, MAX_BLOG_ARTICLE_INTRO_LENGTH)
        end_ellipsis = INTRO_ELLIPSIS
        return intro_text + end_ellipsis

    def _get_text_for_intro(self, character_limit: int) -> str:
        text_blocks: list = self._get_list_of_text_blocks()
        paragraphs_text = ""
        if len(text_blocks) == 0:
            return paragraphs_text

        blocks_cycle = cycle(text_blocks)
        while len(paragraphs_text) < character_limit:
            paragraphs_text = self._extract_paragraph_text_from_block(
                blocks_cycle, paragraphs_text)
        return paragraphs_text

    def _get_list_of_text_blocks(self) -> list:
        whitelisted_block_names = [
            ArticleBodyBlockNames.PARAGRAPH.value,
            ArticleBodyBlockNames.MARKDOWN.value
        ]
        return list(
            filter(
                lambda body_element: body_element.block.name in
                whitelisted_block_names, self.body))

    @staticmethod
    def _extract_paragraph_text_from_block(blocks: cycle, text: str) -> str:
        space_between_texts = " "
        next_block = next(blocks)
        if next_block.block.name == ArticleBodyBlockNames.MARKDOWN.value:
            source_text = "".join(
                BeautifulSoup(markdown(next_block.value),
                              "html.parser").findAll(text=True))
        else:
            source_text = next_block.value.source
        next_text = strip_tags(source_text)

        if len(text) == 0:
            text = next_text
        else:
            text += f"{space_between_texts}{next_text}"
        return text

    def _concatenate_intro_text_from_paragraphs_text(
            self, words_cycle: cycle, character_limit: int) -> str:
        intro_text = ""
        new_text = next(words_cycle)
        while len(new_text) < character_limit:
            intro_text = new_text
            new_text = self._concatenate_strings(intro_text, words_cycle)
        return intro_text

    @staticmethod
    def _concatenate_strings(text: str, words: cycle) -> str:
        space_between_texts = " "
        text += f"{space_between_texts}{next(words)}"
        return text

    def get_proper_url(self) -> str:
        return self.slug

    def get_absolute_url(self) -> str:
        return self.url_path

    def get_context(self, request: WSGIRequest, *args: Any,
                    **kwargs: Any) -> dict:
        context = super().get_context(request, *args, **kwargs)
        self._increase_view_counter()
        context["URL_PREFIX"] = settings.URL_PREFIX
        context["article_body_block_names"] = ArticleBodyBlockNames
        context["GOOGLE_ADS_CONVERSION_ID"] = settings.GOOGLE_ADS_CONVERSION_ID
        context["GOOGLE_TAG_MANAGER_ID"] = settings.GOOGLE_TAG_MANAGER_ID
        return context

    def _increase_view_counter(self) -> None:
        # increase page view counter
        self.views += 1
        self.full_clean()
        self.save()

    def save(self, *args: Any, **kwargs: Any) -> None:  # pylint: disable=signature-differs
        if not BlogArticlePage.objects.filter(
                is_main_article=True) and not self.is_main_article:
            self.is_main_article = True
        if self.is_main_article:
            try:
                article = BlogArticlePage.objects.get(
                    is_main_article=self.is_main_article)
                article.is_main_article = False
                article.save()
            except BlogArticlePage.DoesNotExist:
                pass

        if self.table_of_contents and len(self.headers_list) == 0:
            self.table_of_contents = False

        self._validate_parent_page()
        super().save(*args, **kwargs)

    def clean(self) -> None:
        super().clean()
        self._clean_recommended_articles()
        self._validate_recommended_articles_uniqueness()

    def _clean_recommended_articles(self) -> None:
        self.recommended_articles = StreamValue(
            stream_block=StreamBlock([("page", PageChooserBlock())]),
            stream_data=[("page", stream_child.value)
                         for stream_child in self.recommended_articles
                         if stream_child.value is not None],
        )

    def _validate_parent_page(self) -> None:
        if not isinstance(self.get_parent().specific, BlogIndexPage):
            raise ValidationError(
                message=f"{self.title} must be child of BlogIndexPage")

    def _validate_recommended_articles_uniqueness(self) -> None:
        article_pages_set = set()
        for stream_child in self.recommended_articles:  # pylint: disable=not-an-iterable
            if stream_child.value in article_pages_set:
                raise ValidationError(
                    message=f"'{stream_child.value}' is listed more than once!"
                )
            article_pages_set.add(stream_child.value)