0

I am trying to show 3 latest posts in a sidebar in base.html. I found a previous question (Wagtail - display the three latest posts only on the homepage) and tried to follow but the posts don't show up.

Would appreciate any hint on how to proceed. Thanks!

# blog/models.py
from django.db import models

from modelcluster.fields import ParentalKey
from modelcluster.contrib.taggit import ClusterTaggableManager
from taggit.models import TaggedItemBase
from wagtail.core.models import Page
from wagtail.core.fields import StreamField
from wagtail.core import blocks
from wagtail.admin.edit_handlers import FieldPanel, StreamFieldPanel
from wagtail.images.blocks import ImageChooserBlock
from wagtail.snippets.models import register_snippet

richtext_features = [
    'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 
    "ol", "ul", "hr", 
    "link", "document-link", 
    "image", "embed",
    "code", "blockquote",
    "superscript", "subscript", "strikethrough",
]


class BlogListingPage(Page):
    """Listing page lists all the Blog pages."""

    template = "blog/blog_listing_page.html"

    custom_title = models.CharField(
        max_length=255,
        blank=False,
        null=False,
        help_text='Overwrites the default title',
    )

    content_panels = Page.content_panels + [
        FieldPanel("custom_title"),
    ]

    def get_context(self, request, *args, **kwargs):
        """Adding custom stuff to our context."""
        context = super().get_context(request, *args, **kwargs)
        context["posts"] = BlogPage.objects.live().public().order_by('-date')
        context["latest_posts"] = context["posts"][:3]

        if request.GET.get('tag', None):
            tags = request.GET.get('tag')
            all_posts = context["posts"].filter(tags__slug__in=[tags])

        return context

    class Meta:

        verbose_name = "Blog Listing Page"
        verbose_name_plural = "Blog Listing Pages"


@register_snippet
class BlogTag(TaggedItemBase):
    content_object = ParentalKey(
        'BlogPage',
        on_delete=models.CASCADE,
    )
    
    class Meta:
        verbose_name = "Blog Tag"
        verbose_name_plural = "Blog Tags"


class BlogPage(Page):
    """ For individual blog pages."""
        
    template = "blog/blog_page.html"
    parent_page_types = ['blog.BlogListingPage']
    tags = ClusterTaggableManager(through=BlogTag, blank=True)

    author = models.CharField(max_length=255, default="Niq")
    date = models.DateField("Post date")
    content = StreamField([
        ('heading', blocks.CharBlock(form_classname="full title")),
        ('standfirst', blocks.RichTextBlock(features=richtext_features)),
        ('paragraph', blocks.RichTextBlock(features=richtext_features)),
        ('image', ImageChooserBlock()),
    ], block_counts={
        'heading': {'min_num': 1, 'max_num': 1,},
        'standfirst': {'max_num': 1,},
    })

    content_panels = Page.content_panels + [
        FieldPanel('author'),
        FieldPanel('date'),
        FieldPanel("tags"),
        StreamFieldPanel('content'),
    ]

    @property
    def heading(self):
        for block in self.content:
            if block.block_type == 'heading':
                return block.value

    @property
    def standfirst(self):
        for block in self.content:
            if block.block_type == 'standfirst':
                return block.value


    class Meta:

        verbose_name = "Blog Page"
        verbose_name_plural = "Blog Pages"

blog/templatetags/show_latest_posts.py

from django import template
register = template.Library()

from ..models import BlogPage

@register.inclusion_tag('blog/show_latest_posts.html')
def show_latest_posts():
    latest_posts = BlogPage.objects.live().public().order_by('-date')[:3]
    return {'latest_posts': latest_posts}
    # return latest_posts

templates/blog/show_latest_posts.html

<b>Latest Posts</b>
{% for post in latest_posts %}
  <br><a href="{{ post.url }}" class="text-body text-decoration-none">{{ post.heading }}</a>
{% endfor %}

Part of base.html

<div class="right">
    {% load show_latest_posts %}
    {% show_latest_posts %}
</div>
Niq_Lin
  • 85
  • 1
  • 3
  • 10

1 Answers1

1

The line

{% for post in page.latest_posts %}

should be

{% for post in latest_posts %}

Here, page.latest_posts would refer to a latest_posts method on the current page object, but you haven't defined it this way - rather, you set context["latest_posts"] inside the get_context method, which makes the result available inside the template as the variable latest_posts (not page.latest_posts).

Keep in mind that the latest_posts variable will only be available on page types where you defined that variable inside the get_context method. In this case, you'll find that the latest posts listing shows on BlogIndexPage, but not BlogPage. You could repeat the same code in a get_context method on BlogPage, but a better approach might be to use a custom inclusion template tag to retrieve and display the listing - that way, the code is fully self-contained and doesn't need any extra querying inside your page models.

gasman
  • 23,691
  • 1
  • 38
  • 56
  • Thanks for the suggestion. I read the doc and tried `@register.inclusion_tag('base.html')` but could not get it to work. Can you help take a look at my new models.py posted above? The lines commented out are some of what I tried and failed. – Niq_Lin Jun 12 '21 at 02:39
  • The `@register.inclusion_tag` line needs to be part of a dedicated module under a `templatetags` folder of your app - if you're going that route, I'd suggest reading the [custom template tags](https://docs.djangoproject.com/en/3.2/howto/custom-template-tags/) documentation in full. Also, you wouldn't want to use base.html for this - it would be a template consisting of _just_ the blog post listing. – gasman Jun 12 '21 at 18:44
  • Thanks for your help! I got it to work and updated the post with the code in case someone should find it useful. I have another question that perhaps you can help me understand or point me to the documentation. If I use `return latest_posts` instead of returning a dictionary-`return {'latest_posts': latest_posts}` in `def show_latest_posts():`, I get an error 'PageQuerySet' object does not support item assignment. But why is it ok in models.py to just `return context`? – Niq_Lin Jun 14 '21 at 03:32
  • 1
    Because `context` is a dictionary. In both cases, you're returning a dictionary of variables that will be made available on the template. – gasman Jun 14 '21 at 15:10