이전포스트
[Django] 1. 개발 환경 설정 https://wroni.tistory.com/4
[Django] 2 - 1. URL과 View https://wroni.tistory.com/5
[Django] 2 - 2. 모델 https://wroni.tistory.com/6
[Django] 2 - 3. 장고 관리자 https://wroni.tistory.com/7
[Django] 2 - 4. 조회와 템플릿 https://wroni.tistory.com/8
[Django] 2 - 5. URL과 네임스페이스, 2 - 6. 데이터 저장 https://wroni.tistory.com/9
[Django] 2 - 7. 스태틱 https://wroni.tistory.com/10
[Django] 2 - 8. 부트스트랩 https://wroni.tistory.com/11
[Django] 2 - 9. 템플릿 상속 https://wroni.tistory.com/12
[Django] 2 - 10. 폼 https://wroni.tistory.com/13
[Django] 3 - 1. 네비게이션바 https://wroni.tistory.com/14
[Django] 3 - 2. 페이징 https://wroni.tistory.com/15
[Django] 3 - 3. 템플릿 필터, 3 - 4. 답변 개수 표시 https://wroni.tistory.com/16
[Django] 3 - 5. 로그인과 로그아웃 https://wroni.tistory.com/18
[Django] 3 - 6. 계정생성 https://wroni.tistory.com/19
[Django] 3 - 7. 모델 변경 https://wroni.tistory.com/20
[Django] 3 - 8. 글쓴이 표시 https://wroni.tistory.com/21
[Django] 3 - 9. 수정과 삭제 https://wroni.tistory.com/22
3 - 10. 댓글
1. 댓글 모델
댓글 작성을 하기 위해 댓글 모델을 작성해야 한다.
파일 : C:\projects\mysite\pybo\models.py
class Comment(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE)
content = models.TextField()
create_date = models.DateTimeField()
modify_date = models.DateTimeField(null=True, blank=True)
question = models.ForeignKey(Question, null=True, blank=True, on_delete=models.CASCADE)
answer = models.ForeignKey(Answer, null=True, blank=True, on_delete=models.CASCADE)
Comment 모델의 속성
필드 | 속성 |
author | 댓글 글쓴이 |
content | 댓글 내용 |
create_date | 댓글 작성 일시 |
modify_date | 댓글 수정 일시 |
question | 이 댓글이 달린 질문 |
answer | 이 댓글이 달린 답변 |
질문에 댓글이 달리면 question에 값이 저장되고, 답변에 댓글이 달리면 answer에 값이 저장된다.
즉, question 혹은 answer 둘중 하나에만 저장된다. 그렇기 때문에 question과 answer 모델 속성 모두 null=True, blank=True로 설정해야 한다.
그리고 question이나 answer가 삭제될 경우 댓글도 삭제되도록 on_delete=models.CASCADE를 설정하였다.
모델이 변경되었으니 makemigrations, migrate를 실행한다.
장고로 기능을 추가할 때마다 사용하는 패턴이 있다.
장고 기능 개발 패턴 정리
1. 템플릿에 추가 기능을 위한 링크나 버튼 추가
2. urls.py에 링크에 해당되는 URL 매핑을 작성
3. forms.py에 폼 작성 (폼 필요없으면 생략 가능)
4. views.py 파일에 URL 매핑에 추가한 함수 작성
5. 함수에서 사용할 템플릿 작성
2. 질문 댓글
질문 댓글 링크
질문 상세 템플릿을 수정하자.
파일 : C:\projects\mysite\templates\pybo\question_detail.html
{% extends 'base.html' %}
{% block content %}
<div class="container my-3">
<h2 class="border-bottom py-2">{{ question.subject }}</h2>
<div class="card my-3">
<div class="card-body">
<div class="card-text" style="white-space: pre-line;">{{ question.content }}</div>
<div class="d-flex justify-content-end">
{% if question.modify_date %}
<div class="badge badge-light p-2 text-left mx-3">
<div class="mb-2">modified at</div>
<div>{{ question.modify_date }}</div>
</div>
{% endif %}
<div class="badge badge-light p-2 text-left">
<div class="mb-2">{{ question.author.username }}</div>
<div>{{ question.create_date }}</div>
</div>
</div>
{% if request.user == question.author %}
<div class="my-3">
<a href="{% url 'pybo:question_modify' question.id %}"
class="btn btn-sm btn-outline-secondary">수정</a>
<a href="#" class="delete btn btn-sm btn-outline-secondary"
data-uri="{% url 'pybo:question_delete' question.id %}">삭제</a>
</div>
{% endif %}
<!-- 질문 댓글 Start -->
{% if question.comment_set.count > 0 %}
<div class="mt-3">
{% for comment in question.comment_set.all %}<!-- 등록한 댓글 출력 -->
<div class="comment py-2 text-muted">
<span style="white-space: pre-line;">{{ comment.content }}</span>
<span>
- {{ comment.author }}, {{ comment.create_date }}
{% if comment.modify_date %}
(수정:{{ comment.modify_date }}
{% endif %}
</span>
{% if request.user == comment.author %}
<a href="{% url 'pybo:comment_modify_question' comment.id %}" class="small">수정</a>,
<a href="#" class="small delete"
data-uri="{% url 'pybo:comment_delete_question' comment.id %}">삭제</a>
{% endif %}
</div>
{% endif %}
</div>
<a href="{% url 'pybo:comment_create_question' question.id %}"
class="small"><small>댓글 추가 ..</small></a> <!-- 댓글 추가 -->
</div>
<!-- 질문 댓글 End -->
</div>
...생략...
질문 댓글 Start 부터 End 까지 작성하면 된다. div 태그 주의해서 작성하자.
질문에 등록된 댓글을 보여주도록 {% for comment in question.comment_set.all %} 와 {% endfor %} 사이에 댓글 내용과 글쓴이, 작성일시를 출력하였다.
댓글 글슨이와 로그인한 사용자가 같으면 수정, 삭제 링크가 보이도록 하였다. for 바깥문에는 댓글 추가... 링크도 추가하였다.
for문에 의해 반복되는 댓글 각각은 comment라는 스타일 클래스를 따로 지정하였따.
댓글 영역은 조금 작은 글씨로 보여지도록 의도하였다. 지금까지 빈 파일로 유지되던 style.css에 comment 클래스에 대한 내용을 추가하자.
파일 : C:\projects\mysite\static\style.css
.comment{
border-top:dotted 1px #ddd;
font-size:0.7em;
}
comment 클래스는 댓글 각 상단에 점선을 추가하고 글꼴 크기를 0.7em으로 설정하였다
질문 댓글 URL 매핑
위에서 추가한 수정, 삭제, 댓글 추가 에 해당하는 URL 매핑을 pybo.urls.py에 추가할 것이다.
파일 : C:\projects\mysite\pybo\urls.py
urlpatterns = [
...생략
path('comment/create/question/<int:question_id/>', views.comment_create_question,
name='comment_create_question'),
path('comment/modify/question/<int:comment_id/>', views.comment_modify_question,
name='comment_modify_question'),
path('comment/delete/question/<int:comment_id/>', views.comment_delete_question,
name='comment_delete_question'),
]
댓글 등록, 수정, 삭제에 대한 URL 매핑을 추가하였다. 댓글을 등록할 때는 질문의 id 번호 <int:question_id>가 필요하다.
댓글을 수정하거나 삭제할 때는 <int:comment_id>가 필요하다.
질문 댓글 폼
댓글 등록시 사용할 CommentForm을 다음과 같이 작성한다.
파일 : C:\projects\mysite\pybo\forms.py
from django import forms
from pybo.models import Question, Answer, Comment
... 생략 ...
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ['content']
labels = {
'content': '댓글내용',
}
댓글 작성시에는 content 필드만 사용한다.
질문 댓글 등록 함수
pybo/views.py에 댓글 등록 함수를 추가하자.
파일 : C:\projects\mysite\pybo\views.py
...생략...
from .forms import QuestionForm, AnswerForm, CommentForm
...생략...
@login_required(login_url='common:login')
def comment_create_question(request, question_id):
"""
pybo 질문댓글등록
"""
question = get_object_or_404(Question, pk=question_id)
if request.method == "POST":
form = CommentForm(request.POST)
if form.is_valid():
comment = form.save(commit=False)
comment.author = request.user
comment.create_date = timezone.now()
comment.question = question
comment.save()
return redirect('pybo:detail', question_id=question.id)
else:
form = CommentForm()
context = {'form': form}
return render(request, 'pybo/comment_form.html', context)
댓글이 저장된 후에는 댓글이 작성한 질문 상세(pybo:detail) 화면으로 리다이렉트하였다.
질문에 대한 댓글로 comment.question = question 처럼 comment에 question을 저장하였다.
질문 댓글 템플릿
댓글 등록 위해 comment_form.html 템플릿을 새로 생성하자.
파일 : C:\projects\mysite\templates\pybo\comment_form.html
{% extends 'base.html' %}
{% block content %}
<div class="container my-3">
<h5 class="border-bottom pb-2">댓글등록하기</h5>
<form method="post" class="post-form my-3">
{% csrf_token %}
{% include "form_errors.html" %}
<div class="form-group">
<label for="content">댓글내용</label>
<textarea class="form-control"name="content" id="content"
rows="3">{{ form.content.value|default_if_none:'' }}</textarea>
</div>
<button type="submit" class="btn btn-primary">저장하기</button>
</form>
</div>
{% endblock %}
댓글의 내용만 입력할 수 있는 폼이다.
질문 댓글 수정 함수
이제 질문에 등록된 댓글을 수정하기 위해 comment_modify_question 함수를 추가하자.
파일 : C:\projects\mysite\pybo\views.py
... 생략 ...
from .models import Question, Answer, Comment
... 생략 ...
@login_required(login_url='common:login')
def comment_modify_question(request, comment_id):
"""
pybo 질문댓글수정
"""
comment = get_object_or_404(Comment, pk=comment_id)
if request.user != comment.author:
messages.error(request, '댓글수정권한이 없습니다')
return redirect('pybo:detail', question_id=comment.question.id)
if request.method == "POST":
form = CommentForm(request.POST, instance=comment)
if form.is_valid():
comment = form.save(commit=False)
comment.modify_date = timezone.now()
comment.save()
return redirect('pybo:detail', question_id=comment.question.id)
else:
form = CommentForm(instance=comment)
context = {'form' : form}
return render(request, 'pybo/comment_form.html', context)
질문 댓글 수정 함수는 질문 댓글 등록 함수와 다르지 않다. GET 방식이면 기존 댓글을 조회하여 폼에 반영하고,
POST 방식이면 입력된 값으로 댓글을 업데이트 한다. 또한 업데이트 시 modify_date 수정일시를 반영한다.
질문 댓글 삭제 함수
이제 질문에 등록된 댓글을 삭제하는 함수 comment_delete_question을 추가하자.
파일 : C:\projects\mysite\pybo\views.py
...생략...
@login_required(login_url='common:login')
def comment_delete_qustion(request, comment_id):
"""
pybo 질문 댓글 삭제
"""
comment = get_object_or_404(Comment, pk=comment_id)
if request.user != comment.author:
messages.error(request, '댓글삭제권한이 없습니다')
return redirect('pybo:detail', question_id=comment.question.id)
else:
comment.delete()
return redirect('pybo:detail', question_id=comment.question.id)
댓글을 삭제하면 댓글이 있던 질문 상세 화면으로 리다이렉트한다.
질문 댓글 기능 확인
여기까지 한 것을 확인해보자. 질문 상세화면에서 댓글 추가... 를 누르고 댓글을 추가해보자. 수정과 삭제 기능도 잘 작동하는지 확인한다.
오류..
django.core.exceptions.ImproperlyConfigured:
URL route 'comment/create/question/<int:question_id/>' uses parameter name 'question_id/'
which isn't a valid Python identifier.
urls.py에서 오타가 났나보다. 'comment/create/question/<int:question_id>/'로 적어야 하는데 'comment/create/question_id/>'로 /의 순서가 바뀌었다. 수정하고 다시 웹 서버 구동하면,
또 오류가 뜬다...
AttributeError: module 'pybo.views' has no attribute 'comment_delete_question'
views.py 에서 comment_delete_question이 아니라 question에서 오타가 나서 안 되는 거였다. 다시 웹 서버 구동하면,
휴 이제 정상적으로 구동된다. 이제 본 목적인 질문 댓글 기능을 확인해보자.
의도한대로 잘 나오고 있다.
3. 답변 댓글
답변 댓글 링크
파일 : C:\projects\mysite\templates\pybo\question_detail.html
...생략...
</div>
{% if request.user == answer.author %}
<div class="my-3">
<a href="{% url 'pybo:answer_modify' answer.id %}"
class="btn btn-sm btn-outline-secondary">수정</a>
<a href="#" class="delete btn btn-sm btn-outline-secondary "
data-uri="{% url 'pybo:answer_delete' answer.id %}">삭제</a>
</div>
{% endif %}
<!-- 작성시작 -->
{% if answer.comment_set.count > 0 %}
<div class="mt-3">
{% for comment in answer.comment_set.all %}
<div class="comment py-2 text-muted">
<span style="white-space: pre-line;">{{ comment.content }}</span>
<span>
- {{ comment.author }}, {{ comment.create_date }}
{% if comment.modify_date %}
(수정:{{ comment.modify_date }})
{% endif %}
</span>
{% if request.user == comment.author %}
<a href="{% url 'pybo:comment_modify_answer' comment.id %}" class="small">수정</a>,
<a href="#" class="small delete"
data-uri="{% url 'pybo:comment_delete_answer' comment.id %}">삭제</a>
{% endif %}
</div>
{% endfor %}
</div>
{% endif %}
<div>
<a href="{% url 'pybo:comment_create_answer' answer.id %}"
class="small"><small>댓글 추가 ..</small></a>
</div>
<!-- 작성 끝 -->
</div>
</div>
{% endfor %}
... 생략 ...
질문 댓글 기능을 추가했던 것과 차이가 없으나, question.comment_set이 아니라 answerl.comment_set을 사용하였다.
그리고 수정, 삭제, 추가 시 호출되는 URL만 다르다.
답변 댓글 URL 매핑
urls.py에 URL 매핑을 추가하자.
파일 : C:\projects\mysite\pybo\urls.py
... 생략 ...
urlpatterns = [
... 생략 ...
path('comment/create/answer/<int:answer_id>/', views.comment_create_answer, name='comment_create_answer'),
path('comment/modify/answer/<int:comment_id>/', views.comment_modify_answer, name='comment_modify_answer'),
path('comment/delete/answer/<int:comment_id>/', views.comment_delete_answer, name='comment_delete_answer'),
}
댓글을 등록할 경우에는 답변 id 번호(answer_id)를 사용하였고, 댓글을 수정하거나 삭제할 경우, 댓글의 id 번호(comment_id)를 사용하였다.
답변 댓글 등록, 수정, 삭제 함수
views.py 파일에 답변의 댓글을 등록, 수정, 삭제 하기 위한 함수를 추가하자.
파일 : C:\projects\mysite\pybo\views.py
@login_required(login_url='common:login')
def comment_create_answer(request, answer_id):
"""
pybo 답변 댓글 등록
"""
answer = get_object_or_404(Answer, pk=answer_id)
if request.method == "POST":
form = CommentForm(request.POST)
if form.is_valid():
comment = form.save(commit=False)
comment.author = request.user
comment.create_date = timezone.now()
comment.answer = answer
comment.save()
return redirect('pybo:detail', question_id=comment.answer.question.id)
else:
form = CommentForm()
context = {'form': form}
return render(request, 'pybo/comment_form.html', context)
@login_required(login_url='common:login')
def comment_modify_answer(request, comment_id):
"""
pybo 답변 댓글 수정
"""
comment = get_object_or_404(Comment, pk=comment_id)
if request.user != comment.author:
messages.error(request, '댓글수정권한이 없습니다')
return redirect('pybo:detail', question_id=comment.answer.question.id)
if request.method == "POST":
form = CommentForm(request.POST, instance=comment)
if form.is_valid():
comment = form.save(commit=False)
comment.modify_date = timezone.now()
comment.save()
return redirect('pybo:detail', question_id=comment.answer.question.id)
else:
form = CommentForm(instance=comment)
context = {'form': form}
return render(request, 'pybo/comment_form.html', context)
@login_required(login_url='common:login')
def comment_delete_answer(request, comment_id):
"""
pybo 답변 댓글 삭제
"""
comment = get_object_or_404(Comment, pk=comment_id)
if request.user != comment.author:
messages.error(request, '댓글삭제권한이 없습니다')
return redirect('pybo:detail', question_id=comment.answer.question.id)
else:
comment.delete()
return redirect('pybo:detail', question_id=comment.answer.question.id)
질문 댓글 등록, 수정, 삭제 했을 때의 과정과 같다. 이제 잘 구동되는지 확인하면,
질문, 답변 모두 댓글 등록도 되고 수정, 삭제도 된다.
※ 본 내용은 django 공부 기록이며, 점프 투 장고를 참고하였습니다.
'Django > Django 기초' 카테고리의 다른 글
[Django] 3 - 11. views.py 파일 분리 (0) | 2021.12.21 |
---|---|
[Django] 3 - 9. 수정과 삭제 (0) | 2021.12.15 |
[Django] 3 - 8. 글쓴이 표시 (0) | 2021.12.14 |
[Django] 3 - 7. 모델 변경 (0) | 2021.12.09 |
[Django] 3 - 6. 계정생성 (0) | 2021.12.07 |