[Django] 게시글에 태그 붙이기
tag를 도와줄 오픈소스를 받아온다.
- 하나의 필드에 여러 개의 태그를 추가하거나 삭제
- 태그 이름을 통해 모델 인스턴스를 검색 & 그룹화하고 집계
- 자동 완성 기능과 연동(django-taggit-autosuggest 패키지를 함께 사용)
Getting Started — django-taggit 1.3.0 documentation
Add "taggit" to your project’s INSTALLED_APPS setting. Run ./manage.py migrate. Note If you want django-taggit to be CASE-INSENSITIVE when looking up existing tags, you’ll have to set TAGGIT_CASE_INSENSITIVE (in settings.py or wherever you have your Dj
django-taggit.readthedocs.io
1. 오픈소스 다운
pip install django-taggit
pip install django-taggit-templatetags2
2. settings.py
INSTALLED_APPS = [
...
'taggit.apps.TaggitAppConfig',
'taggit_templatetags2',
...
]
TAGGIT_CASE_INSENSITIVE = True
TAGGIT_LIMIT = 50
- taggit 설정
# 대소문자 처리
TAGGIT_CASE_INSENSITIVE = True
# 태그 클라우드에서 표시할 최대 태그 수
TAGGIT_LIMIT = 50
# 태그 최대 길이(기본 50)
TAGGIT_MAX_TAG_LENGT = 30
# 빈 태그 생성을 막는다
TAGGIT_REMOVE_EMPTY = True
# 문자열에서 태그 추출 구분자(기본',')
TAGGIT_TAGS_FROM_STRING = ''
# 모델에서 태그 추출 구분자(기본',')
TAGGIT_TAGS_FROM_MODEL = ''
# 중복 태그를 허용하는가 (기본 False)
TAGGIT_ALLOW_DUPLICATES = False
3. models.py에 적용
from taggit.managers import TaggableManager
from django.urls import reverse
class Article(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
title = models.CharField(max_length=100)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
like_users = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name='like_articles')
bookmark_user = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name='bookmark_articles')
views = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name='view_articles')
# 태그를 넣어준다
tags = TaggableManager(blank=True)
# tag를 쓴 detail에 연결이 편하기 위해 위해사용
def get_absolute_url(self):
return reverse('articles:detail', args=[int(self.id)])
4. admin.py
admin.site.register(Article)
class ArticleAdmin(admin.ModelAdmin):
list_display = ('title', 'content', 'tag_list',)
list_filter = ('tag_list',)
# 검색 박스 표시, title, content 칼럼에서 검색
search_fields = ('title', 'content')
# title 필드를 사용해 미리 채워지도록
prepopulated_fields = {'slug':('title',)}
# Article 레코드 리스트를 가져오는 메소드 오버라이딩
# N:N 관계에서 쿼리 횟수를 줄여 성능 높일때 : prefetch_related
def get_queryset(self, request):
return super().get_queryset(request).prefetch_related('tags')
def tag_list(self, obj):
return ', '.join(o.name for o in obj.tags.all())
5. migrate
6. urls.py
# 태그 목록을 볼 수 있는 url
# TemplateView를 상속받아 정의
path('tag/', views.TagCloudTV.as_view(), name='tag_cloud'),
# 태그가 달린 게시글들의 목록
# ListView를 상속받아 정의
path('tag/<str:tag>/', views.TaggedObjectLV.as_view(), name='tagged_object_list')
7. views.py
create 때 tag를 받아 저장
@login_required
def create(request):
if request.method == 'POST':
form = ArticleForm(request.POST)
images = request.FILES.getlist('image')
# 태그를 , 기준으로 분리
tags = request.POST.get('tags').split(',')
if form.is_valid():
article = form.save(commit=False)
article.user = request.user
article.save()
# tags를 하나씩 article.tags에 넣어준다
for tag in tags:
article.tags.add(tag.strip())
for i in images:
ArticleImage.objects.create(article=article, image=i)
return redirect('articles:index')
else:
print(form.errors)
else:
form = ArticleForm()
imageform = ArticleImageForm()
context = {
'form': form,
'imageform' : imageform
}
return render(request, 'articles/create.html', context)
detail에 태그가 나오도록 출력
def detail(request, article_pk):
article = Article.objects.get(pk=article_pk)
# view
if request.user not in article.views.all():
article.views.add(request.user)
comment_form = ArticleCommentForm()
comments = article.articlecomment_set.all()
tags = article.tags.all()
context = {
'article': article,
'comment_form' : comment_form,
'comments' : comments,
'tags' : tags
}
return render(request, 'articles/detail.html', context)
태그목록과 태그를 쓴 게시글을 보여줄 html
from django.views.generic import ListView, TemplateView
class TagCloudTV(TemplateView):
template_name = 'taggit/taggit_cloud.html'
class TaggedObjectLV(ListView):
template_name = 'taggit/taggit_article_list.html'
model = Article
def get_queryset(self):
return Article.objects.filter(tags__name=self.kwargs.get('tag'))
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['tagname'] = self.kwargs['tag']
return context
8. templates
forms.py에서 create때 tag를 받을 수 있도록 저장
class ArticleForm(forms.ModelForm):
tags = forms.CharField(
widget=forms.TextInput(
attrs={
'class': 'form-control',
'placeholder' : '태그 사이에 쉼표를 넣어주세요.'
}
)
)
class Meta:
model = Article
fields = ('tags')
detail.html
<div>
<b>TAGS</b><br>
# taggit_templatetag2_tags 모듈을 가져온다
{% load taggit_templatetags2_tags %}
# object(artice)의 태그를 가져온다
{% get_tags_for_object article as "tags" %} <!-- ** -->
{% for tag in tags %}
<a href="{% url 'articles:tagged_object_list' tag.name %}">{{tag.name}}</a>
{% endfor %}
</div>
taggit_article_list.html
<h1>As for tag - {{ tagname }}</h1>
# object_list 객체는 TaggedObjectLV 클래스형 뷰에서 넘겨주는 컨텍스트 변수로서 Article 리스트가 담겨있음
{% for article in object_list %}
# 각 디테일 페이지로 연결
<h2><a href='{{ article.get_absolute_url }}'>{{ article.title }}</a></h2>
{{tag.num_times}}
{% endfor %}
taggit_cloud.html
{% extends "base.html" %}
{% block style %}
<style>
.tag-cloud{
width: 40%;
margin-left: 30px;
text-align: center;
padding: 5px;
border: 1px solid orange;
background-color: #ffc;
}
.tag-1 {font-size: 12px}
.tag-2 {font-size: 14px}
.tag-3 {font-size: 16px}
.tag-4 {font-size: 18px}
.tag-5 {font-size: 20px}
.tag-6 {font-size: 24px}
</style>
{% endblock style %}
{% block content %}
<article>
<h1>Article Tag Cloud</h1>
<div class="tag-cloud">
{% load taggit_templatetags2_tags %}
# 모든 태그 추출해서 tags변수에 할당
{% get_tagcloud as tags %}
{% for tag in tags %}
<span class="tag-{{tag.weight|floatformat:0}}"> <!-- style 관련-->
<a href="{% url 'articles:tagged_object_list' tag.name %}"> {{tag.name}}({{tag.num_times}})</a>
</span>
{% endfor %}
</div>
</article>
{% endblock content %}
---
Error
- NOT NULL constraint failed:
위와 같은 오류가 발생하였다. 이 경우 제약조건때문에 null 값을 넣을 수 없는건데 model에서 아무리 제약을 풀어줘도 안된다면 migrations와 DB를 깔끔하게 날리고 진행하자. 나같은 경우 그 전에 있던 데이터때문에 안됐으며 지우니 깔끔하게 진행되었다.
- 태그를 삭제한 경우
태그의 글자는 저장되어있지만, 사용수가 없어지거나 태그가 없는 게시물에서 오류가 남
태그가 빈 문자열이라도 처리할 수 있도록 tag.name을 조건을 붙여줌
{% load taggit_templatetags2_tags %}
{% get_tags_for_object article as "tags" %}
{% if tags %}
{% for tag in tags %}
{% if tag.name %}
<a href="{% url 'articles:tagged_object_list' tag.name %}">{{tag.name}}</a>
{% endif %}
{% endfor %}
{% endif %}
클라우드에서 0인 경우는 보지 않으려고 tag.num_times도 적용
{% load taggit_templatetags2_tags %}
{% get_tagcloud as tags %} <!--모든 태그 추출해서 tags변수에 할당-->
{% for tag in tags %}
<span class="tag-{{tag.weight|floatformat:0}}"> <!-- style 관련-->
{% if tag.name and tag.num_times %}
<a href="{% url 'articles:tagged_object_list' tag.name %}">{{ tag.name }}({{ tag.num_times }})</a>
{% endif %}
</span>
{% endfor %}