[PYTHON] Wagtail Recommendations (6) Add categories and tags

Introduction

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.

Add category model and tag model

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.

Display tags and categories in the sidebar

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).

Display of page list with the same category tag

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 %}

in conclusion

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.

Link

-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

Recommended Posts

Wagtail Recommendations (6) Add categories and tags
Wagtail Recommendations (2) Let's define, edit, and display a page
Wagtail Recommendations (3) Understand and use the tree structure of pages