Filter Wagtail Snippets by Tag đź’ˇ
In this tutorial I'll show you how to add tags to Wagtail Snippets. We also create a custom SnippetChooser to filter Snippets by tag.
Wagtail comes with Page, Image and Document content types. Pages have a tree structure. Images and documents do not have any hierarchy. You can group images and documents by applying tags. Tagging helps to describe an item and allows it to be found when using a chooser.
Which tags are used is up to the content creator. Tags are a great way to organise content in a flexible way.

Above: Add tags during image upload. Below: Filter by tag when choosing an image.

Create a Snippet
Most websites need additional content types. Imagine a website for a Business Club: It may also need Event, Venue and Organisation types. Let's create a Django model to store Organisation objects and register it as a Wagtail Snippet. A Snippet makes a Django model editable via the Wagtail admin via the Snippets menu-item.
from django.db import models
from wagtail.snippets.models import register_snippet
@register_snippet
class Organisation(models.Model):
name = models.CharField(max_length=100)
def __str__(self):
return self.name
panels = [
FieldPanel('name', classname='full'),
]
Create an EventPage
Our Business Club organises events. We need an event page. The host of the event will be an organisation. Here we define yet another content type named EventPage.
Wagtail uses Django’s multi-table inheritance feature to allow multiple page models to be used in the same tree.
Our EventPage is a subclass of the Wagtail Page model. The Wagtail Page model provides all the core functionality: page tree, basic fields (title, slug, etc), admin interface, url routes, view, search, revisions, etc.
We define an extra field host
with a foreign key to Organisation. The SnippetChooserPanel
enables us to choose our Organisation snippets.
from wagtail.core.models import Page
from wagtail.snippets.edit_handlers import SnippetChooserPanel
class EventPage(Page):
host = models.ForeignKey(
Organisation,
on_delete=models.PROTECT,
help_text='Choose a host organisation for this event.'
)
content_panels = Page.content_panels + [
SnippetChooserPanel('host')
]
Great, now we can create organisations, event pages and choose a host organisation!


Add Search
Our Business Club has many member organisations. The organisation list will be very long! This will make it hard to select a specific organisation. Let's add search:
from wagtail.search import index
@register_snippet
class Organisation(index.Indexed, models.Model):
...
search_fields = [
index.SearchField('name', partial_match=True),
]
Add Tags
Wagtail fully supports django-taggit so we recommend using that.
Let's add tags. By default django-taggit uses a “through model” with a GenericForeignKey. This allows a tag to be linked to any model. We don't want that. The tags we create should be used on Organisations and not bleed to Images or Documents. TaggedItemBase
does wat we want. It allows us to use a Foreign key to link to the Organisation model.
from taggit.managers import TaggableManager
from taggit.models import TaggedItemBase
class OrganisationTag(TaggedItemBase):
content_object = models.ForeignKey(
'app.Organisation',
on_delete=models.CASCADE,
related_name='tagged_items'
)
@register_snippet
class Organisation(models.Model):
...
tags = TaggableManager(through=OrganisationTag)
We now can add tags to organisations! Editors already know how tags work. User adoption should be a smooth process.


Unfortunately the SnippetChooser does not show tags. Why? Organisation -a snippet- is a custom type. There is no way for Wagtail to know if this type has tags or not. Wagtail does not make assumptions, therefore the SnippetChooser also does not have tags by default.
Override the Organisation SnippetChooser
The Wagtail Documentation does not mention how to customise the SnippetChooser. We are on undocumented terrain! This means future releases of Wagtail might change this code without any warning. Realise that an upgrade of Wagtail might break our customisation!
There are two reasons I'm not too worried. First: removing my custom snippet chooser will re-enable the default SnippetChooser. Second: the concept of choosers is here to stay. We'll be overriding a view, it is likely we can do the same in the future.
Get on with it!
When 'Choose an Organisation' is clicked you can see an Ajax request being fired. This is the view we need to customise. The url is /admin/snippets/choose/website/organisation/
.
Let's create the taggedsnippetchooser
app, add it to INSTALLED_APPS
, create a urls.py, views.py and template directory. In project main urls.py we include taggedsnippetchooser_urls
before wagtailadmin_urls
.
from taggedsnippetchooser import urls as taggedsnippetchooser_urls
urlpatterns = [
url(r'^admin/', include(taggedsnippetchooser_urls)),
url(r'^admin/', include(wagtailadmin_urls)),
]
In taggedsnippetchooser/urls.py
we add the rest of the pattern. Note that we hard code the slugs website
and organisation
. So that only the organisation SnippetChooser uses the custom code. All other content types use the Wagtail provided SnippetChooser.
from django.conf.urls import url
from .views import choose
kwargs = {'app_label': 'app', 'model_name': 'organisation'}
urlpatterns = [
url(r'^snippets/choose/website/organisation/$', choose, kwargs, name='choose'),
]
The original choose
view lives at wagtail/snippets/views/chooser.py
. Just copy/paste the view method and relevant imports into taggedsnippetchooser/views.py
. Also copy the original choose.html
and choose.js
templates from wagtail/snippets/templates/wagtailsnippets/chooser/
to our template directory at taggedsnippetchooser/templates/taggedsnippetchooser/
.
Now we have overridden the chooser view for Organisation snippets. A request to /admin/snippets/choose/website/organisation/
isn't served by Wagtail, but by our code!
This is a good moment to check-in changes. It makes it easier for our future self to see the difference between original Wagtail code and our customisations.
Customise the Organisation SnippetChooser
In the next section we add our customisations. Most code is borrowed from Wagtail's ImageChooser.
In views.py
. Just before the # paginator
we add a tag filter. If the querystring parameters contain the tag key, the items will be filterd by tag.
tag_name = request.GET.get('tag')
if tag_name:
items = items.filter(tags__name=tag_name)
To display tags and make them clickable we have to get the Organisation tags and add them to the context to render in our templates.
- Import the Organisation model.
- Use our templates.
-
Add
popular_tags
to the context.from app.models import Organisation # 1.
...
return render_modal_workflow( request, 'taggedsnippetchooser/choose.html', 'taggedsnippetchooser/choose.js', # 2. { ... 'popular_tags': Organisation.tags.most_common(), # 3. }
Render the tags in the template. Edit the taggedsnippetchooser/choose.html
templates to display the tags. After <li class="submit">...</li>
insert:
{% if popular_tags %}
<li class="taglist">
<h3>{% trans 'Popular tags' %}</h3>
{% for tag in popular_tags %}
<a class="suggested-tag tag" href="/admin/snippets/choose/website/organisation/?tag={{ tag.name|urlencode }}">{{ tag.name }}</a>
{% endfor %}
</li>
{% endif %}
A click on a tag should trigger a Ajax request with the clicked tag as querystring parameter. Edit taggedsnippetchooser/choose.js
just above ajaxifyLinks(modal.body);
insert:
function fetchResults(requestData) {
$.ajax({
url: searchUrl,
data: requestData,
success: function(data, status) {
$('#search-results').html(data);
ajaxifyLinks($('#search-results'));
}
});
}
$('a.suggested-tag').on('click', function() {
event.preventDefault();
var currentTag = $(this).text();
$('#id_q').val('');
fetchResults({
tag: currentTag,
results: 'true',
});
return false;
});
Done!
Now we can add tags to organisation snippets, search and filter organisations in the SnippetChooser. Pretty sweet right?
