-
[4월 5주차-4/28~29]🎯 Django 블로그 프로젝트Why Not SW CAMP 5기/수업 기록 2025. 4. 29. 14:23
안녕하세요! 오늘은 Django를 활용해서 직접 만든 블로그 프로젝트의 주요 기능들을 정리해보려 합니다.
(장고 설치부터 검색, 댓글, 이메일 공유까지 직접 구현했어요!)
🖋️ 구현한 주요 기능
📌 1. 게시글 목록 출력 (Post List)
- 최신순으로 모든 게시글을 출력합니다.
- 한 페이지에 4개씩만 보여주고, 페이지네이션(pagination)을 적용했습니다.
- 특정 태그(tag)가 달린 게시글만 필터링해서 볼 수도 있습니다.
✔️ 사용 기술:
- Paginator 클래스
- taggit 패키지를 통한 태그 관리
- URL 쿼리 파라미터로 페이지 번호 처리
paginator = Paginator(post_list, 4) page_number = request.GET.get('page', 1) posts = paginator.page(page_number)
📌 2. 게시글 상세 보기 (Post Detail)
- 게시글을 클릭하면 상세 내용을 볼 수 있습니다.
- 게시글 하단에는 댓글(Comment) 리스트가 함께 출력됩니다.
- 같은 태그를 가진 비슷한 게시글을 추천해주는 기능도 추가했습니다.
✔️ 사용 기술:
- get_object_or_404를 통한 안전한 조회
- Count('tags')를 통한 비슷한 글 추천
similar_posts = Post.published.filter(tags__in=post_tags_ids).exclude(id=post.id) similar_posts = similar_posts.annotate(same_tags=Count('tags')).order_by('-same_tags', '-publish')[:4]
📌 3. 게시글 이메일 공유 (Post Share)
- 게시글을 이메일로 다른 사람에게 추천할 수 있습니다.
- 이메일 제목과 내용은 폼에 작성한 내용을 기반으로 자동 생성됩니다.
✔️ 사용 기술:
- send_mail 함수로 이메일 발송
- build_absolute_uri로 절대 경로 생성
subject = f"{cd['name']} recommends you read {post.title}" send_mail(subject, message, '990303amy88@gmail.com', [cd['to']])
📌 4. 댓글 작성 (Post Comment)
- 게시글 하단에서 바로 댓글을 작성할 수 있습니다.
- 작성된 댓글은 승인(active=True)된 것만 보여주게 설정했습니다.
✔️ 사용 기술:
- @require_POST 데코레이터로 POST 요청만 허용
- ModelForm 기반 댓글 폼
form = CommentForm(data=request.POST) if form.is_valid(): comment = form.save(commit=False) comment.post = post comment.save()
📌 5. 게시글 검색 기능 (Post Search)
- 키워드를 입력하면 제목과 본문에 해당 키워드를 포함하는 게시글을 검색합니다.
- PostgreSQL SearchVector를 활용해 빠른 검색이 가능하도록 구현했습니다.
✔️ 사용 기술:
- PostgreSQL 전용 SearchVector 사용
- 검색 결과 출력
results = Post.published.annotate( search=SearchVector('title', 'body'), ).filter(search=query)
🏁 프로젝트를 하며 느낀 점
처음에는 Django의 구조가 낯설었지만, 직접 앱을 만들면서 모델-폼-뷰-템플릿 사이의 흐름을 이해할 수 있었습니다.
단순한 CRUD를 넘어, 댓글, 검색, 추천, 이메일 공유까지 구현하면서 웹 개발이 정말 매력적이라는 걸 느꼈어요.특히,
- 에러 발생 시 문제를 파악하고 해결하는 과정
- 데이터베이스 최적화를 고민해보는 경험
- Django의 편리함과 복잡함을 동시에 체감한 점
모두 값진 경험이었습니다.
📸 캡처/코드 추가
blog/views.py
from django.shortcuts import render, get_object_or_404 from .models import Post, Comment from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from django.views.generic import ListView from django.contrib.postgres.search import SearchVector from .forms import EmailPostForm, CommentForm, SearchForm from django.core.mail import send_mail from django.views.decorators.http import require_POST from taggit.models import Tag from django.db.models import Count class PostListView(ListView): """ Alternative post list view """ queryset = Post.published.all() context_object_name='posts' paginate_by = 3 template_name = 'blog/post/list.html' def post_list(request, tag_slug=None): post_list = Post.published.all() tag = None if tag_slug: tag = get_object_or_404(Tag, slug=tag_slug) post_list = post_list.filter(tags__in=[tag]) paginator = Paginator(post_list, 4) page_number = request.GET.get('page', 1) try: posts = paginator.page(page_number) except PageNotAnInteger: posts= paginator.page(1) except EmptyPage: posts=paginator.page(paginator.num_pages) return render(request, 'blog/post/list.html', {'posts': posts, 'tag': tag}) def post_detail(request,year, month, day, post): post = get_object_or_404(Post, status=Post.Status.PUBLISHED, slug=post, publish__year=year, publish__month=month, publish__day=day) comments = post.comments.filter(active=True) form = CommentForm() post_tags_ids = post.tags.values_list('id', flat=True) similar_posts = Post.published.filter(tags__in=post_tags_ids).exclude(id=post.id) similar_posts = similar_posts.annotate(same_tags=Count('tags')).order_by('-same_tags','-publish')[:4] return render(request, 'blog/post/detail.html', {'post': post, 'comments': comments, 'form': form, 'similar_posts': similar_posts}) def post_share(request, post_id): post = get_object_or_404(Post, id= post_id, status = Post.Status.PUBLISHED) sent = False if request.method == 'POST': form = EmailPostForm(request.POST) if form.is_valid(): cd = form.cleaned_data post_url = request.build_absolute_uri(post.get_absolute_url()) subject = f"{cd['name']} recommends you read" f"{post.title}" message = f"Read {post.title} at {post_url}\n\n" \ f"{cd['name']}\'s comments: {cd['comments']}" send_mail(subject, message, '990303amy88@gmail.com', [cd['to']]) sent = True else: form = EmailPostForm() return render(request, 'blog/post/share.html', {'post': post, 'form': form, 'sent':sent}) @require_POST def post_comment(request, post_id): post = get_object_or_404(Post, id=post_id, status = Post.Status.PUBLISHED) comment = None form = CommentForm(data=request.POST) if form.is_valid(): comment = form.save(commit=False) comment.post = post comment.save() return render(request, 'blog/post/comment.html', {'post': post, 'form': form, 'comment': comment}) def post_search(request): form = SearchForm() query = None results = [] if 'query' in request.GET: form = SearchForm(request.GET) if form.is_valid(): query = form.cleaned_data['query'] results = Post.published.annotate( search=SearchVector('title', 'body'), ).filter(search=query) return render(request, 'blog/post/search.html', {'form': form, 'query': query, 'results': results})
'Why Not SW CAMP 5기 > 수업 기록' 카테고리의 다른 글
[4월 5주차-4/29~30]🌐 Django로 나만의 로그인 + 이미지 북마크 웹사이트 만들기 (feat. 구글 소셜 로그인까지) (1) 2025.04.30 [4월 4주차-4/24]RubiBlog: Django + Amazon Lightsail 배포기 (0) 2025.04.24 [4월 4주차-4/22]📝 Django로 블로그 만들기 – 글/댓글/썸네일까지 (0) 2025.04.22 [4월 4주차-4/21]🍔 Django로 햄버거 검색 웹 만들기 (0) 2025.04.21 [4월 3주차-4/15-16]Node.js & AWS를 활용한 이미지 갤러리 웹사이트 구축기 (0) 2025.04.16