Last time, we introduced the page class PostPage
, which is the image of a block post, so this time, let's add the tag and category functions to it. This is a frequent topic, for example, in the official documentation Your first Wagtail site and Tutorial here.
First, add the category and tag model definitions to cms/models.py and associate them with PostPage
as shown below.
...
from modelcluster.fields import ..., ParentalManyToManyField
from modelcluster.contrib.taggit import ClusterTaggableManager
from taggit.models import TaggedItemBase, Tag as TaggitTag
...
class PostPage(Page):
...
categories = ParentalManyToManyField(
'cms.PostCategory',
blank=True
)
tags = ClusterTaggableManager(
through='cms.PostTag',
blank=True
)
...
content_panels = Page.content_panels + [
...,
FieldPanel('categories', widget=forms.CheckboxSelectMultiple),
FieldPanel('tags'),
]
...
...
@register_snippet
class PostCategory(models.Model):
name = models.CharField(max_length=255)
panels = [
FieldPanel('name'),
]
def __str__(self):
return self.name
class Meta:
verbose_name = "Category"
verbose_name_plural = "Categories"
class PostTag(TaggedItemBase):
content_object = ParentalKey(PostPage, related_name='tagged_posts')
@register_snippet
class Tag(TaggitTag):
class Meta:
proxy = True
Let's start with the PostCategory
class. This is for representing categories, and is a simple class that inherits Django's default django.db.models.Model
class and holds only the string name
in the field. The @register_snippet
decorator registers this class as a snippet in Wagtail. As a result, you can edit the category on the snippet edit screen of the management site. The specification of panels
is the setting of the editing panel used at that time.
As you can see from the field categories
added to the PostPage
class, you can use ParentalManyToManyField
to link the categories defined above to the page many-to-many. Also, in the place of content_panels
, by specifyingwidget =
in the argument, the correspondence between the page and the category can be specified by the check box.
Next, let's move on to the tag model. In Wagtail, it is standard to implement the tagging function using django-taggit. That is why the definition of the PostTag
class and the method of adding the tags
field to the PostPage
class are different from those of the category (details are omitted here). With the settings up to this point, you should be able to freely add new tags to the page on the edit screen of PostPage
.
The Tag
class is defined as the proxy model of the taggit.models.Tag
class imported under the name TaggitTag
, and the @ register_snippet
decorator is applied to the category on the snippet edit screen of the management site. This is so that not only the tags but also the tags will appear. This makes it convenient to open the snippet edit screen and check the list of tags added so far.
At this stage, log in to the management site and add some categories from the snippet edit screen. Then, from the edit screen of the page of the PostPage
class, let's select a category or add a tag.
Next, when displaying the page of the PostPage
class with the category specified or the tag added, try to make that information appear in the sidebar. Therefore, templates/cms/post_page.html was modified as follows.
{% block side_bar %}
{{ block.super }}
<div class="card my-4">
<h4 class="card-header">
Categories
</h4>
<div class="card-body">
{% for category in page.categories.all %}
<a class="btn btn-outline-primary btn-sm m-1" href="{% pageurl top_page %}category/?category={{ category }}" role="button">
{{ category.name }}
</a>
{% endfor %}
</div>
</div>
<div class="card my-4">
<h4 class="card-header">
Tags
</h4>
<div class="card-body">
{% for tag in page.tags.all %}
<a class="btn btn-outline-primary btn-sm m-1" href="{% pageurl top_page %}tag/?tag={{ tag }}" role="button">
{{ tag.name }}
</a>
{% endfor %}
</div>
</div>
{% endblock %}
You can see that both the category and the tag are displayed as buttons in the Bbootstrap card. If the link destination of the button is a category,
top_page URL/category /? category = category string
In the case of tags,
top_page URL/tag /? tag = tag string
It can be seen that Here, as a page to undertake these, let's create one page of the ListPage
class as a child element of the corresponding TopPage
page, one for the category and one for the tag (PROMOTE tab of the edit screen). Set the Slug string to category and tag, respectively).
Next, when the ListPage
instance created above and linked to the button is displayed, the ListPage
page with the specified category and tag is displayed in a list. Let's add a little work to the definition of. Specifically, modify the get_context ()
method as follows.
class ListPage(Page):
...
def get_context(self, request):
context = super().get_context(request)
context['top_page'] = self.get_top_page()
context['breads'] = self.get_breads()
if self.related_pages.count():
context['page_list'] = [item.page for item in self.related_pages.all()]
else:
tag = request.GET.get('tag')
category = request.GET.get('category')
if category:
context['category'] = category
context['page_list'] = PostPage.objects.descendant_of(self.get_top_page()).filter(categories__name=category).live().order_by('-first_published_at')
elif tag:
context['tag'] = tag
context['page_list'] = PostPage.objects.descendant_of(self.get_top_page()).filter(tags__name=tag).live().order_by('-first_published_at')
else:
context['page_list'] = self.get_children().live().order_by('-first_published_at')
return context
Below if
, you can see that the page_list
to be included in the context is changed according to the conditions. I hope you can check the details in the above code, but the point is that if category
or tag
is specified in the query of the GET method, only the PostPage
instance to which it is attached is The point is that the one that is acquired and sorted in descending order of the posting date is called page_list
. In addition, the character string itself of the specified category or tag is also included in the context.
So if you press the button above to jump to a URL with a category
or tag
query, this modified get_context ()
method will be called before the corresponding ListPage
is rendered, and the Since page_list
contains only PostPage
instances with the specified category and tag, they will be listed as a result.
By the way, I made the following modifications to templates/cms/list_page.html to display the information on Jumbotron so that I can see what category or tag the page corresponding to is displayed. Let's keep it.
{% block header_text %}
{{ block.super }}
{% if category %}
<h5>Posts in category: {{ category|upper }}</h5>
{% elif tag %}
<h5>Posts given tag: {{ tag|upper }}</h5>
{% endif %}
{% endblock %}
This time I've briefly looked at how to add category and tag information to the PostPage
page, and in the process I also touched on how to handle Wagtail snippets.
The schedule for the next time and beyond is undecided so far, but there are still interesting topics left, so I would like to write a sequel again someday if I have the opportunity.
-Wagtail Recommendations (1) A story about choosing Wagtail by comparing and examining Django-based CMS -Wagtail Recommendation (2) Let's define, edit, and display the page -Wagtail Recommendation (3) Understand and use the tree structure of the page -Wagtail Recommendation (4) Let's pass context to template -Wagtail Recommendation (5) Let's add your own block to StreamField