장고 태그 시스템 만들기
사용자가 관련 주제를 쉽게 찾을 수 있게 포스트 앱에 태그 시스템을 추가했다.
모델
태그 모델은 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>
이 페이지는 선택한 태그와 관련된 모든 포스트를 목록으로 표시하며, 태그에 해당하는 포스트가 없을 경우 사용자에게 적절한 메시지를 제공한다.
이제 태그를 클릭했을 때 해당 태그가 추가된 포스트들을 쉽게 볼 수 있는 기능이 추가되었다. 사용자가 태그를 클릭하여 관련된 콘텐츠를 탐색할 수 있게 되었고, 이는 전체적인 사용자 경험을 크게 향상시킬 것이다. 앞으로는 이러한 기능을 확장하여 태그별 필터링 및 검색 기능도 추가해볼 예정이다.