Search code examples
pythonhtmldjangowagtaildjango-taggit

Django/Wagtail/taggit - Getting model specific tag list


I have seen this:

Generating a unique list of Django-Taggit Tags in Wagtail;

and the solutions there have generated a list of all stored tags, i.e. image, document, etc tags.

I am trying to accomplish a similar goal. On a News Index Page, have a dropdown of the News Page Tags.

I can't seem to get a list of tags that are only News Page Tags, currently this gives me a list of all tags in my site, including image and document tags.

from django.template.response import TemplateResponse
from modelcluster.fields import ParentalKey, ParentalManyToManyField
from modelcluster.tags import ClusterTaggableManager
from taggit.models import TaggedItemBase, Tag
from core.models import Page

class NewsPageTag(TaggedItemBase):
    content_object = ParentalKey('NewsPage', related_name='tagged_items')


class NewsPage(Page):
    tags = ClusterTaggableManager(through=NewsPageTag, blank=True)


class NewsIndexPage(Page):

    def serve(self, request, *args, **kwargs):
        context['tags'] = Tag.objects.all().distinct('taggit_taggeditem_items__tag')
        return TemplateResponse(
            request,
            self.get_template(request, *args, **kwargs),
            context
        )

I have also tried:

from django.contrib.contenttypes.models import ContentType
# ...
def serve(self, request, *args, **kwargs):
    news_content_type = ContentType.objects.get_for_model(NewsPage)
    context['tags'] = Tag.objects.filter(
        taggit_taggeditem_items__content_type=news_content_type
    )
    return TemplateResponse(
        request,
        self.get_template(request, *args, **kwargs),
        context
    )

which assigns context['tags'] an empty set

My template:

{% if tags.all.count %}
    {% for tag in tags.all %}
        <a class="dropdown-item" href="?tag={{ tag.id }}">{{ tag }}</a>
    {% endfor %}
{% endif %}

HELP! This feels like it shouldn't be so complicated. Thank you


Solution

  • You could copy how this is achieved in the new Wagtail Bakery Demo application which is a great reference.

    Essentially just getting all the child pages and then getting their tags, using set to ensure that they are unique objects.

    First add a method to your NewsIndexPage that will help you get these tags in a consistent way.

    See: models.py

    class NewsIndexPage(Page):
    
        # Returns the list of Tags for all child posts of this BlogPage.
        def get_child_tags(self):
            tags = []
            news_pages = NewsPage.objects.live().descendant_of(self);
            for page in news_pages:
                # Not tags.append() because we don't want a list of lists
                tags += page.get_tags
            tags = sorted(set(tags))
            return tags
    

    Because you have a method on your NewsPageIndex model, you do not need o override the serve method, you can get the tags directly in your template like this.

        {% if page.get_child_tags %}
            {% for tag in page.get_child_tags %}
                <a class="dropdown-item" href="?tag={{ tag.id }}">{{ tag }}</a>
            {% endfor %}
        {% endif %}
    

    See a similar template approach in the Wagtail Bakery Demo's blog_index_page.html

    Note: You can still add to the context by doing something like this in the serve method:

    context['tags'] = self.get_child_tags()