장고 태그 시스템 만들기

소요 시간: 10분

사용자가 관련 주제를 쉽게 찾을 수 있게 포스트 앱에 태그 시스템을 추가했다.


모델

태그 모델은 Post 모델 다음에 추가했다. 태그 이름을 저장하는 name 필드를 생성하고, 중복 저장되지 않도록 unique=True 옵션을 추가했다.

그리고 Post 모델에 태그 모델을 ManyToManyField로 연결했다. 포스트와 태그는 다대다 관계로 하나의 태그에 여러 포스트들을 연결할 수 있다.

# posts/models.py
from django.db import models

class Tag(models.Model):
    name = models.CharField(max_length=100, unique=True)

    def __str__(self):
        return self.name

class Post(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    tags = models.ManyToManyField(Tag, related_name='posts')

    def __str__(self):
        return self.title

그 다음, admin.py 파일에서 장고 관리 사이트에 모델을 등록했다. PostAdmin과 TagAdmin 클래스를 정의하여 각각의 필드를 어떻게 보여줄지 설정했다. 이를 통해 관리자는 포스트와 태그를 쉽게 추가하고 수정할 수 있다.

from django.contrib import admin
from .models import Post, Tag

@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
    list_display = ('title', 'content')

@admin.register(Tag)
class TagAdmin(admin.ModelAdmin):
    list_display = ('name',)


이제 `forms.py`에서 포스트를 작성하는 폼을 만들었다. 사용자가 쉼표로 태그를 입력할 수 있도록 `tags` 필드를 추가하고, `save` 메서드를 오버라이드하여 태그를 저장하는 로직을 구현했다. 이 과정에서 사용자가 입력한 태그를 `Tag` 모델과 연결해주는 작업을 했다.

# posts/forms.py
from django import forms
from .models import Post, Tag

class PostForm(forms.ModelForm):
    tags = forms.CharField(max_length=200, help_text="쉼표로 태그를 구분하세요")

    class Meta:
        model = Post
        fields = ['title', 'content', 'tags']

    def save(self, *args, **kwargs):
        instance = super(PostForm, self).save(commit=False)
        instance.save()
        tags = self.cleaned_data['tags']
        tag_list = [tag.strip() for tag in tags.split(',')]
        # tag_list = [tag.strip() for tag in tags.split('#') if tag.strip()] # 해시 태그로 구분
        for tag_name in tag_list:
            tag, created = Tag.objects.get_or_create(name=tag_name)
            instance.tags.add(tag)
        return instance

태그는 기존에 저장된 것과 중복이 될 수 있다. 이를 해결하려면 if문을 사용해야 하는데 간단히 get_or_create 메서드를 활용해 코드를 좀 더 깔끔하게 정리했다.


뷰 정의

뷰를 설정하여 포스트 목록과 생성 페이지를 만들었다. `post_list` 함수는 데이터베이스에서 모든 포스트를 가져와서 리스트 형태로 보여주고, `post_create` 함수는 사용자가 새 포스트를 작성할 수 있도록 돕는다.

from django.shortcuts import render, redirect
from .models import Post
from .forms import PostForm

def post_list(request):
    posts = Post.objects.all()
    return render(request, 'community/post_list.html', {'posts': posts})

def post_create(request):
    if request.method == 'POST':
        form = PostForm(request.POST)
        if form.is_valid():
            form.save()
            return redirect('post_list')
    else:
        form = PostForm()
    return render(request, 'community/post_form.html', {'form': form})

def tag_posts(request, tag_id):
    tag = get_object_or_404(Tag, id=tag_id)  # 태그 ID로 태그 가져오기
    posts = tag.posts.all()  # 해당 태그에 연결된 포스트 쿼리
    return render(request, 'community/tag_posts.html', {'tag': tag, 'posts': posts})  # 템플릿으로 전달

get_object_or_404를 사용하여 태그를 안전하게 가져오고, 만약 해당 ID의 태그가 존재하지 않으면 404 오류를 발생시킨다. 이 부분이 중요하다, 사용자가 존재하지 않는 태그에 접근했을 때의 예외 처리를 해주기 때문이다.


URL 설정

기존 Post 앱 URL 패턴에 태그 관련 URL를 추가했다.

# posts/urls.py
from django.urls import path
from .views import post_list, post_create, tag_posts

urlpatterns = [
    path('', post_list, name='post_list'),  # 포스트 목록 URL
    path('create/', post_create, name='post_create'),  # 포스트 생성 URL
    path('tag/%lt;int:tag_id%gt;/', tag_posts, name='tag_posts'),  # 태그 관련 URL 추가
]


템플릿

마지막으로 포스트 목록과 생성 페이지의 HTML 템플릿을 작성했다. 포스트 목록에서는 각 포스트와 그에 연결된 태그를 보여준다. 사용자가 태그를 클릭하면 해당 태그에 관련된 포스트 목록으로 이동하게 된다.

<!-- community/templates/community/post_list.html -->
<!DOCTYPE html>
<html>
<head>
    <title>Post List</title>
</head>
<body>
    <h2>{{ post.title }}</h2>
    <p>{{ post.content }}</p>
    {% for tag in post.tags.all %}
    <a href="{% url 'tag_posts' tag.id %}">{{ tag.name }}</a>
    <!-- 만약 해시 태그로 구분한다면 -->
    <a href="{% url 'tag_posts' tag.id %}">#{{ tag.name }}</a>
    {% endfor %}
    <a href="{% url 'post_create' %}">Create New Post</a>
</body>
</html>

그리고 태그에 관련된 포스트를 보여주는 새로운 템플릿을 생성했다. 사용자가 클릭한 태그와 연결된 모든 포스트를 목록으로 표시한다.

<!-- posts/templates/community/tag_posts.html -->
<!DOCTYPE html>
<html>
<head>
    <title>Posts with Tag: {{ tag.name }}</title>
</head>
<body>
    <h1>Posts with Tag: {{ tag.name }}</h1>
    <a href="{% url 'post_list' %}">Back to Post List</a>
    <ul>
        {% for post in posts %}
            <li>
                <h2>{{ post.title }}</h2>
                <p>{{ post.content }}</p>
            </li>
        {% empty %}
            <li>No posts found for this tag.</li>  <!-- 태그에 해당하는 포스트가 없을 경우 안내 메시지 -->
        {% endfor %}
    </ul>
</body>
</html>

이 페이지는 선택한 태그와 관련된 모든 포스트를 목록으로 표시하며, 태그에 해당하는 포스트가 없을 경우 사용자에게 적절한 메시지를 제공한다.


이제 태그를 클릭했을 때 해당 태그가 추가된 포스트들을 쉽게 볼 수 있는 기능이 추가되었다. 사용자가 태그를 클릭하여 관련된 콘텐츠를 탐색할 수 있게 되었고, 이는 전체적인 사용자 경험을 크게 향상시킬 것이다. 앞으로는 이러한 기능을 확장하여 태그별 필터링 및 검색 기능도 추가해볼 예정이다.


장고 리스트