장고 댓글 만들기 – 왕초보 파이썬 장고 (Python Django) 쇼핑몰 강의

웹서비스의 지속정인 성장은 세대간의 간격을 해결해야 합니다.

그러기 위해선 기존 서비스를 좀도 깊게 거창하게 하기 보다 한두가지 가지고 잘 조합해가는 과정을 한걸음씩 지속 하면 됩니다.

즉 지금의 상태를 여러 측면에서 관심을 기울여야 하고 그러면서 개선해 가는 과정 입니다.

그래서 보니 우리는 쇼핑몰의 핵심적인 기능을 그동안 구현해 왔지만, 상품평인 댓글 기능이 필요하다고 생각했습니다.

장고를 이용하면 여러분은 레고 처럼 원하는 기능을 무엇이든 만들 수 있습니다.

여러분이 상상하는 것은 무엇이든 추가해 갈 수 있는데요.

댓글 기능을 구현해 보도록 하겠습니다.

이 시간을 통해 여러분은 쇼핑몰 뿐만 아니라, 블로그나 게시판 등 글이 있는 커뮤니티 사이트 등 어디에도 적용이 가능한 댓글 구현 능력을 가지게 됩니다.

(참고로 먼저 이 강의를 보신 후 직접 블로그에 다는 형태의 강의도 추가로 보시고 싶으신 분은

실전(實戰) Django(장고) 강좌 – 프로 블로그 따라 만들기  강의의

“12.실전 장고 블로그 – 댓글(Comment) 기능 만들기” 를 보시면 조금 더 발전 하실 수 있습니다. )

  1. 모델 만들기

먼저 댓글 모델을 만듭니다. 들어갈 내용들은 댓글 작성자(author), 댓글 내용(text), 댓글 작성 시간(create_date)입니다.

class Comment(models.Model):
    product = models.ForeignKey(Product, related_name='comments', on_delete=models.CASCADE )
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    text = models.TextField()
    created_date = models.DateTimeField(default=timezone.now)
    modify_date = models.DateTimeField(null=True, blank=True)
    approved_comment = models.BooleanField(default=True)

    def __str__(self):
        return self.text

상품(Product) 에 댓글이 추가될 것입니다. 그리고 로그인 한 사용자(User)만 댓글을 쓸 수 있게 할것이기에 기존 모델인 Product와 User 와의 관계를 정의합니다.

댓글 수가 상품과 사용자 보다 많을 것이기에 외래키를 댓글 모델에서 지정을 한 것입니다.

models.ForeignKey 에서 상품페이지(Product)와 User가 지워지면 덧글도 지워지게 on_delete=models.CASCADE하였습니다.

댓글 기능에서 중요한 것이 악플 처리가 시대적 과제로 부각되고 있습니다.

관리자 임의로 댓글을 악플이나 마녀사냥이라고 삭제하면 악플 단 사람이 원한을 품고 관리자를 찾아와 협박을 하기도 하고 괴롭히니까

(십여년간  커뮤니티를 운영하다 보니,  정말 몰입하는 에고가 천하에 서리가 내리게 할 만큼 집요한 분들 많더라구요)

이 부분의 처리를 개선한 새로운 관리 기법으로 관리자가 모든 책임을 떠 안지 않게 관리자는 댓글 승인만 하지 않고

공통체에서 자체적으로 지우는 새로운 방식을 구현하기로 했습니다.

그래서 댓글 승인 approved_comment을 추가하였고 장고 admin에서 기본으로는 다 승인된 형태로 나오게 되며,

문제가 되면 승인 해제 하여 사용자 커뮤니티에서 처리하게 할것입니다.

이를 위해 다음과 같이 하였습니다approved_comment = models.BooleanField(default=True) 에서 기본적으로 댓글은 관리자기 처리 안해도  True, 즉 승인으로 처리됩니다.

modify_date 은 글쓴이가 댓글 수정을 했을때 수정시간을 반영합니다.

쇼핑몰이고 금전이 오가므로 나중에 말 바꾸는 일을 방지 하기 위해 글을 바꾼 시간 표시는 필수 겠죠.

앞선 create_date (글쓴 시간) 처럼 modify_date 필드도 장고가 기본으로 제공하는 timezone 을 사용하여 import 해 줍니다.

(from django.utils import timezone)  

마지막으로

    def __str__(self): 
        return self.text  

을 통해 admin 에 등록된 commnets에는 댓글이 보입니다. 만약 이것을 안 사용하면 오류는 안나지만 그냥 object로 나오게 되어 일일이 눌러서 들어가야 내용을

볼수 있게 됩니다. 

 

 

*레고처럼 여러분 모델에 적용 할때

댓글을 달고 싶은 곳의 모델을 붙여 넣기 하면 됩니다.

여기서는 Product 와User의 관계를 지정했는데, 만약 기존 모델로 Blog글이라면 Product대신 Blog를

회원가입을 받지 않는 사이트거나 회원가입 여부와 관계 없이 글을 쓸 수 있게 한다면 author 라는 필드만 정의하면 되겠죠.

그럴때는 문자 길이 정도 잡아주면 되겠습니다. 예를 들면 다음과 같이 합니다.

author = models.CharField(max_length=50)    

 

2. 모델 데이타베이스에 반영하기

 

앞서 만든 모델을 반영해 주기 위해 마이그레이션 작업을 해줍니다.

2-1 먼저 모델 변경 사항을 알려주시고

python manage.py makemigrations

2-2 모델 변경을 반영합니다.

python manage.py migrate

 

3. Admin 패널에 모델 반영해 주기

admin.py에 추가하기 위해서는 먼저 위에 만든 Comment model을 가져와 줍니다.

우리는 많은 모델을 만들었으므로,  models 에서 * 으로 처리했습니다. 땡(*) 은 우리말로 번역하면 몽땅이라는 것입니다.

따라서 models에서 만든 Product , Customer, Shippingadress  그리고 방금 만든 Commet등등 모델을 다 이용할 수 있습니다.

다음과 같이 합니다.

from .models import *         

그리고 어드민에 만든 Comment 모델을 추가해 줍니다.

admin.site.register(Comment)

이제  서버를 실행시키고 (python manage.py runserver)

어드민으로 들어가면 (http://127.0.0.1:8000/admin/)

방금 만든 Comment 가 보입니다. 

여러분은 여기서 댓글을 추가 삭제할 수 있습니다. Comment추가를 눌러보시면 앞서 모델에서

만든 필드들을 확인 할 수 있습니다. 여러분은 이런식으로 마음껏 필드를 원하시는 대로 조립해 주면 됩니다.

만든 필드들의 역활을 확인할 수 있도록 글을 원하시는대로 작성해주세요. 춘추필봉(春秋筆鋒)을 휘두르듯, 토황소격문(討黃巢檄文)을 적는 심정으로.

 

 

4. 댓글 작업 루틴 확인

계속해서 먼저 admin에서 작성한 댓글을 보여주고 그리고 이어서 우리는 글쓴이가 삭제 수정하게 하고 , 스팸댓글 공유 삭제 기능 추가하도록 하겠습니다.

우리는 지금까지 쇼핑몰을 만들면서 익숙해진 다음의 루틴을 사용합니다. 

다시한번 복습하면 다음과 같습니다.

1 템플릿 작업 – 글 쓰기, 수정 삭제 , 스팸댓글 공유 삭제 버튼 추가

2 urls.py에 링크에 해당되는 URL 매핑 작성

3 forms.py에 장고 폼 작성

4 views.py 파일에 URL 매핑에 추가한 함수 작성

5 함수에서 사용할 템플릿 작성

 

5.등록한 댓글 보여지게 하기

등록한 댓글이 보여지도록 product_detail.html 에 다음을 추가해 주세요.

장바구니 담기</button> 다음줄에 추가해 주시면 됩니다.
 
{% for comment in product.comments.all %}
    <!-- #product.comments.all 에서 루핑// -->
    {% if user.is_authenticated or comment.approved_comment %}
    <div class="comment">
        <div class="editcomment">
            {{ comment.created_date }}
              {% if comment.modify_date %}
              (수정:{{ comment.modify_date }})
              {% endif %}
              
        </div>
        <strong>(🐺 :{{ comment.author }})</strong>
        <p>✒️ :{{ comment.text|linebreaks }}</p>
    </div>
    {% endif %}
  {% empty %} 
    <p>앙직 댓글이 없어요~~</p>
{% endfor %}

댓글이 보여지도록 {% for comment in product.comments.all %}{% endfor %}사이에 댓글이 나오는데 보여지는 조건으로는 사용자가 인증되었거나 “또는” 승인된 댓글이 보여지도록  {% if user.is_authenticated or comment.approved_comment %}와  {% endif %}사이에 댓글 내용{{ comment.text|linebreaks }} 과 글쓴이{{ comment.author }}), 작성일시{{ comment.created_date }}를 출력했으며, 수정된 날짜가 있으면 수정된 날짜{{ comment.modify_date }}를 출력합니다.  따라서 승인안된 스팸글은 로그인 하지 않은 비회원은 볼 수 없습니다.

글이 없을 경우에 대비해 {% empty %} 을 이용해 보여줄 내용이 없으면 댓글이 없다는 내용이 나오도록 합니다.

또 제품과 댓글이 구분되고 좀더  있어보이는 쇼핑몰 디자인이 되도록 댓글에는 스타일 작업을 했습니다. 

.comment {
    border-top:dotted 1px rgb(223, 12, 12);
    font-size:0.9em;
	margin: 20px 0px 20px 20px;
	background-color: rgb(233, 186, 31);
}

.editcomment {
    
    background-color: rgb(0, 140, 255);
}

6. 댓글 작성하기

이제는 방문자가 로그인을 하면 댓글을 작성할 수 있게 만들어 봅시다.

forms.py 만들어 CommentForm 이라는 class 작성 합니다. 장고의 모델폼을 사용하는데, 모델 폼을 사용. 모델과 필드만 정해주면 자동으로 form이 작동됩니다.

아래에 보면 model 로 우리가 위에서 만든 Comment를 그리고 필드로는 입력시 글만 지정을 했습니다.

왜냐하면 우리는 로그인한 사용자가 글을 쓰게 할 것이니까요. 필드의 사용 예를 보기 위해 #fields = ('author', 'text',)을 주석처리해서 넣었습니다. 이 경우는 작성자 author와 글 text를 입력할 수 

있게 되겠죠?

class CommentForm(forms.ModelForm):

    class Meta:
        model = Comment
        #fields = ('author', 'text',)
        fields = ['text']
        labels = {
            'text': '댓글쓰기',
        }

 

views.py 파일에 댓글을 등록할 함수를 추가합니다.

 

def add_comment(request, pk):
    product = get_object_or_404(Product, pk=pk)
    if request.method == "POST":
        form = CommentForm(request.POST)
        if form.is_valid():
            comment = form.save(commit=False)
            comment.author = request.user
            comment.product = product
            comment.save()
            return redirect('product_detail', pk=product.pk)
    else:
        form = CommentForm()

    return render(request, 'store/add_comment.html', {'form': form})

 

댓글 저장 후에 제품 product_detail페이지로 리다이렉트됩니다. 그리고 댓글 작성을 위해서 add_comment.html로 갑니다.

저장할 객체가 없을때 404 에러 출력을 위해 get_object_or_404사용하고 이를 위해 임포트 해줍니다.

from django.shortcuts import get_object_or_404

물론 처음에  CommentForm 을 임포트 하는걸 잊으면 안되겠죠?

from .forms import ProductForm,CreateUserForm,CommentForm

 

7.댓글 작성 템플릿 작업

 

댓글 작성을 위한 add_comment.html 파일 작업을 합니다.

{% extends 'store/메인.html'%}
{% load static %}
{% block content %}
    <h1>새 덧글</h1>
    <form method="POST" class="post-form">{% csrf_token %}
        {{ form.as_p }}
        <button type="submit" class="btn btn-success">쓰기</button>
    </form>
{% endblock %}

 

8. 댓글 작성 URL 매핑

urls.py에 댓글 작성을 위한 url매핑 작업을 합니다.

path('product/<int:pk>/comment/', views.add_comment, name="add_comment"), 

 

9. 댓글 수정과 삭제 함수 만들기

댓글 수정 기능은 댓글 추가와 같은 방식입니다. 업데이트 시 comment.modify_date = timezone.now() 으로 수정 시간을 반영합니다.

댓글 삭제는 comment.delete()로 수행하고 삭제후에는 제품페이지로 다시 이동합니다.  인증된 사용자만 수정과 삭제가 가능하도록 

@login_required을 사용합니다. 

그리고 이를 위해  login_required를 임포트 해줘야 합니다.

from django.contrib.auth.decorators import login_required

 

 

@login_required
def comment_modify(request, pk):
    comment = get_object_or_404(Comment, pk=pk)
    
    if request.method == "POST":
        form = CommentForm(request.POST, instance=comment)
        if form.is_valid():
            comment = form.save()
            comment.modify_date = timezone.now()
            comment.save()
            return redirect('product_detail', pk=comment.product.pk)
    else:
        form = CommentForm(instance=comment)    
    return render(request, 'store/add_comment.html', {'form': form})
#댓글 수정 함수는 댓글 등록 함수와 같은 형태    



@login_required
def comment_remove(request, pk):
    comment = get_object_or_404(Comment, pk=pk)
    comment.delete()
    return redirect('product_detail', pk=comment.product.pk)

10. 댓글 수정, 삭제 URL 매핑

urls.py에 추가한 수정과 삭제 views를 매핑해 줍니다.

path('product/<int:pk>/remove/', views.comment_remove, name="comment_remove"),
path('product/<int:pk>/modify/', views.comment_modify, name='comment_modify'),

11 .댓글 수정, 삭제, 스팸 댓글 표시 기능 구현

 

이제 지금까지 작업한 모든것을 반영한 product_detail.html 을 완성합니다.

 

{% for comment in product.comments.all %}
    <!-- #product.comments.all 에서 루핑// -->
    {% if user.is_authenticated or comment.approved_comment %}
    <!-- 인증이 안되면 승인된 댓글은 보이나 스팸 댓글 지우는 행위에 참가 할 수 없게하기 위해 -->
    
    <div class="comment">
        <div class="editcomment">
            {{ comment.created_date }}
              {% if comment.modify_date %}
              (수정:{{ comment.modify_date }})
              {% endif %}
              
            {% if not comment.approved_comment %}
                <a class="btn btn-default" href="{% url 'comment_remove' pk=comment.pk %}">💩</a>
            {% endif %}
            
            

            {% if request.user == comment.author %}
            <a href="{% url 'comment_modify' pk=comment.pk  %}" class="small">수정</a>,
            <a href="{% url 'comment_remove' pk=comment.pk  %}" class="small">삭제</a>
            {% endif %}
        </div>
        <strong>(🐺 :{{ comment.author }})</strong>
        <p>✒️ :{{ comment.text|linebreaks }}</p>
    </div>
    {% endif %}
  {% empty %} 
    <p>앙직 댓글이 없어요~~</p>
{% endfor %}
{% if user.is_authenticated %}
                    
      <a class="btn btn-success" href="{% url 'add_comment' pk=product.pk %}">댓글추가</a>  

{% else %} 
<a href="{% url 'login' %}" class="btn btn-danger">로그인 후 댓글 달기</a>
{% endif %}

admin섹션에서 댓글 승인을 해제하면

삭제할 수 있게 됩니다 <a class="btn btn-default" href="{% url 'comment_remove' pk=comment.pk %}">💩</a>

또 댓글을 쓴 사람의 경우만 자신의 댓글의 수정과 삭제를 할 수 있도록 {% if request.user == comment.author %}{% endif %}사이에

수정과 삭제를 연결합니다.

사용자가 인증된 경우 {% if user.is_authenticated %} 에만 댓글 추가 버튼이 보이고 아니면 로그인 하라고 나옵니다.

<a href="{% url 'login' %}" class="btn btn-danger">로그인 후 댓글 달기</a>

자 이제 완성 되었습니다!

이런식으로 여러분들은 원하시는 기능들을 계속 레고 처럼 추가해 갈 수 있어요!

왕초보 파이썬 장고 쇼핑몰 따라만들기
왕초보 파이썬 장고 쇼핑몰 따라만들기

 

 

8월 20, 2021
shop2school 제휴 문의1-302-613 -1812 |이메일 info@shop2world.com | 법인명: SHOP2WORLD, INC. | 2801 CENTERVILLE RD 1ST FLOOR PMB 8085 WILMINGTON DE 19808 USA.