A reusable Django application for simple tagging with comprehensive manager and form support.
—
Django REST framework serializers and fields for handling tags in API endpoints. Django-taggit provides comprehensive integration with Django REST Framework, enabling seamless serialization and deserialization of tags in API responses and requests.
A specialized serializer field for handling tag lists in REST API endpoints.
class TagListSerializerField(serializers.ListField):
"""
Serializer field for handling tag lists.
Supports both string and list input formats, with optional
pretty printing and custom ordering.
"""
child = serializers.CharField()
def __init__(self, **kwargs):
"""
Initialize the field.
Parameters:
- pretty_print (bool): Enable pretty JSON formatting
- style (dict): Widget style options
- **kwargs: Additional field options
"""
def to_internal_value(self, value):
"""
Convert input to internal representation.
Parameters:
- value: String (JSON) or list of tag names
Returns:
list: List of tag name strings
Raises:
ValidationError: If input format is invalid
"""
def to_representation(self, value):
"""
Convert internal value to JSON representation.
Parameters:
- value: Tag manager or list of tags
Returns:
TagList: Formatted list of tag names
"""A mixin serializer for models that use TaggableManager fields.
class TaggitSerializer(serializers.Serializer):
"""
Mixin serializer for handling tagged models.
Automatically handles TagListSerializerField instances
during create and update operations.
"""
def create(self, validated_data):
"""
Create instance with tag handling.
Parameters:
- validated_data (dict): Validated serializer data
Returns:
Model instance with tags applied
"""
def update(self, instance, validated_data):
"""
Update instance with tag handling.
Parameters:
- instance: Model instance to update
- validated_data (dict): Validated serializer data
Returns:
Updated model instance with tags applied
"""A specialized list class for tag representation with pretty printing support.
class TagList(list):
"""
List subclass with pretty printing support.
Enhances regular Python lists with JSON formatting
capabilities for API responses.
"""
def __init__(self, *args, pretty_print=True, **kwargs):
"""
Initialize TagList.
Parameters:
- pretty_print (bool): Enable formatted JSON output
"""
def __str__(self):
"""
String representation with optional pretty printing.
Returns:
str: JSON-formatted string representation
"""Basic integration with a model that has TaggableManager fields.
from rest_framework import serializers
from taggit.serializers import TaggitSerializer, TagListSerializerField
from myapp.models import Article
class ArticleSerializer(TaggitSerializer, serializers.ModelSerializer):
tags = TagListSerializerField()
class Meta:
model = Article
fields = ['id', 'title', 'content', 'tags', 'created_at']Handling models with multiple TaggableManager fields.
class BlogPostSerializer(TaggitSerializer, serializers.ModelSerializer):
tags = TagListSerializerField()
categories = TagListSerializerField()
class Meta:
model = BlogPost
fields = ['id', 'title', 'content', 'tags', 'categories', 'published_at']
# Usage in views
class BlogPostViewSet(viewsets.ModelViewSet):
queryset = BlogPost.objects.all()
serializer_class = BlogPostSerializer
def get_queryset(self):
# Optimize queries with prefetch_related
return super().get_queryset().prefetch_related('tags', 'categories')Customizing TagListSerializerField behavior.
class CustomArticleSerializer(TaggitSerializer, serializers.ModelSerializer):
tags = TagListSerializerField(
required=False,
allow_empty=True,
help_text="List of tags for this article"
)
# Custom ordering for tags
ordered_tags = TagListSerializerField(
source='tags',
order_by=['name']
)
class Meta:
model = Article
fields = ['id', 'title', 'content', 'tags', 'created_at']Different ways to send tag data to the API.
# Example API requests
import requests
# List format (preferred)
data = {
'title': 'My Article',
'content': 'Article content...',
'tags': ['python', 'django', 'tutorial']
}
# String format (also supported)
data = {
'title': 'My Article',
'content': 'Article content...',
'tags': '["python", "django", "tutorial"]'
}
response = requests.post('/api/articles/', json=data)How tags appear in API responses.
{
"id": 1,
"title": "My Article",
"content": "Article content...",
"tags": ["python", "django", "tutorial"],
"created_at": "2024-01-15T10:30:00Z"
}Including additional tag information in API responses.
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = ['name', 'slug']
class DetailedArticleSerializer(TaggitSerializer, serializers.ModelSerializer):
tags = TagSerializer(many=True, read_only=True)
tag_names = TagListSerializerField(write_only=True)
class Meta:
model = Article
fields = ['id', 'title', 'content', 'tags', 'tag_names', 'created_at']
def create(self, validated_data):
tag_names = validated_data.pop('tag_names', [])
instance = super().create(validated_data)
instance.tags.set(tag_names)
return instance
def update(self, instance, validated_data):
tag_names = validated_data.pop('tag_names', None)
instance = super().update(instance, validated_data)
if tag_names is not None:
instance.tags.set(tag_names)
return instanceImplementing tag-based filtering in API views.
from django_filters import rest_framework as filters
from rest_framework import viewsets
class ArticleFilter(filters.FilterSet):
tags = filters.CharFilter(method='filter_by_tags')
has_tag = filters.CharFilter(field_name='tags__name', lookup_expr='iexact')
def filter_by_tags(self, queryset, name, value):
"""Filter by multiple tags (comma-separated)."""
tag_names = [tag.strip() for tag in value.split(',')]
return queryset.filter(tags__name__in=tag_names).distinct()
class Meta:
model = Article
fields = ['tags', 'has_tag']
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
filterset_class = ArticleFilter
search_fields = ['title', 'content', 'tags__name']
def get_queryset(self):
return super().get_queryset().prefetch_related('tags')Providing tag usage statistics through API endpoints.
from django.db.models import Count
from rest_framework.decorators import action
from rest_framework.response import Response
class TagViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Tag.objects.all()
serializer_class = TagSerializer
@action(detail=False, methods=['get'])
def popular(self, request):
"""Get most popular tags."""
popular_tags = Tag.objects.annotate(
usage_count=Count('tagged_items')
).filter(usage_count__gt=0).order_by('-usage_count')[:10]
data = [
{
'name': tag.name,
'slug': tag.slug,
'usage_count': tag.usage_count
}
for tag in popular_tags
]
return Response(data)
@action(detail=True, methods=['get'])
def articles(self, request, pk=None):
"""Get articles for a specific tag."""
tag = self.get_object()
articles = Article.objects.filter(tags=tag)
serializer = ArticleSerializer(articles, many=True)
return Response(serializer.data)
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
@action(detail=False, methods=['get'])
def by_tag(self, request):
"""Get articles grouped by tags."""
tag_name = request.query_params.get('tag')
if not tag_name:
return Response({'error': 'tag parameter required'}, status=400)
articles = Article.objects.filter(tags__name__iexact=tag_name)
serializer = self.get_serializer(articles, many=True)
return Response({
'tag': tag_name,
'count': articles.count(),
'articles': serializer.data
})Custom validation for tag input in serializers.
from rest_framework import serializers
from django.core.exceptions import ValidationError
class ValidatedArticleSerializer(TaggitSerializer, serializers.ModelSerializer):
tags = TagListSerializerField()
class Meta:
model = Article
fields = ['id', 'title', 'content', 'tags']
def validate_tags(self, value):
"""Custom tag validation."""
if len(value) > 10:
raise serializers.ValidationError("Maximum 10 tags allowed")
for tag in value:
if len(tag) > 50:
raise serializers.ValidationError(f"Tag '{tag}' is too long (max 50 characters)")
if not tag.replace(' ', '').replace('-', '').isalnum():
raise serializers.ValidationError(f"Tag '{tag}' contains invalid characters")
return value
def validate(self, data):
"""Cross-field validation."""
tags = data.get('tags', [])
title = data.get('title', '')
# Ensure title doesn't conflict with tags
if title.lower() in [tag.lower() for tag in tags]:
raise serializers.ValidationError("Title cannot be the same as a tag")
return dataImplementing tag-based permissions in API views.
from rest_framework.permissions import BasePermission
class TagPermission(BasePermission):
"""Custom permission for tag operations."""
def has_permission(self, request, view):
if request.method in ['GET', 'HEAD', 'OPTIONS']:
return True
return request.user.is_authenticated
def has_object_permission(self, request, view, obj):
if request.method in ['GET', 'HEAD', 'OPTIONS']:
return True
# Only allow editing of own articles
return obj.author == request.user
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
permission_classes = [TagPermission]
def perform_create(self, serializer):
serializer.save(author=self.request.user)Handling bulk tag operations through the API.
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework import status
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
@action(detail=False, methods=['post'])
def bulk_tag(self, request):
"""Add tags to multiple articles."""
article_ids = request.data.get('article_ids', [])
tags = request.data.get('tags', [])
if not article_ids or not tags:
return Response(
{'error': 'article_ids and tags are required'},
status=status.HTTP_400_BAD_REQUEST
)
articles = Article.objects.filter(id__in=article_ids)
updated_count = 0
for article in articles:
article.tags.add(*tags)
updated_count += 1
return Response({
'updated_count': updated_count,
'tags_added': tags
})
@action(detail=False, methods=['post'])
def bulk_untag(self, request):
"""Remove tags from multiple articles."""
article_ids = request.data.get('article_ids', [])
tags = request.data.get('tags', [])
articles = Article.objects.filter(id__in=article_ids)
updated_count = 0
for article in articles:
article.tags.remove(*tags)
updated_count += 1
return Response({
'updated_count': updated_count,
'tags_removed': tags
})Install with Tessl CLI
npx tessl i tessl/pypi-django-taggit