We can't find the internet
Attempting to reconnect
Something went wrong!
Hang in there while we get back on track
In my blog, I list a number of related blog posts for each post. This is based on the tags I apply to the posts.
I'm using the taggit
module to manage the tags. I'm also using wagtailmarkdown
for having Markdown support in the post body.
My model looks like this:
blog/models.py
from django.db import models
from django.db.models.aggregates import Count
from django.db.models.fields import DateTimeField
from django.utils import timezone
from modelcluster.fields import ParentalKey
from modelcluster.contrib.taggit import ClusterTaggableManager
from taggit.models import TaggedItemBase
from wagtail.admin.edit_handlers import FieldPanel, MultiFieldPanel
from wagtail.core.models import Page, PageManager
from wagtailmarkdown.edit_handlers import MarkdownPanel
from wagtailmarkdown.fields import MarkdownField
class BlogPostTag(TaggedItemBase):
content_object = ParentalKey('BlogPost', related_name='tagged_items', on_delete=models.CASCADE)
class BlogPostManager(PageManager):
def related_posts(self, post, max_items=5):
tags = post.tags.all()
matches = BlogPost.objects.filter(tags__in=tags).live().annotate(Count('title'))
matches = matches.exclude(pk=post.pk)
related = matches.order_by('-title__count')
return related[:max_items]
class BlogPost(Page):
objects = BlogPostManager()
body = MarkdownField(blank=True)
tags = ClusterTaggableManager(through=BlogPostTag, blank=True)
date = DateTimeField(blank=True, default=None, null=True, db_index=True)
date.verbose_name = 'Publish Date'
content_panels = [
MultiFieldPanel(
[
FieldPanel('title', classname="full title"),
FieldPanel('date'),
FieldPanel('tags'),
],
heading="Post Details",
classname="collapsible"
),
MarkdownPanel('body'),
]
def get_context(self, request, *args, **kwargs):
context = super(BlogPost, self).get_context(request)
context['related_posts'] = BlogPost.objects.related_posts(self)
return context
A whole lot of code, but lets look at it step by step.
At the top, you'll find the list of imports of all the items we need.
We first define BlogPostTag
which is a single tag which can be added to a blog post. The blog posts themselves are defined in the class BlogPost
which inherits from the base Page class. I do this to get base functionality of a Wagtail page in there. So far, that's all pretty standard.
We also define a couple of field such as body
and date
. To add tags to the blog post, we define a ClusterTaggableManager
through the BlogPostTag
class. This essentially creates a many-to-many relation between the blog posts and the tags. To make them editable, we also need to add them to the content_panels
.
I also created a class BlogPostManager
which inherits from Wagtail's PageManager
class. I like this approach as it keeps the functions to access the blog posts nicely separated from the actual blog post definition. By using my custom manager as the objects
variable in the BlogPost
class, it makes it nice and clean.
In this example, BlogPostManager
only defines one single extra method called related_posts
. This is the method responsible for finding the related posts. It will first get the list of tags assigned to the given post. Based on that list, it will filter out all live blog posts which have tags in common. It also annotates the them by the number of tags they have in common. We also exclude the blog posts itself as we don't want it to show in the listing.
The list is ordered starting with the posts that have most tags in common.
I also added a limit so that we don't get all posts, but just a subset.
This is then used in the get_context
function of the blog post so that they are available in the template.
In the template, we can show the related posts with a loop statement:
blog/templates/blog/blog_post.html
<h1>{{ page.title }}</h1>
{{ page.body | markdown | safe }}
{% if related_posts %}
<h1 class="related">Related Posts</h1
<ul>
{% for post in related_posts %}
<li><a href="{% pageurl post %}">{{ post.title }}</a></li>
{% endfor %}
</ul>
{% endif %}
If this post was enjoyable or useful for you, please share it! If you have comments, questions, or feedback, you can email my personal email. To get new posts, subscribe use the RSS feed.