본문 바로가기

Django

[Django] Blog 앱 확장 - Tag 달기

이미 만들어 둔 블로그 앱에 태그 기능을 추가해 보겠다.

블로그의 각 포스트마다 태그를 보여주고 해당 태그를 클릭하는 경우,

그 태그를 가진 모든 포스트의 리스트를 보여준다.

 

첫번째로 setting.py에 tagging 앱을 등록해주어야 한다.

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'bookmark.apps.BookmarkConfig',
    'blog.apps.BlogConfig',
    'taggit.apps.TaggitAppConfig', #추가
    'taggit_templatetags2', #추가
]

TAGGIT_CASE_INSENSITIVE = True
TAGGIT_LIMIT = 50

TAGGIT_CASE_INSENSITIVE 옵션은 태그 이름에 대소문자를 구분하지 않는다는 설정이다.

 

models.py 수정

기존 blog/models.py 파일에 tag옵션을 위해 몇가지만 추가해주도록 하자

from django.db import models
from django.urls import reverse
from taggit.managers import TaggableManager #추가


class Post(models.Model):
    title = models.CharField(verbose_name='TITLE', max_length=50)
    slug = models.SlugField('SLUG', unique=True, allow_unicode=True, help_text='one word for title alias.')
    description = models.CharField('DESCRIPTION', max_length=100, blank=True, help_text='simple description text.')
    content = models.TextField('CONTENT')
    create_dt = models.DateTimeField('CREATE DATE', auto_now_add=True)
    modify_dt = models.DateTimeField('MODIFY DATE', auto_now=True)
    tags = TaggableManager(blank=True) #추가

 

admin.py 수정

포스트별로 태그 이름이 어드민 화면에 나타나도록 admin.py파일을 수정해야 한다.

from django.contrib import admin
from blog.models import Post


@admin.register(Post)
class PostAdmin(admin.ModelAdmin): 
    list_display  = ('id', 'title', 'modify_dt', 'tag_list') 
    list_filter   = ('modify_dt',) 
    search_fields = ('title', 'content') 
    prepopulated_fields = {'slug': ('title',)} 

    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())

Post 레코드 리스트를 가져오는 get_queryset() 메소드를 오버라이딩한다.

이유는 Post 테이블과 Tag 테이블이 ManyToMany 관계이므로, Tag 테이블의 관련 레코드를

한 번의 쿼리로 미리 가져오기 위함이다.

N:N 관계에서 쿼리 횟수를 줄여 성능을 높이고자 할 때 prefetch_related() 메소드를 사용한다.

 

다음과 같이 코드를 수정했다면 변경사항을 데이터베이스에 반영해주자

python manage.py makemigrations blog
python manage.py migrate

 

정상적으로 추가되고 admin에 표시된 것을 확인할 수 있다.

 

URLconf 수정하기

# blog/urls.py
# Example: /blog/tag/
path('tag/', views.TagCloudTV.as_view(), name='tag_cloud'),

# Example: /blog/tag/tagname/
path('tag/<str:tag>/', views.TaggedObjectLV.as_view(), name='tagged_object_list'),

TagCloudTV 클래스형 뷰는 태그 클라우드를 보여주기 위한 뷰로서,

템플릿 처리만 하면 되므로 TemplateView를 상속받아 정의한다.

TaggedObjectLV 클래스형 뷰는 포스트 리스트를 가져오는 뷰로서,

ListView를 상속받아 정의한다

 

view.py 수정하기

from django.views.generic import ListView, DetailView, TemplateView
#--- Tag View
class TagCloudTV(TemplateView):
    template_name = 'taggit/taggit_cloud.html'


class TaggedObjectLV(ListView):
    template_name = 'taggit/taggit_post_list.html'
    model = Post

    def get_queryset(self):
        return Post.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

 

템플릿 수정하기

#post_detail.html

{% extends "base.html" %}

{% block title %}post_detail.html{% endblock %}

{% block content %}
    <h2>{{ object.title }}</h2>

    <p>
        {% if object.get_next %}
        <a href="{{ object.get_next.get_absolute_url }}" title="View previous post">
            <i class="fas fa-arrow-circle-left"></i> {{ object.get_next }}
        </a>
        {% endif %}

        {% if object.get_previous %}
        | <a href="{{ object.get_previous.get_absolute_url }}" title="View next post">
        {{ object.get_previous }} <i class="fas fa-arrow-circle-right"></i>
        </a>
        {% endif %}
    </p>

    <div>{{ object.modify_dt|date:"j F Y" }}</div>
    <br>

    <div>
        {{ object.content|linebreaks }}
    </div>

    <br>
    <div>
        <b>TAGS</b> <i class="fas fa-tag"></i>
        {% load taggit_templatetags2_tags %}
        {% get_tags_for_object object as "tags" %}
        {% for tag in tags %}
        <a href="{% url 'blog:tagged_object_list' tag.name %}">{{tag.name}}</a>
        {% endfor %}
	&emsp;
        <a href="{% url 'blog:tag_cloud' %}"> <span class="btn btn-info btn-sm">TagCloud</span> </a>
    </div>
{% endblock %}
#taggit_cloud.html
{% extends "base.html" %}

{% block title %}taggit_cloud.html{% endblock %}

{% block extra-style %}
<style type="text/css">
.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 %}

{% block content %}
    <h1>Blog Tag Cloud</h1>
    <br>

    <div class="tag-cloud">
        {% load taggit_templatetags2_tags %}
        {% get_tagcloud as tags %}
        {% for tag in tags %}
        <span class="tag-{{tag.weight|floatformat:0}}">
            <a href="{% url 'blog:tagged_object_list' tag.name %}"> {{tag.name}}({{tag.num_times}})</a>
        </span>
        {% endfor %}
    </div>
{% endblock %}
#taggit_post_list.html
{% extends "base.html" %}

{% block title %}taggit_post_list.html{% endblock %} 

{% block content %}

    <h1>Posts for tag - {{ tagname }}</h1> 
    <br>

    {% for post in object_list %}
        <h2><a href='{{ post.get_absolute_url }}'>{{ post.title }}</a></h2> 
        {{ post.modify_dt|date:"N d, Y" }} 
        <p>{{ post.description }}</p> 
    {% endfor %}

{% endblock %}

위와 같이 파일들을 수정하고 작동시켰을때 다음과 같이 작동한다면 정상적으로 적용이 된것이다.

'Django' 카테고리의 다른 글

[Django] Blog 앱 확장 - 검색 기능  (0) 2022.07.31
[Django] Blog 앱 확장 - 댓글 달기  (0) 2022.07.30
[Django] Bookmark 앱, Blog 앱 개선하기  (0) 2022.07.22
[Django] 첫 페이지 만들기  (0) 2022.07.22
[Django] Blog 앱 개발  (0) 2022.07.22