A reusable Django application for simple tagging with comprehensive manager and form support.
—
Django admin integration for managing tags and tagged items. Django-taggit provides comprehensive admin interfaces for tag management, including search functionality, tag merging capabilities, and inline editing of tagged items.
Admin interface for managing Tag objects with search, filtering, and bulk operations.
class TagAdmin(admin.ModelAdmin):
"""
Admin interface for Tag model.
Features:
- List display with name and slug
- Search functionality by tag name
- Alphabetical ordering
- Prepopulated slug field
- Tag merging action
"""
list_display = ["name", "slug"]
ordering = ["name", "slug"]
search_fields = ["name"]
prepopulated_fields = {"slug": ["name"]}
actions = ["render_tag_form"]
def get_urls(self):
"""Add custom URLs for tag management."""
def render_tag_form(self, request, queryset):
"""Admin action to merge selected tags."""
def merge_tags_view(self, request):
"""View for handling tag merge operations."""# Register the Tag admin (automatically done by django-taggit)
from django.contrib import admin
from taggit.models import Tag
from taggit.admin import TagAdmin
admin.site.register(Tag, TagAdmin)Inline admin for managing TaggedItem relationships within other model admin interfaces.
class TaggedItemInline(admin.StackedInline):
"""
Inline admin for TaggedItem model.
Allows editing tag relationships directly within
the admin interface of tagged models.
"""
model = TaggedItemfrom django.contrib import admin
from taggit.admin import TaggedItemInline
from myapp.models import Article
class ArticleAdmin(admin.ModelAdmin):
list_display = ['title', 'created_at']
inlines = [TaggedItemInline]
admin.site.register(Article, ArticleAdmin)Integrating tagging functionality into your model admin interfaces.
from django.contrib import admin
from taggit.models import Tag
class ArticleAdmin(admin.ModelAdmin):
list_display = ['title', 'author', 'created_at', 'tag_list']
list_filter = ['created_at', 'tags']
search_fields = ['title', 'content', 'tags__name']
def tag_list(self, obj):
"""Display tags in list view."""
return ', '.join([tag.name for tag in obj.tags.all()])
tag_list.short_description = 'Tags'
def get_queryset(self, request):
"""Optimize queries for tag display."""
return super().get_queryset(request).prefetch_related('tags')
admin.site.register(Article, ArticleAdmin)Built-in functionality for merging multiple tags into a single tag.
class MergeTagsForm(forms.Form):
"""
Form for merging multiple tags into one.
Used in the admin interface to combine duplicate
or related tags into a single tag.
"""
new_tag_name = forms.CharField(
label="New Tag Name",
max_length=100,
help_text="Enter new or existing tag name"
)The tag merging process:
Custom admin configurations for enhanced tag management.
from django.contrib import admin
from django.db.models import Count
from taggit.models import Tag, TaggedItem
class TaggedItemAdmin(admin.ModelAdmin):
list_display = ['tag', 'content_type', 'object_id', 'content_object']
list_filter = ['content_type', 'tag']
search_fields = ['tag__name']
raw_id_fields = ['tag']
class CustomTagAdmin(admin.ModelAdmin):
list_display = ['name', 'slug', 'usage_count', 'created_at']
list_filter = ['created_at']
search_fields = ['name', 'slug']
readonly_fields = ['slug', 'usage_count']
ordering = ['-usage_count', 'name']
def usage_count(self, obj):
"""Show how many times tag is used."""
return obj.tagged_items.count()
usage_count.short_description = 'Usage Count'
usage_count.admin_order_field = 'tagged_items__count'
def get_queryset(self, request):
"""Annotate with usage count for sorting."""
return super().get_queryset(request).annotate(
usage_count=Count('tagged_items')
)
# Register custom admin
admin.site.unregister(Tag)
admin.site.register(Tag, CustomTagAdmin)
admin.site.register(TaggedItem, TaggedItemAdmin)Advanced filtering and search capabilities in the admin interface.
from django.contrib import admin
from django.db.models import Q
class TagFilter(admin.SimpleListFilter):
title = 'Tag'
parameter_name = 'tag'
def lookups(self, request, model_admin):
"""Provide tag options for filtering."""
tags = Tag.objects.all().order_by('name')[:50] # Limit for performance
return [(tag.id, tag.name) for tag in tags]
def queryset(self, request, queryset):
"""Filter queryset by selected tag."""
if self.value():
return queryset.filter(tags__id=self.value())
return queryset
class PopularTagFilter(admin.SimpleListFilter):
title = 'Tag Popularity'
parameter_name = 'popularity'
def lookups(self, request, model_admin):
return [
('popular', 'Popular (10+ uses)'),
('moderate', 'Moderate (5-9 uses)'),
('rare', 'Rare (1-4 uses)'),
('unused', 'Unused'),
]
def queryset(self, request, queryset):
from django.db.models import Count
if self.value() == 'popular':
return queryset.annotate(
usage=Count('tagged_items')
).filter(usage__gte=10)
elif self.value() == 'moderate':
return queryset.annotate(
usage=Count('tagged_items')
).filter(usage__range=(5, 9))
elif self.value() == 'rare':
return queryset.annotate(
usage=Count('tagged_items')
).filter(usage__range=(1, 4))
elif self.value() == 'unused':
return queryset.filter(tagged_items__isnull=True)
return queryset
class ArticleAdmin(admin.ModelAdmin):
list_display = ['title', 'author', 'tag_list']
list_filter = [TagFilter, 'created_at']
search_fields = ['title', 'content', 'tags__name']
def tag_list(self, obj):
return ', '.join([tag.name for tag in obj.tags.all()[:3]])
tag_list.short_description = 'Tags'Implementing bulk operations for tag management in the admin.
from django.contrib import admin, messages
from django.shortcuts import redirect
class BulkTagAdmin(admin.ModelAdmin):
actions = ['add_tag_to_selected', 'remove_tag_from_selected', 'clear_tags_from_selected']
def add_tag_to_selected(self, request, queryset):
"""Add a specific tag to selected objects."""
tag_name = "bulk-processed" # Could be made configurable
count = 0
for obj in queryset:
obj.tags.add(tag_name)
count += 1
messages.success(request, f'Added tag "{tag_name}" to {count} objects.')
add_tag_to_selected.short_description = 'Add "bulk-processed" tag to selected items'
def remove_tag_from_selected(self, request, queryset):
"""Remove a specific tag from selected objects."""
tag_name = "draft"
count = 0
for obj in queryset:
obj.tags.remove(tag_name)
count += 1
messages.success(request, f'Removed tag "{tag_name}" from {count} objects.')
remove_tag_from_selected.short_description = 'Remove "draft" tag from selected items'
def clear_tags_from_selected(self, request, queryset):
"""Clear all tags from selected objects."""
count = 0
for obj in queryset:
obj.tags.clear()
count += 1
messages.success(request, f'Cleared all tags from {count} objects.')
clear_tags_from_selected.short_description = 'Clear all tags from selected items'
class ArticleAdmin(BulkTagAdmin):
list_display = ['title', 'author', 'tag_list']
# ... other configurationsCustomizing admin templates for enhanced tag management interfaces.
<!-- admin/taggit/tag/change_list.html -->
{% extends "admin/change_list.html" %}
{% block extrahead %}
{{ block.super }}
<style>
.tag-usage-high { background-color: #d4edda; }
.tag-usage-medium { background-color: #fff3cd; }
.tag-usage-low { background-color: #f8d7da; }
</style>
{% endblock %}
{% block result_list %}
<script>
// Add visual indicators for tag usage
document.addEventListener('DOMContentLoaded', function() {
const rows = document.querySelectorAll('tbody tr');
rows.forEach(function(row) {
const usageCell = row.querySelector('td:nth-child(3)');
if (usageCell) {
const usage = parseInt(usageCell.textContent);
if (usage >= 10) {
row.classList.add('tag-usage-high');
} else if (usage >= 5) {
row.classList.add('tag-usage-medium');
} else if (usage > 0) {
row.classList.add('tag-usage-low');
}
}
});
});
</script>
{{ block.super }}
{% endblock %}Creating a custom admin view for tag statistics and management.
from django.contrib import admin
from django.urls import path
from django.shortcuts import render
from django.db.models import Count
from taggit.models import Tag
class TagStatsAdmin(admin.ModelAdmin):
change_list_template = 'admin/tag_stats.html'
def changelist_view(self, request, extra_context=None):
# Tag statistics
tag_stats = Tag.objects.annotate(
usage_count=Count('tagged_items')
).order_by('-usage_count')
popular_tags = tag_stats[:10]
unused_tags = tag_stats.filter(usage_count=0)
extra_context = extra_context or {}
extra_context.update({
'popular_tags': popular_tags,
'unused_tags_count': unused_tags.count(),
'total_tags': tag_stats.count(),
})
return super().changelist_view(request, extra_context=extra_context)
# Register with custom admin
admin.site.unregister(Tag)
admin.site.register(Tag, TagStatsAdmin)<!-- templates/admin/tag_stats.html -->
{% extends "admin/change_list.html" %}
{% block content_title %}
<h1>Tag Statistics</h1>
{% endblock %}
{% block result_list %}
<div class="results">
<div class="stats-summary">
<h2>Summary</h2>
<p>Total Tags: {{ total_tags }}</p>
<p>Unused Tags: {{ unused_tags_count }}</p>
</div>
<div class="popular-tags">
<h2>Most Popular Tags</h2>
<table>
<thead>
<tr>
<th>Tag</th>
<th>Usage Count</th>
</tr>
</thead>
<tbody>
{% for tag in popular_tags %}
<tr>
<td><a href="{% url 'admin:taggit_tag_change' tag.pk %}">{{ tag.name }}</a></td>
<td>{{ tag.usage_count }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{{ block.super }}
{% endblock %}Managing permissions for tag administration.
from django.contrib import admin
from django.contrib.auth.decorators import permission_required
class SecureTagAdmin(admin.ModelAdmin):
def has_delete_permission(self, request, obj=None):
"""Restrict tag deletion to superusers."""
return request.user.is_superuser
def has_change_permission(self, request, obj=None):
"""Allow tag editing for users with change permission."""
return request.user.has_perm('taggit.change_tag')
def get_queryset(self, request):
"""Filter tags based on user permissions."""
qs = super().get_queryset(request)
if not request.user.is_superuser:
# Regular users can only see tags they've created
# (requires custom Tag model with created_by field)
pass
return qs
# Custom permissions in models.py
class CustomTag(Tag):
created_by = models.ForeignKey('auth.User', on_delete=models.CASCADE)
class Meta:
permissions = [
('can_merge_tags', 'Can merge tags'),
('can_bulk_delete_tags', 'Can bulk delete tags'),
]Install with Tessl CLI
npx tessl i tessl/pypi-django-taggit