ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [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})

Designed by Tistory.