2019.05.16 장고 댓글 삭제 및 수정 구현하기

댓글 수정 및 삭제 구현하기

  1. views.py 만들기
  2. 템플릿 만들기
  3. urls.py 만들기

update , delete 페이지에 접근했을 때 구동되는 로직들

함수형 뷰 구현하기


def comment_update(request, comment_id):

    comment = get_object_or_404(Comment, pk=comment_id)
    document = get_object_or_404(Document, pk=comment.document.id)

    if request.user != comment.author:
        messages.warning(request, "권한 없음")
        return redirect(document)

    if request.method == "POST":
        form = CommentForm(request.POST, instance=comment)
        if form.is_valid():
            form.save()
            return redirect(document)
    else:
        form = CommentForm(instance=comment)
    return render(request,'board/comment/comment_update.html',{'form':form})


def comment_delete(request, comment_id):

    comment = get_object_or_404(Comment, pk=comment_id)
    document = get_object_or_404(Document, pk=comment.document.id)

    if request.user != comment.author and not request.user.is_staff and request.user != document.author:
        messages.warning(request, '권한 없음')
        return redirect(document)

    if request.method == "POST":
        comment.delete()
        return redirect(document)
    else:
        return render(request, 'board/comment/comment_delete.html', {'object':comment})
  1. 해당 객체가 있는지 확인 : get_object_or_404, objects.get , objects.filter.exists
  2. 객체에 대한 권한 체크 - 작성자, 관리자
  3. get : 해당 페이지에 필요한 값 입력 받기
  4. post : 입력 받은 값에 대한 처리 -> 삭제 , 업데이트
  5. 처리 후 페이지 이동
  6. 여기서 중요한 것은 instance = comment를 넣어주어서 기존에 등록되었던 값이 보이도록 해줘야 한다.
  7. instance를 등록해주지 않으면 새로운 값으로 생각하고 새롭게 생성하게 된다.

클래스 뷰


from django.views.generic.edit import CreateView, UpdateView, DeleteView

class CommentUpdate(UpdateView):
    model = Comment
    fields = [ 'text']
    template_name_suffix = '_update'
    # success_url = '/'


    def dispatch(self, request, *args, **kwargs):
        object = self.get_object()
        if object.author != request.user:
            messages.warning(request, '수정할 권한이 없습니다.')
            return HttpResponseRedirect('/')
            # 삭제 페이지에서 권한이 없다! 라고 띄우거나
            # detail페이지로 들어가서 삭제에 실패했습니다. 라고 띄우거나
        else:
            return super(CommentUpdate, self).dispatch(request, *args, **kwargs)

from django.http import HttpResponseRedirect
from django.contrib import messages


class CommentDelete(DeleteView):
    model = Comment
    template_name_suffix = '_delete' 
    success_url = '/'

    def dispatch(self, request, *args, **kwargs):
        object = self.get_object()
        if object.author != request.user:
            messages.warning(request, '삭제할 권한이 없습니다.')
            return HttpResponseRedirect('/')
        else:
            return super(CommentDelete, self).dispatch(request, *args, **kwargs)
  1. 객체에 대한 권한 체크 - 작성자, 관리자 - dispatch
  2. 해당 객체가 있는지 확인 : get_object, get_queryset
  3. get : 해당 페이지에 필요한 값 입력 받기 - def get
  4. post : 입력 받은 값에 대한 처리 -> 삭제 , 업데이트 - def post
  5. dispatch를 통해 get과 post를 한번에 처리할 수 있고 class generic 뷰를 상속받아서 더 간단하게 구현 가능하다.
  6. 처리 후 페이지 이동

def dispatch에서 POST와 GET나누어서 처리하기

def dispatch(self, request, *args, **kwargs):
    object = self.get_object()
    if request.method == "POST":
        super().post(request, *args, **kwargs)
    else:   
        super().post(request, *args, **kwargs)

템플릿 만들기

delet.html (board/comment/comment_delete.html)


{ % extends 'base.html' % }
{ % block content % }
<!--진짜 지울건지 확인을 받아야 한다. form을 또 보여줄 필요가 없다.-->
<div class="alert alert-primary" role="alert">
  진짜로 {{object}}를 지우실 건가요?
</div>
<form action="" method ="post">
    { % csrf_token% }
    <input type="submit" value="Delete OK">
</form>
{ % endblock % }
  • form에 따로 받을 양식은 없다. post 형태로 오면 그냥 지우면 된다.

update.html (board/comment/comment_update.html)


{ % extends 'base.html' % }


{ % block content % }

<form action="" method = "post" enctype="multipart/form-data">
    { % csrf_token % }
    <table>
    {{form.as_table}}
        </table>
    <input type="submit" value="Write">

</form>

{ % endblock % }
  • update를 할 때 중요한 것은 바로 전체 자료가 남아 있도록 해야 한다.
 if request.method == "POST":
        form = CommentForm(request.POST, instance=comment)
        if form.is_valid():
            form.save()
            return redirect(document)
    else:
        form = CommentForm(instance=comment)
  • 여기서 알 수 있듯이 함수형 뷰로 해당 로직을 처리해줄 때는 꼭 instance= comment를 남겨서 기존에 데이터를 넘어오도록 해야 한다.

urls.py 연결하기 (함수형 뷰에서 구현했다고 가정)


from .views import comment_delete, comment_update

app_name = 'board'

urlpatterns = [
    path('comment/delete/<int:comment_id>/', comment_delete, name="comment_delete"),
    path('comment/update/<int:comment_id>/', comment_update, name="comment_update"),

]

잘 되는지 확인해보기

  • 해당 내용에 대해 잘 작동하는지 확인해본다.

2019.05.16 장고 댓글 기능 구현하기 함수형 뷰

댓글 기능 구현하기(함수형 뷰에서 구현하기)

  1. 모델이 있다고 꼭 그 모델의 뷰가 필요한 것은 아니다.

  2. 댓글 같은 경우 그 컨텐츠의 아래에 적히는 것이기 때문에 그 컨텐츠의 뷰에서 처리한다.

  3. 폼이 있어야 한다. form.py를 만들거나 템플릿에서 만들어줘야 한다.

  4. 한 뷰에서 처리하기(detail 뷰에서 처리하기)

    1. 모델 구현하기
    2. form.py 구현하기
    3. 뷰 구현하기
    4. 템플릿 구현하기
  5. 댓글 기능을 하나의 뷰로 빼서 처리하기

    1. 모델 구현하기
    2. form.py구현하기
    3. 뷰 구현하기
    4. 템플릿 구현하기
      1. form 의 action에 view를 연결하기 위해 url을 설정해줘야한다.
    5. url 연결해주기
      1. url을 뷰로 연결시켜준다.

모델 구현하기


class Comment(models.Model):
    document = models.ForeignKey(Document, on_delete=models.CASCADE, related_name='comments')
    author = models.ForeignKey(get_user_model(), on_delete=models.SET_NULL, null=True, blank=True, related_name='comments')
    text = models.TextField()
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)
    like = models.IntegerField(default=0)
    dislike = models.IntegerField(default=0)

    def __str__(self):
        return (self.author.username if self.author else "무명")+ "의 댓글"
  • get_user_model : 커스텀 모델 , 커스텀 USER를 넣거나 없으면 그냥 USER를 받음
  • author : models.SET_NULL : 연결된 foreignkey 즉 USER가 삭제되어도 Comment를 null로 바꾸고 남겨놓는다. 단 null=True가 필요하다.

form.py 구현하기

from .models import Document, Comment

class CommentForm(forms.ModelForm):
    #text = forms.TextInput(label = '댓글')

    class Meta:
        model = Comment
        fields = ['text']

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['text'].label = "댓글"
  • text필드를 댓글이라는 문구로 바꾸어 줄 수 있다.
  • text = forms.TextInput(label = '댓글') 혹은
  • 아래의 def _init_ 을 통해 구현하기

views.py 뷰 구현하기

document_detail 뷰에서 구현할 때

def document_detail(request, document_id):

    document = get_object_or_404(Document, pk=document_id)

    #만약 post일때만 댓글 입력에 관한 처리를 더한다.

    if request.method == "POST":
        comment_form = CommentForm(request.POST)
        comment_form.instance.author_id = request.user.id
        comment_form.instance.document_id = document_id
        if comment_form.is_valid():
            comment = comment_form.save()


    #models.py에서 document의 related_name을 comments로 해놓았다.

    comment_form = CommentForm()
    comments = document.comments.all()

    return render(request, 'board/document_detail.html', {'object':document, "comments":comments, "comment_form":comment_form})
  • detail에서는 get_object_or_404로 하나만 뽑아내야 하고 따라서 pk키도 필요하다.

    • list에서는 get_list_or_404로 전체를 뽑아낸다.
  • document.comments.all() 아래에 와야지 논리오류가 발생하지 않는다.

  • comment_form 의 위치에 따라서 글자가 바뀐다.

    • comment_form.save 아래에 위치하게 되면 저장된 상태로 그대로 넘어가서 정보가 남아있게 된다.
  • self.instance = 상속받은 객체 그 자체

댓글 기능 따로 빼서 구현하기


def comment(request, document_id):

    if request.method == "POST":
        comment_form = CommentForm(request.POST)
        comment_form.instance.author_id = request.user.id
        comment_form.instance.document_id = document_id
        if comment_form.is_valid():
            comment = comment_form.save()

    return HttpResponseRedirect(reverse_lazy('board:detail', args=[document_id]))

# get_absolute_url을 설정해 놓았을 시(in models)
    def get_absolute_url(self):
        return reverse('board:detail', args=[self.id])

 제일 마지막 문장    return redirect(document)로  대체가능
  • 하지만 이렇게 해주면 해당 뷰로 접근하기 위한 url이 필요하게 되고 탬플릿의 form에서 action에 url을 설정해줘야 한다.
  • 그리기 위해서 또 urls.py에 url을 연결하여 해당 views로 접근하도록 한다.
  • 그리고 그 뷰를 실행하고 redirect를 활용하여 detail 페이지로 가도록 구현해준다.

템플릿 구현하기

  • document_detail.html

{ % extends 'base.html' % }


{ % block content % }

{{object.title}} 

{{object.text}}

{{object.image.url}}

{{object.author.username}}



<form action="{ % url "board:comment" object.id % }" method="POST">
{ % csrf_token % }

    {{comment_form.as_p}}
    <input type="submit" value="Comment" class="btn btn-outline-primary">

</form>

<table class="table table-striped">
    { % for comment in comments % }
    <tr>
        <td>{{ comment.text }}</td>
        <td>{{ comment.author.username}}</td>
        <td>{{ comment.created }}</td>
        <td><a href="{ % url 'board:comment_delete' comment.id % }">삭제하기</a></td>
        <td><a href="{ % url 'board:comment_update' comment.id % }">수정하기</a></td>
    </tr>
    { % endfor % }
</table>

{ % endblock % }
  • 만들어 놓은 comment_form을 가지고 온다.
  • 댓글 기능을 따로 구현하였다면 위와 같이 form의 action이 필요하고 그렇지 않다면 없어도 된다.
  • comments를 돌면서 관련 댓글을 뽑아오는데 이 comments는 detail 뷰에서 받아왔다.
    • document = get_object_or_404(Document, pk=document_id) 를 통해 특정 document에 대한 것들만 다 가지고 온다음에
    • comments = document.comments.all()를 통해 해당 document의 comment를 모두 가지고 왔다.
  • 옛날에 나는 일단 모든 comment를 다 가지고 온 다음에 템플릿 단에서 각 객체들의 id를 비교하여 보여줄 수 있도록 했었고
  • 그 다음에는 filter를 통해 구현해보려고 헀었다.
  • 하지만 그럴 필요 없이 미리 document를 pk값으로 특정 document를 가지고 오고 그 모든 comment를 템플릿으로 넘겨줌으로서 해결 할 수 있다.

+ Recent posts