0
# View Helpers
1
2
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.
3
4
## Capabilities
5
6
### Function-Based View Helper
7
8
Generic view function for listing objects filtered by a specific tag.
9
10
```python { .api }
11
def tagged_object_list(request, slug, queryset, **kwargs):
12
"""
13
Generic view function for listing objects with specific tag.
14
15
Parameters:
16
- request: HTTP request object
17
- slug (str): Tag slug to filter by
18
- queryset: Model queryset to filter
19
- **kwargs: Additional view parameters passed to generic view
20
21
Returns:
22
HttpResponse: Rendered template with filtered object list
23
24
Raises:
25
Http404: If tag with given slug does not exist
26
"""
27
```
28
29
```python
30
from django.shortcuts import render
31
from taggit.views import tagged_object_list
32
from myapp.models import Article
33
34
def articles_by_tag(request, slug):
35
return tagged_object_list(
36
request,
37
slug,
38
Article.objects.all(),
39
template_name='articles/tagged_list.html',
40
context_object_name='articles'
41
)
42
```
43
44
### Class-Based View Mixin
45
46
Mixin for class-based views that adds tag-based filtering functionality.
47
48
```python { .api }
49
class TagListMixin:
50
"""
51
Mixin for views filtering objects by tag.
52
53
Provides tag-based filtering functionality for class-based views,
54
particularly ListView, with automatic template name resolution
55
and context enhancement.
56
"""
57
tag_suffix = "_tag"
58
59
def dispatch(self, request, *args, **kwargs):
60
"""
61
Handle request dispatch with tag filtering setup.
62
63
Parameters:
64
- request: HTTP request object
65
- *args: Positional arguments
66
- **kwargs: Keyword arguments including 'slug' for tag
67
68
Returns:
69
HttpResponse: Response from parent dispatch method
70
71
Raises:
72
Http404: If tag with given slug does not exist
73
"""
74
75
def get_queryset(self, **kwargs):
76
"""
77
Return queryset filtered by tag.
78
79
Parameters:
80
- **kwargs: Additional filtering parameters
81
82
Returns:
83
QuerySet: Filtered queryset containing only objects with the tag
84
"""
85
86
def get_template_names(self):
87
"""
88
Return template names with tag-specific variants.
89
90
Adds template names with tag suffix to support
91
tag-specific templates while maintaining fallbacks.
92
93
Returns:
94
list: List of template names to try
95
"""
96
97
def get_context_data(self, **kwargs):
98
"""
99
Add tag context to template.
100
101
Parameters:
102
- **kwargs: Existing context data
103
104
Returns:
105
dict: Context dictionary with added 'tag' variable
106
"""
107
```
108
109
## Usage Examples
110
111
### Basic Function-Based View
112
113
Simple implementation using the tagged_object_list function.
114
115
```python
116
from django.urls import path
117
from taggit.views import tagged_object_list
118
from myapp.models import BlogPost
119
120
# In urls.py
121
urlpatterns = [
122
path('posts/tag/<slug:slug>/',
123
lambda request, slug: tagged_object_list(
124
request, slug, BlogPost.objects.published()
125
),
126
name='posts_by_tag'),
127
]
128
129
# Template: myapp/blogpost_list.html
130
# Context variables: object_list, tag
131
```
132
133
### Class-Based View with TagListMixin
134
135
Using the mixin with ListView for enhanced functionality.
136
137
```python
138
from django.views.generic import ListView
139
from taggit.views import TagListMixin
140
from myapp.models import Article
141
142
class ArticleTagListView(TagListMixin, ListView):
143
model = Article
144
context_object_name = 'articles'
145
paginate_by = 10
146
147
def get_queryset(self):
148
# Call parent to get tag-filtered queryset
149
queryset = super().get_queryset()
150
# Add additional filtering
151
return queryset.filter(published=True).order_by('-created_at')
152
153
# In urls.py
154
path('articles/tag/<slug:slug>/', ArticleTagListView.as_view(), name='articles_by_tag')
155
```
156
157
### Advanced Tag View Implementation
158
159
Custom view with additional features and error handling.
160
161
```python
162
from django.views.generic import ListView
163
from django.shortcuts import get_object_or_404
164
from django.db.models import Count
165
from taggit.models import Tag
166
from myapp.models import Article
167
168
class AdvancedTagView(ListView):
169
model = Article
170
template_name = 'articles/tag_detail.html'
171
context_object_name = 'articles'
172
paginate_by = 20
173
174
def dispatch(self, request, *args, **kwargs):
175
self.tag = get_object_or_404(Tag, slug=kwargs['slug'])
176
return super().dispatch(request, *args, **kwargs)
177
178
def get_queryset(self):
179
return Article.objects.filter(
180
tags=self.tag,
181
published=True
182
).select_related('author').order_by('-created_at')
183
184
def get_context_data(self, **kwargs):
185
context = super().get_context_data(**kwargs)
186
context.update({
187
'tag': self.tag,
188
'article_count': self.get_queryset().count(),
189
'related_tags': Tag.objects.filter(
190
article__in=self.get_queryset()
191
).exclude(id=self.tag.id).annotate(
192
usage_count=Count('article')
193
).order_by('-usage_count')[:10]
194
})
195
return context
196
```
197
198
### Multiple Tag Filtering
199
200
View that filters by multiple tags simultaneously.
201
202
```python
203
from django.views.generic import ListView
204
from django.db.models import Q
205
from taggit.models import Tag
206
207
class MultipleTagView(ListView):
208
model = Article
209
template_name = 'articles/multi_tag.html'
210
211
def get_queryset(self):
212
tag_slugs = self.request.GET.get('tags', '').split(',')
213
tag_slugs = [slug.strip() for slug in tag_slugs if slug.strip()]
214
215
if not tag_slugs:
216
return Article.objects.none()
217
218
# Get all specified tags
219
tags = Tag.objects.filter(slug__in=tag_slugs)
220
221
# Filter articles that have ALL specified tags
222
queryset = Article.objects.published()
223
for tag in tags:
224
queryset = queryset.filter(tags=tag)
225
226
return queryset.distinct()
227
228
def get_context_data(self, **kwargs):
229
context = super().get_context_data(**kwargs)
230
tag_slugs = self.request.GET.get('tags', '').split(',')
231
context['active_tags'] = Tag.objects.filter(
232
slug__in=[s.strip() for s in tag_slugs if s.strip()]
233
)
234
return context
235
236
# Usage: /articles/multi-tag/?tags=python,django,tutorial
237
```
238
239
### Tag Cloud View
240
241
View for displaying a tag cloud with usage counts.
242
243
```python
244
from django.views.generic import TemplateView
245
from django.db.models import Count
246
from taggit.models import Tag
247
248
class TagCloudView(TemplateView):
249
template_name = 'tags/cloud.html'
250
251
def get_context_data(self, **kwargs):
252
context = super().get_context_data(**kwargs)
253
254
# Get tags with usage counts
255
tags_with_counts = Tag.objects.annotate(
256
usage_count=Count('tagged_items')
257
).filter(usage_count__gt=0).order_by('name')
258
259
# Calculate relative sizes for cloud display
260
if tags_with_counts:
261
max_count = max(tag.usage_count for tag in tags_with_counts)
262
min_count = min(tag.usage_count for tag in tags_with_counts)
263
264
for tag in tags_with_counts:
265
if max_count == min_count:
266
tag.weight = 1.0
267
else:
268
tag.weight = (tag.usage_count - min_count) / (max_count - min_count)
269
270
context.update({
271
'tags': tags_with_counts,
272
'tag_count': tags_with_counts.count()
273
})
274
return context
275
```
276
277
### API Integration Views
278
279
Views for tag-related API endpoints.
280
281
```python
282
from django.http import JsonResponse
283
from django.views.generic import View
284
from django.db.models import Count
285
from taggit.models import Tag
286
287
class TagAutocompleteView(View):
288
"""Autocomplete API for tag suggestions."""
289
290
def get(self, request):
291
query = request.GET.get('q', '').strip()
292
if not query:
293
return JsonResponse({'tags': []})
294
295
tags = Tag.objects.filter(
296
name__icontains=query
297
).annotate(
298
usage_count=Count('tagged_items')
299
).order_by('-usage_count', 'name')[:10]
300
301
data = {
302
'tags': [
303
{
304
'name': tag.name,
305
'slug': tag.slug,
306
'usage_count': tag.usage_count
307
}
308
for tag in tags
309
]
310
}
311
return JsonResponse(data)
312
313
class PopularTagsView(View):
314
"""API endpoint for most popular tags."""
315
316
def get(self, request):
317
limit = int(request.GET.get('limit', 20))
318
tags = Tag.objects.annotate(
319
usage_count=Count('tagged_items')
320
).filter(usage_count__gt=0).order_by('-usage_count')[:limit]
321
322
data = {
323
'tags': [
324
{
325
'name': tag.name,
326
'slug': tag.slug,
327
'usage_count': tag.usage_count
328
}
329
for tag in tags
330
]
331
}
332
return JsonResponse(data)
333
```
334
335
## Template Integration
336
337
### Basic Template Structure
338
339
Templates for tag-filtered views with common patterns.
340
341
```html
342
<!-- articles/tagged_list.html -->
343
<h1>Articles tagged with "{{ tag.name }}"</h1>
344
345
<div class="tag-info">
346
<p>{{ articles|length }} article{{ articles|length|pluralize }} found</p>
347
</div>
348
349
<div class="articles">
350
{% for article in articles %}
351
<article class="article-summary">
352
<h2><a href="{{ article.get_absolute_url }}">{{ article.title }}</a></h2>
353
<p>{{ article.summary }}</p>
354
<div class="article-tags">
355
{% for article_tag in article.tags.all %}
356
<a href="{% url 'articles_by_tag' article_tag.slug %}"
357
class="tag {% if article_tag == tag %}current{% endif %}">
358
{{ article_tag.name }}
359
</a>
360
{% endfor %}
361
</div>
362
</article>
363
{% empty %}
364
<p>No articles found with tag "{{ tag.name }}".</p>
365
{% endfor %}
366
</div>
367
368
<!-- Pagination -->
369
{% if is_paginated %}
370
<div class="pagination">
371
{% if page_obj.has_previous %}
372
<a href="?page={{ page_obj.previous_page_number }}">Previous</a>
373
{% endif %}
374
375
<span class="page-info">
376
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
377
</span>
378
379
{% if page_obj.has_next %}
380
<a href="?page={{ page_obj.next_page_number }}">Next</a>
381
{% endif %}
382
</div>
383
{% endif %}
384
```
385
386
### Tag Cloud Template
387
388
Template for displaying tag clouds with weighted sizes.
389
390
```html
391
<!-- tags/cloud.html -->
392
<div class="tag-cloud">
393
<h2>Tag Cloud</h2>
394
<div class="cloud-container">
395
{% for tag in tags %}
396
<a href="{% url 'articles_by_tag' tag.slug %}"
397
class="cloud-tag"
398
style="font-size: {{ tag.weight|floatformat:1|add:'1' }}em;"
399
title="{{ tag.usage_count }} article{{ tag.usage_count|pluralize }}">
400
{{ tag.name }}
401
</a>
402
{% endfor %}
403
</div>
404
</div>
405
406
<style>
407
.tag-cloud {
408
margin: 2rem 0;
409
}
410
411
.cloud-container {
412
line-height: 1.8;
413
}
414
415
.cloud-tag {
416
display: inline-block;
417
margin: 0.2em 0.4em;
418
padding: 0.2em 0.4em;
419
background: #f0f0f0;
420
border-radius: 3px;
421
text-decoration: none;
422
color: #333;
423
transition: background 0.2s;
424
}
425
426
.cloud-tag:hover {
427
background: #e0e0e0;
428
}
429
</style>
430
```
431
432
## URL Configuration
433
434
### URL Patterns
435
436
Common URL patterns for tag-based views.
437
438
```python
439
# urls.py
440
from django.urls import path
441
from taggit.views import tagged_object_list
442
from myapp.views import (
443
ArticleTagListView, AdvancedTagView, MultipleTagView,
444
TagCloudView, TagAutocompleteView, PopularTagsView
445
)
446
from myapp.models import Article
447
448
app_name = 'tags'
449
450
urlpatterns = [
451
# Function-based view
452
path('articles/<slug:slug>/',
453
lambda request, slug: tagged_object_list(
454
request, slug, Article.objects.published()
455
),
456
name='articles_by_tag_simple'),
457
458
# Class-based views
459
path('articles/tag/<slug:slug>/',
460
ArticleTagListView.as_view(),
461
name='articles_by_tag'),
462
463
path('advanced/<slug:slug>/',
464
AdvancedTagView.as_view(),
465
name='advanced_tag_view'),
466
467
path('multi-tag/',
468
MultipleTagView.as_view(),
469
name='multi_tag_view'),
470
471
# Tag cloud
472
path('cloud/',
473
TagCloudView.as_view(),
474
name='tag_cloud'),
475
476
# API endpoints
477
path('api/autocomplete/',
478
TagAutocompleteView.as_view(),
479
name='tag_autocomplete'),
480
481
path('api/popular/',
482
PopularTagsView.as_view(),
483
name='popular_tags'),
484
]
485
```