0
# REST Framework Integration
1
2
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.
3
4
## Capabilities
5
6
### TagListSerializerField
7
8
A specialized serializer field for handling tag lists in REST API endpoints.
9
10
```python { .api }
11
class TagListSerializerField(serializers.ListField):
12
"""
13
Serializer field for handling tag lists.
14
15
Supports both string and list input formats, with optional
16
pretty printing and custom ordering.
17
"""
18
child = serializers.CharField()
19
20
def __init__(self, **kwargs):
21
"""
22
Initialize the field.
23
24
Parameters:
25
- pretty_print (bool): Enable pretty JSON formatting
26
- style (dict): Widget style options
27
- **kwargs: Additional field options
28
"""
29
30
def to_internal_value(self, value):
31
"""
32
Convert input to internal representation.
33
34
Parameters:
35
- value: String (JSON) or list of tag names
36
37
Returns:
38
list: List of tag name strings
39
40
Raises:
41
ValidationError: If input format is invalid
42
"""
43
44
def to_representation(self, value):
45
"""
46
Convert internal value to JSON representation.
47
48
Parameters:
49
- value: Tag manager or list of tags
50
51
Returns:
52
TagList: Formatted list of tag names
53
"""
54
```
55
56
### TaggitSerializer
57
58
A mixin serializer for models that use TaggableManager fields.
59
60
```python { .api }
61
class TaggitSerializer(serializers.Serializer):
62
"""
63
Mixin serializer for handling tagged models.
64
65
Automatically handles TagListSerializerField instances
66
during create and update operations.
67
"""
68
69
def create(self, validated_data):
70
"""
71
Create instance with tag handling.
72
73
Parameters:
74
- validated_data (dict): Validated serializer data
75
76
Returns:
77
Model instance with tags applied
78
"""
79
80
def update(self, instance, validated_data):
81
"""
82
Update instance with tag handling.
83
84
Parameters:
85
- instance: Model instance to update
86
- validated_data (dict): Validated serializer data
87
88
Returns:
89
Updated model instance with tags applied
90
"""
91
```
92
93
### TagList
94
95
A specialized list class for tag representation with pretty printing support.
96
97
```python { .api }
98
class TagList(list):
99
"""
100
List subclass with pretty printing support.
101
102
Enhances regular Python lists with JSON formatting
103
capabilities for API responses.
104
"""
105
106
def __init__(self, *args, pretty_print=True, **kwargs):
107
"""
108
Initialize TagList.
109
110
Parameters:
111
- pretty_print (bool): Enable formatted JSON output
112
"""
113
114
def __str__(self):
115
"""
116
String representation with optional pretty printing.
117
118
Returns:
119
str: JSON-formatted string representation
120
"""
121
```
122
123
## Basic Serializer Integration
124
125
### Simple Model Serializer
126
127
Basic integration with a model that has TaggableManager fields.
128
129
```python
130
from rest_framework import serializers
131
from taggit.serializers import TaggitSerializer, TagListSerializerField
132
from myapp.models import Article
133
134
class ArticleSerializer(TaggitSerializer, serializers.ModelSerializer):
135
tags = TagListSerializerField()
136
137
class Meta:
138
model = Article
139
fields = ['id', 'title', 'content', 'tags', 'created_at']
140
```
141
142
### Multiple Tag Fields
143
144
Handling models with multiple TaggableManager fields.
145
146
```python
147
class BlogPostSerializer(TaggitSerializer, serializers.ModelSerializer):
148
tags = TagListSerializerField()
149
categories = TagListSerializerField()
150
151
class Meta:
152
model = BlogPost
153
fields = ['id', 'title', 'content', 'tags', 'categories', 'published_at']
154
155
# Usage in views
156
class BlogPostViewSet(viewsets.ModelViewSet):
157
queryset = BlogPost.objects.all()
158
serializer_class = BlogPostSerializer
159
160
def get_queryset(self):
161
# Optimize queries with prefetch_related
162
return super().get_queryset().prefetch_related('tags', 'categories')
163
```
164
165
### Custom Field Configuration
166
167
Customizing TagListSerializerField behavior.
168
169
```python
170
class CustomArticleSerializer(TaggitSerializer, serializers.ModelSerializer):
171
tags = TagListSerializerField(
172
required=False,
173
allow_empty=True,
174
help_text="List of tags for this article"
175
)
176
177
# Custom ordering for tags
178
ordered_tags = TagListSerializerField(
179
source='tags',
180
order_by=['name']
181
)
182
183
class Meta:
184
model = Article
185
fields = ['id', 'title', 'content', 'tags', 'created_at']
186
```
187
188
## API Input/Output Formats
189
190
### JSON Input Formats
191
192
Different ways to send tag data to the API.
193
194
```python
195
# Example API requests
196
import requests
197
198
# List format (preferred)
199
data = {
200
'title': 'My Article',
201
'content': 'Article content...',
202
'tags': ['python', 'django', 'tutorial']
203
}
204
205
# String format (also supported)
206
data = {
207
'title': 'My Article',
208
'content': 'Article content...',
209
'tags': '["python", "django", "tutorial"]'
210
}
211
212
response = requests.post('/api/articles/', json=data)
213
```
214
215
### JSON Output Format
216
217
How tags appear in API responses.
218
219
```json
220
{
221
"id": 1,
222
"title": "My Article",
223
"content": "Article content...",
224
"tags": ["python", "django", "tutorial"],
225
"created_at": "2024-01-15T10:30:00Z"
226
}
227
```
228
229
## Advanced Serializer Patterns
230
231
### Nested Tag Information
232
233
Including additional tag information in API responses.
234
235
```python
236
class TagSerializer(serializers.ModelSerializer):
237
class Meta:
238
model = Tag
239
fields = ['name', 'slug']
240
241
class DetailedArticleSerializer(TaggitSerializer, serializers.ModelSerializer):
242
tags = TagSerializer(many=True, read_only=True)
243
tag_names = TagListSerializerField(write_only=True)
244
245
class Meta:
246
model = Article
247
fields = ['id', 'title', 'content', 'tags', 'tag_names', 'created_at']
248
249
def create(self, validated_data):
250
tag_names = validated_data.pop('tag_names', [])
251
instance = super().create(validated_data)
252
instance.tags.set(tag_names)
253
return instance
254
255
def update(self, instance, validated_data):
256
tag_names = validated_data.pop('tag_names', None)
257
instance = super().update(instance, validated_data)
258
if tag_names is not None:
259
instance.tags.set(tag_names)
260
return instance
261
```
262
263
### Tag Filtering and Search
264
265
Implementing tag-based filtering in API views.
266
267
```python
268
from django_filters import rest_framework as filters
269
from rest_framework import viewsets
270
271
class ArticleFilter(filters.FilterSet):
272
tags = filters.CharFilter(method='filter_by_tags')
273
has_tag = filters.CharFilter(field_name='tags__name', lookup_expr='iexact')
274
275
def filter_by_tags(self, queryset, name, value):
276
"""Filter by multiple tags (comma-separated)."""
277
tag_names = [tag.strip() for tag in value.split(',')]
278
return queryset.filter(tags__name__in=tag_names).distinct()
279
280
class Meta:
281
model = Article
282
fields = ['tags', 'has_tag']
283
284
class ArticleViewSet(viewsets.ModelViewSet):
285
queryset = Article.objects.all()
286
serializer_class = ArticleSerializer
287
filterset_class = ArticleFilter
288
search_fields = ['title', 'content', 'tags__name']
289
290
def get_queryset(self):
291
return super().get_queryset().prefetch_related('tags')
292
```
293
294
### Tag Statistics in APIs
295
296
Providing tag usage statistics through API endpoints.
297
298
```python
299
from django.db.models import Count
300
from rest_framework.decorators import action
301
from rest_framework.response import Response
302
303
class TagViewSet(viewsets.ReadOnlyModelViewSet):
304
queryset = Tag.objects.all()
305
serializer_class = TagSerializer
306
307
@action(detail=False, methods=['get'])
308
def popular(self, request):
309
"""Get most popular tags."""
310
popular_tags = Tag.objects.annotate(
311
usage_count=Count('tagged_items')
312
).filter(usage_count__gt=0).order_by('-usage_count')[:10]
313
314
data = [
315
{
316
'name': tag.name,
317
'slug': tag.slug,
318
'usage_count': tag.usage_count
319
}
320
for tag in popular_tags
321
]
322
return Response(data)
323
324
@action(detail=True, methods=['get'])
325
def articles(self, request, pk=None):
326
"""Get articles for a specific tag."""
327
tag = self.get_object()
328
articles = Article.objects.filter(tags=tag)
329
serializer = ArticleSerializer(articles, many=True)
330
return Response(serializer.data)
331
332
class ArticleViewSet(viewsets.ModelViewSet):
333
queryset = Article.objects.all()
334
serializer_class = ArticleSerializer
335
336
@action(detail=False, methods=['get'])
337
def by_tag(self, request):
338
"""Get articles grouped by tags."""
339
tag_name = request.query_params.get('tag')
340
if not tag_name:
341
return Response({'error': 'tag parameter required'}, status=400)
342
343
articles = Article.objects.filter(tags__name__iexact=tag_name)
344
serializer = self.get_serializer(articles, many=True)
345
return Response({
346
'tag': tag_name,
347
'count': articles.count(),
348
'articles': serializer.data
349
})
350
```
351
352
### Validation and Error Handling
353
354
Custom validation for tag input in serializers.
355
356
```python
357
from rest_framework import serializers
358
from django.core.exceptions import ValidationError
359
360
class ValidatedArticleSerializer(TaggitSerializer, serializers.ModelSerializer):
361
tags = TagListSerializerField()
362
363
class Meta:
364
model = Article
365
fields = ['id', 'title', 'content', 'tags']
366
367
def validate_tags(self, value):
368
"""Custom tag validation."""
369
if len(value) > 10:
370
raise serializers.ValidationError("Maximum 10 tags allowed")
371
372
for tag in value:
373
if len(tag) > 50:
374
raise serializers.ValidationError(f"Tag '{tag}' is too long (max 50 characters)")
375
376
if not tag.replace(' ', '').replace('-', '').isalnum():
377
raise serializers.ValidationError(f"Tag '{tag}' contains invalid characters")
378
379
return value
380
381
def validate(self, data):
382
"""Cross-field validation."""
383
tags = data.get('tags', [])
384
title = data.get('title', '')
385
386
# Ensure title doesn't conflict with tags
387
if title.lower() in [tag.lower() for tag in tags]:
388
raise serializers.ValidationError("Title cannot be the same as a tag")
389
390
return data
391
```
392
393
### Permissions and Access Control
394
395
Implementing tag-based permissions in API views.
396
397
```python
398
from rest_framework.permissions import BasePermission
399
400
class TagPermission(BasePermission):
401
"""Custom permission for tag operations."""
402
403
def has_permission(self, request, view):
404
if request.method in ['GET', 'HEAD', 'OPTIONS']:
405
return True
406
return request.user.is_authenticated
407
408
def has_object_permission(self, request, view, obj):
409
if request.method in ['GET', 'HEAD', 'OPTIONS']:
410
return True
411
412
# Only allow editing of own articles
413
return obj.author == request.user
414
415
class ArticleViewSet(viewsets.ModelViewSet):
416
queryset = Article.objects.all()
417
serializer_class = ArticleSerializer
418
permission_classes = [TagPermission]
419
420
def perform_create(self, serializer):
421
serializer.save(author=self.request.user)
422
```
423
424
### Bulk Operations
425
426
Handling bulk tag operations through the API.
427
428
```python
429
from rest_framework.decorators import action
430
from rest_framework.response import Response
431
from rest_framework import status
432
433
class ArticleViewSet(viewsets.ModelViewSet):
434
queryset = Article.objects.all()
435
serializer_class = ArticleSerializer
436
437
@action(detail=False, methods=['post'])
438
def bulk_tag(self, request):
439
"""Add tags to multiple articles."""
440
article_ids = request.data.get('article_ids', [])
441
tags = request.data.get('tags', [])
442
443
if not article_ids or not tags:
444
return Response(
445
{'error': 'article_ids and tags are required'},
446
status=status.HTTP_400_BAD_REQUEST
447
)
448
449
articles = Article.objects.filter(id__in=article_ids)
450
updated_count = 0
451
452
for article in articles:
453
article.tags.add(*tags)
454
updated_count += 1
455
456
return Response({
457
'updated_count': updated_count,
458
'tags_added': tags
459
})
460
461
@action(detail=False, methods=['post'])
462
def bulk_untag(self, request):
463
"""Remove tags from multiple articles."""
464
article_ids = request.data.get('article_ids', [])
465
tags = request.data.get('tags', [])
466
467
articles = Article.objects.filter(id__in=article_ids)
468
updated_count = 0
469
470
for article in articles:
471
article.tags.remove(*tags)
472
updated_count += 1
473
474
return Response({
475
'updated_count': updated_count,
476
'tags_removed': tags
477
})
478
```