A reusable Django application for simple tagging with comprehensive manager and form support.
—
Generic view components for creating tag-filtered list views and displaying objects by tag. Django-taggit provides utilities for implementing tag-based filtering in both function-based and class-based views.
Generic view function for listing objects filtered by a specific tag.
def tagged_object_list(request, slug, queryset, **kwargs):
"""
Generic view function for listing objects with specific tag.
Parameters:
- request: HTTP request object
- slug (str): Tag slug to filter by
- queryset: Model queryset to filter
- **kwargs: Additional view parameters passed to generic view
Returns:
HttpResponse: Rendered template with filtered object list
Raises:
Http404: If tag with given slug does not exist
"""from django.shortcuts import render
from taggit.views import tagged_object_list
from myapp.models import Article
def articles_by_tag(request, slug):
return tagged_object_list(
request,
slug,
Article.objects.all(),
template_name='articles/tagged_list.html',
context_object_name='articles'
)Mixin for class-based views that adds tag-based filtering functionality.
class TagListMixin:
"""
Mixin for views filtering objects by tag.
Provides tag-based filtering functionality for class-based views,
particularly ListView, with automatic template name resolution
and context enhancement.
"""
tag_suffix = "_tag"
def dispatch(self, request, *args, **kwargs):
"""
Handle request dispatch with tag filtering setup.
Parameters:
- request: HTTP request object
- *args: Positional arguments
- **kwargs: Keyword arguments including 'slug' for tag
Returns:
HttpResponse: Response from parent dispatch method
Raises:
Http404: If tag with given slug does not exist
"""
def get_queryset(self, **kwargs):
"""
Return queryset filtered by tag.
Parameters:
- **kwargs: Additional filtering parameters
Returns:
QuerySet: Filtered queryset containing only objects with the tag
"""
def get_template_names(self):
"""
Return template names with tag-specific variants.
Adds template names with tag suffix to support
tag-specific templates while maintaining fallbacks.
Returns:
list: List of template names to try
"""
def get_context_data(self, **kwargs):
"""
Add tag context to template.
Parameters:
- **kwargs: Existing context data
Returns:
dict: Context dictionary with added 'tag' variable
"""Simple implementation using the tagged_object_list function.
from django.urls import path
from taggit.views import tagged_object_list
from myapp.models import BlogPost
# In urls.py
urlpatterns = [
path('posts/tag/<slug:slug>/',
lambda request, slug: tagged_object_list(
request, slug, BlogPost.objects.published()
),
name='posts_by_tag'),
]
# Template: myapp/blogpost_list.html
# Context variables: object_list, tagUsing the mixin with ListView for enhanced functionality.
from django.views.generic import ListView
from taggit.views import TagListMixin
from myapp.models import Article
class ArticleTagListView(TagListMixin, ListView):
model = Article
context_object_name = 'articles'
paginate_by = 10
def get_queryset(self):
# Call parent to get tag-filtered queryset
queryset = super().get_queryset()
# Add additional filtering
return queryset.filter(published=True).order_by('-created_at')
# In urls.py
path('articles/tag/<slug:slug>/', ArticleTagListView.as_view(), name='articles_by_tag')Custom view with additional features and error handling.
from django.views.generic import ListView
from django.shortcuts import get_object_or_404
from django.db.models import Count
from taggit.models import Tag
from myapp.models import Article
class AdvancedTagView(ListView):
model = Article
template_name = 'articles/tag_detail.html'
context_object_name = 'articles'
paginate_by = 20
def dispatch(self, request, *args, **kwargs):
self.tag = get_object_or_404(Tag, slug=kwargs['slug'])
return super().dispatch(request, *args, **kwargs)
def get_queryset(self):
return Article.objects.filter(
tags=self.tag,
published=True
).select_related('author').order_by('-created_at')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'tag': self.tag,
'article_count': self.get_queryset().count(),
'related_tags': Tag.objects.filter(
article__in=self.get_queryset()
).exclude(id=self.tag.id).annotate(
usage_count=Count('article')
).order_by('-usage_count')[:10]
})
return contextView that filters by multiple tags simultaneously.
from django.views.generic import ListView
from django.db.models import Q
from taggit.models import Tag
class MultipleTagView(ListView):
model = Article
template_name = 'articles/multi_tag.html'
def get_queryset(self):
tag_slugs = self.request.GET.get('tags', '').split(',')
tag_slugs = [slug.strip() for slug in tag_slugs if slug.strip()]
if not tag_slugs:
return Article.objects.none()
# Get all specified tags
tags = Tag.objects.filter(slug__in=tag_slugs)
# Filter articles that have ALL specified tags
queryset = Article.objects.published()
for tag in tags:
queryset = queryset.filter(tags=tag)
return queryset.distinct()
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
tag_slugs = self.request.GET.get('tags', '').split(',')
context['active_tags'] = Tag.objects.filter(
slug__in=[s.strip() for s in tag_slugs if s.strip()]
)
return context
# Usage: /articles/multi-tag/?tags=python,django,tutorialView for displaying a tag cloud with usage counts.
from django.views.generic import TemplateView
from django.db.models import Count
from taggit.models import Tag
class TagCloudView(TemplateView):
template_name = 'tags/cloud.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# Get tags with usage counts
tags_with_counts = Tag.objects.annotate(
usage_count=Count('tagged_items')
).filter(usage_count__gt=0).order_by('name')
# Calculate relative sizes for cloud display
if tags_with_counts:
max_count = max(tag.usage_count for tag in tags_with_counts)
min_count = min(tag.usage_count for tag in tags_with_counts)
for tag in tags_with_counts:
if max_count == min_count:
tag.weight = 1.0
else:
tag.weight = (tag.usage_count - min_count) / (max_count - min_count)
context.update({
'tags': tags_with_counts,
'tag_count': tags_with_counts.count()
})
return contextViews for tag-related API endpoints.
from django.http import JsonResponse
from django.views.generic import View
from django.db.models import Count
from taggit.models import Tag
class TagAutocompleteView(View):
"""Autocomplete API for tag suggestions."""
def get(self, request):
query = request.GET.get('q', '').strip()
if not query:
return JsonResponse({'tags': []})
tags = Tag.objects.filter(
name__icontains=query
).annotate(
usage_count=Count('tagged_items')
).order_by('-usage_count', 'name')[:10]
data = {
'tags': [
{
'name': tag.name,
'slug': tag.slug,
'usage_count': tag.usage_count
}
for tag in tags
]
}
return JsonResponse(data)
class PopularTagsView(View):
"""API endpoint for most popular tags."""
def get(self, request):
limit = int(request.GET.get('limit', 20))
tags = Tag.objects.annotate(
usage_count=Count('tagged_items')
).filter(usage_count__gt=0).order_by('-usage_count')[:limit]
data = {
'tags': [
{
'name': tag.name,
'slug': tag.slug,
'usage_count': tag.usage_count
}
for tag in tags
]
}
return JsonResponse(data)Templates for tag-filtered views with common patterns.
<!-- articles/tagged_list.html -->
<h1>Articles tagged with "{{ tag.name }}"</h1>
<div class="tag-info">
<p>{{ articles|length }} article{{ articles|length|pluralize }} found</p>
</div>
<div class="articles">
{% for article in articles %}
<article class="article-summary">
<h2><a href="{{ article.get_absolute_url }}">{{ article.title }}</a></h2>
<p>{{ article.summary }}</p>
<div class="article-tags">
{% for article_tag in article.tags.all %}
<a href="{% url 'articles_by_tag' article_tag.slug %}"
class="tag {% if article_tag == tag %}current{% endif %}">
{{ article_tag.name }}
</a>
{% endfor %}
</div>
</article>
{% empty %}
<p>No articles found with tag "{{ tag.name }}".</p>
{% endfor %}
</div>
<!-- Pagination -->
{% if is_paginated %}
<div class="pagination">
{% if page_obj.has_previous %}
<a href="?page={{ page_obj.previous_page_number }}">Previous</a>
{% endif %}
<span class="page-info">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
</span>
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">Next</a>
{% endif %}
</div>
{% endif %}Template for displaying tag clouds with weighted sizes.
<!-- tags/cloud.html -->
<div class="tag-cloud">
<h2>Tag Cloud</h2>
<div class="cloud-container">
{% for tag in tags %}
<a href="{% url 'articles_by_tag' tag.slug %}"
class="cloud-tag"
style="font-size: {{ tag.weight|floatformat:1|add:'1' }}em;"
title="{{ tag.usage_count }} article{{ tag.usage_count|pluralize }}">
{{ tag.name }}
</a>
{% endfor %}
</div>
</div>
<style>
.tag-cloud {
margin: 2rem 0;
}
.cloud-container {
line-height: 1.8;
}
.cloud-tag {
display: inline-block;
margin: 0.2em 0.4em;
padding: 0.2em 0.4em;
background: #f0f0f0;
border-radius: 3px;
text-decoration: none;
color: #333;
transition: background 0.2s;
}
.cloud-tag:hover {
background: #e0e0e0;
}
</style>Common URL patterns for tag-based views.
# urls.py
from django.urls import path
from taggit.views import tagged_object_list
from myapp.views import (
ArticleTagListView, AdvancedTagView, MultipleTagView,
TagCloudView, TagAutocompleteView, PopularTagsView
)
from myapp.models import Article
app_name = 'tags'
urlpatterns = [
# Function-based view
path('articles/<slug:slug>/',
lambda request, slug: tagged_object_list(
request, slug, Article.objects.published()
),
name='articles_by_tag_simple'),
# Class-based views
path('articles/tag/<slug:slug>/',
ArticleTagListView.as_view(),
name='articles_by_tag'),
path('advanced/<slug:slug>/',
AdvancedTagView.as_view(),
name='advanced_tag_view'),
path('multi-tag/',
MultipleTagView.as_view(),
name='multi_tag_view'),
# Tag cloud
path('cloud/',
TagCloudView.as_view(),
name='tag_cloud'),
# API endpoints
path('api/autocomplete/',
TagAutocompleteView.as_view(),
name='tag_autocomplete'),
path('api/popular/',
PopularTagsView.as_view(),
name='popular_tags'),
]Install with Tessl CLI
npx tessl i tessl/pypi-django-taggit