장고:답변쓰기(댓글쓰기): 두 판 사이의 차이
104번째 줄: | 104번째 줄: | ||
</div> | </div> | ||
</syntaxhighlight>글 detail 뷰에서 question객체를 탬플릿에 보냈는데, question 객체 안에 연결된 answer객체가 함께 담겨 딸려간다. | </syntaxhighlight>글 detail 뷰에서 question객체를 탬플릿에 보냈는데, question 객체 안에 연결된 answer객체가 함께 담겨 딸려간다. | ||
(이때 하위 모델은 대문자로 시작하더라도 소문자로 써주어야 한다.) | |||
|좌측에서 등록을 위한 부분만 달리하면 된다.<syntaxhighlight lang="html"> | |좌측에서 등록을 위한 부분만 달리하면 된다.<syntaxhighlight lang="html"> | ||
<!--답변등록을 위한 것--> | <!--답변등록을 위한 것--> |
2021년 3월 21일 (일) 13:21 기준 최신판
장고! 웹 프레임워크! 틀:장고
개요[편집 | 원본 편집]
글에 답변을 다는 기능이다. 댓글기능을 추가하는 거라 보면 좋겠다.
url 작성[편집 | 원본 편집]
urls.py 안에 필요한 기능을 다 담아주어야 한다.(뷰의 설정이나, import방식에 따라 뷰를 불러오는 방식이 달라질 수 있다.)
일반적으로 코드를 짜는 경우 | 제네릭 뷰를 사용하는 경우 |
---|---|
다음과 같은 path를 추가해준다.
실제로 이동하는 링크는 아니지만, 기능을 구현하기 위한 것.from django.urls import path
from . views #해당 앱의 뷰를 불러온다.
app_name = 'pool'
urlpatterns = [
path('answer/create/<int:question_id>/', views.answer_create, name='answer_create'),
path('answer/update/<int:answer_id>/', views.answer_update, name='answer_update'),
path('answer/delete/<int:answer_id>/', views.answer_delete, name='answer_delete'),
]
|
함수명을 바꾸어주어야 한다.
views.클래스뷰명.as_view()), 형태로. 클래스형 뷰임을 지정해주기 위해. |
#아직 함수를 짜주진 않았지만, 앞으로 만들 함수에 대해 연결해두자.
모델 작성[편집 | 원본 편집]
포함해야 할 것들이 있다. 작성자, 작성일자, 내용 등의 요소를 포함하여 모델을 작성한다. 이름은 answer로 하자.
/앱/models.py 안에 작성한다.
from django.contrib.auth.models import User
from django.db import models
class Answer(models.Model):#세부내용은 필요에 따라..
question = models.ForeignKey(Question, on_delete=models.PROTECT)
content = models.CharField(max_length=300)
create_date = models.DateTimeField()
관리자 기능에서 확인[편집 | 원본 편집]
모델이 제대로 작성되고, 글이 제대로 생성될 수 있는지 관리자기능에 등록한 후 answer를 작성해보자.
관리자 등록은 장고:관리자페이지 참조.
이상이 없으면 뷰 작성으로 넘어가자.
뷰, 탬플릿 작성[편집 | 원본 편집]
탬플릿과 연관이 깊으니, 같이 짜주자. 한 단계, 한 단계 차근차근 작동을 확인하며 넘어가자.
답변 작성하기[편집 | 원본 편집]
답변을 작성하는 기능의 view를 짜보자.
아무래도 form을 사용하지 않으면 유효성검사에 대한 처리를 따로 해주어야 한다. 모델을 저렇게 짜두어도 유효성검사를 해주진 않는다.(글자제한을 두어도 그냥 입력되어버린다.)
과정 | 코드 | 폼을 사용하는 경우 |
---|---|---|
form작성 | 전체적으로..
글쓰기에서 글 작성과 유사하게 만들면 된다.from django import forms
from pybo.models import Answer
class AnswerForm(forms.ModelForm):
class Meta:
model = Answer
fields = ['content']
labels = {
'content': '댓글내용',
}
| |
template작성 | 댓글 작성은 화면에서 이루어지므로, 탬플릿을 먼저 작성한다.
<!--답변등록을 위한 것-->
<form action="{% url '앱이름:answer_create' question.id %}" method="post">
{% csrf_token %}
<textarea name="content" id="content" rows="15"></textarea>
<input type="submit" value="답변등록">
</form>
<!--답변을 보기 위한 것-->
<div>
<ul>
{% for answer in question.answer_set.all %}
<li>{{ answer.content }}</li>
{% endfor %}
</ul>
</div>
(이때 하위 모델은 대문자로 시작하더라도 소문자로 써주어야 한다.) |
좌측에서 등록을 위한 부분만 달리하면 된다.<!--답변등록을 위한 것-->
<form action="{% url '앱이름:answer_create' question.id %}" method="post">
{% csrf_token %}
<!--폼에러를 나타내기 위한 것-->
{% if form.errors %}
<div class="alert alert-danger" role="alert">
{% for field in form %}
{% if field.errors %}
<strong>{{ field.label }}</strong>
{{ field.errors }}
{% endif %}
{% endfor %}
</div>
{% endif %}
<!--답변등록을 위한 칸-->
{{ form.as_p }}
<input type="submit" value="댓글등록">
</form>
|
view 작성 | 작성 후에 다시 원 글로 이동이 필요하므로 redirect를 추가한다.from django.shortcuts import render, get_object_or_404, redirect
from .models import * #모델을 불러온다.
def answer_create(request, question_id):
question = get_object_or_404(Question, pk=question_id)
question.answer_set.create(content=request.POST.get('content'), create_date=timezone.now())
return redirect('앱이름:detail', question_id=question.id)
question 객체의 answer_set 속성에 들어있는 객체를 의미. [어떤 방식으로 들어있는지는 DB를 까봐야겠다.]
from django.shortcuts import render, get_object_or_404, redirect
from .models import * #모델을 불러온다.
def answer_create(request, question_id):
question = get_object_or_404(Question, pk=question_id)
answer = Answer(question=question, content=request.POST.get('content'), create_date=timezone.now())
answer.save()
return redirect('앱이름:detail', question_id=question.id)
|
from django.shortcuts import render, get_object_or_404, redirect
from .models import * #모델을 불러온다.
from .forms import AnswerForm #answer폼을 불러온다.
def answer_create(request, question_id):
question = get_object_or_404(Question, pk=question_id)
if request.method == "POST":
form = AnswerForm(request.POST)#post를 통해 받은 폼.
if form.is_valid():
answer = form.save(commit=False)
answer.question = question #question객체 연결.
answer.create_date = timezone.now()
answer.save()
return redirect('앱이름:detail', question_id=question.id)
else:
form = AnswerForm()
context = {'question': question, 'form': form}
return render(request, 'detail.html', context)
#폼이 적절하지 않다면 에러메시지와 함께 detail로 보낸다.
|
답변수정[편집 | 원본 편집]
과정 | 코드 | 폼을 사용하는 경우 |
---|---|---|
탬플릿 수정 | 역시, 작성자에게만 수정버튼이 보이게 조작한다.
답변을 보여주는 for 태그 안에 넣자.{% if request.user == answer.author %}<!--답변 작성자에게만 보이게끔.-->
<div class="my-3">
<a href="{% url 'pool:answer_update' answer.id %}"
class="btn btn-sm btn-outline-secondary">수정</a>
</div>
{% endif %}
|
|
탬플릿 추가 | 상세조회 탬플릿 아래 답변들을 보였는데, 상세조회 탬플릿 안에서 수정하기엔 번거로움이 많다.(귀찮)
못할 건 아니지만, (question객체를 담아 보내면 가능, 아니면 detail뷰를 조작하거나.) 간편한 수정을 위해 새로운 탬플릿을 만든다. 답변 수정은 그 폼을 담았다는 의미로 answer_form.html로 만든다.{% extends 'common.html' %}
{% block content %}
<div class="container my-3">
<form method="post" class="post-form">
{% csrf_token %}
{% include "form_errors.html" %}
<div class="form-group">
<label for="content">답변내용</label>
<textarea class="form-control" name="content" id="content"
rows="10">{{ form.content.value|default_if_none:'' }}</textarea>
</div>
<button type="submit" class="btn btn-primary">저장하기</button>
</form>
</div>
{% endblock %}
|
|
view작성 | @login_required(login_url='membership:login')
def answer_update(request, answer_id):
answer = get_object_or_404(Answer, pk=answer_id)
if request.user != answer.author:
messages.error(request, '수정권한이 없습니다')
return redirect('pool:detail', question_id=answer.question.id)
if request.method == "POST":
form = AnswerForm(request.POST, instance=answer)
if form.is_valid():
answer = form.save(commit=False)
answer.author = request.user
answer.modify_date = timezone.now()
answer.save()
return redirect('pool:detail', question_id=answer.question.id)
else:
form = AnswerForm(instance=answer)
context = {'answer': answer, 'form': form}
return render(request, 'answer_form.html', context)
|
답변삭제[편집 | 원본 편집]
과정 | 코드 |
---|---|
탬플릿 수정 | 수정버튼 옆에 다음의 코드를 넣어준다.(한 묶음 안에)<a href="#" class="delete btn btn-sm btn-outline-secondary " data-uri="{% url 'pool:answer_delete' answer.id %}">삭제</a>
|
탬플릿 추가 | 상세조회 탬플릿 아래 답변들을 보였는데, 상세조회 탬플릿 안에서 수정하기엔 번거로움이 많다.(귀찮)
못할 건 아니지만, (question객체를 담아 보내면 가능, 아니면 detail뷰를 조작하거나.) 간편한 수정을 위해 새로운 탬플릿을 만든다. 답변 수정은 그 폼을 담았다는 의미로 answer_form.html로 만든다.{% extends 'common.html' %}
{% block content %}
<div class="container my-3">
<form method="post" class="post-form">
{% csrf_token %}
{% include "form_errors.html" %}
<div class="form-group">
<label for="content">답변내용</label>
<textarea class="form-control" name="content" id="content"
rows="10">{{ form.content.value|default_if_none:'' }}</textarea>
</div>
<button type="submit" class="btn btn-primary">저장하기</button>
</form>
</div>
{% endblock %}
|
view작성 | @login_required(login_url='membership:login')
def answer_delete(request, answer_id):
answer = get_object_or_404(Answer, pk=answer_id)
if request.user != answer.author:
messages.error(request, '삭제권한이 없습니다')
else:
answer.delete()
return redirect('pool:detail', question_id=answer.question.id)
|
대댓글[편집 | 원본 편집]
댓글에 또 댓글을 달 수도 있지! 위 내용을 조금 수정해 question 대신 answer의 외래키를 사용하게끔 받으면 대댓글을 달 수 있다.
[나중에 페이징을 위해선 대댓글도 같은 모델에서 다루는 게 좋지 않을까 싶다. 같은 모델의 외래키를 사용해서.. question 외래키와 answer외래키를 사용해서 null, blank 속성을 True로 줘서... 페이징 하는 게 좋을 듯한데;]
대대대대대댓글까지 만들려면 객체가 같은 모델을 외래키로 참조하게 하면 될 듯하다. 댓글의 깊이 제한은 탬플릿에서 if와 for 태그의 조합으로 가능할듯.
과정 | 일반적으로 코드를 짜는 경우 |
---|---|
urls.py 수정 | 다음과 같은 path를 추가해준다.from django.urls import path
from . views #해당 앱의 뷰를 불러온다.
app_name = 'pool'
urlpatterns = [
path('comment/create/<int:answer_id>/', views.comment_create, name='comment_create'),
path('comment/update/<int:comment_id>/', views.comment_update, name='comment_update'),
path('comment/delete/<int:comment_id>/', views.comment_delete, name='comment_delete'),
]
|
모델 작성 | models..py에 추가해준다.class Comment(models.Model):
answer = models.ForeignKey(Answer, null=True, blank=True, on_delete=models.SET_NULL)
author = models.ForeignKey(User, on_delete=models.PROTECT)
content = models.CharField(max_length=300)
create_date = models.DateTimeField()
modify_date = models.DateTimeField(null=True, blank=True)
|
form 작성 | 작성을 위해 폼을 만든다. forms.py에 내용 추가.from pybo.models import Question, Answer, Comment#대댓글 모델 추가
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ['content']
labels = {
'content': '댓글내용',
}
|
template수정 | 대댓글을 입력하는 버튼과 대댓글의 수정, 삭제버튼을 만들어야 한다.{% 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>
|
template작성 | 댓글과 같은 이유로 폼을 따로 작성해준다.
comment_form.html로 만들자.{% extends 'common.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 %}
|
view 작성 | 윗부분과 상당히 중복된다.from .forms import QuestionForm, AnswerForm, CommentForm #대댓글 폼 추가.
@login_required(login_url='membership:login')
def comment_create(request, answer_id):
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('pool:detail', question_id=comment.answer.question.id)
else:
form = CommentForm()
context = {'form': form}
return render(request, 'comment_form.html', context)
@login_required(login_url='membership:login')
def comment_update(request, comment_id):
comment = get_object_or_404(Comment, pk=comment_id)
if request.user != comment.author:
messages.error(request, '댓글수정권한이 없습니다')
return redirect('pool: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.author = request.user
comment.modify_date = timezone.now()
comment.save()
return redirect('pool:detail', question_id=comment.answer.question.id)
else:
form = CommentForm(instance=comment)
context = {'form': form}
return render(request, 'comment_form.html', context)
@login_required(login_url='membership:login')
def comment_delete(request, comment_id):
comment = get_object_or_404(Comment, pk=comment_id)
if request.user != comment.author:
messages.error(request, '댓글삭제권한이 없습니다')
return redirect('pool:detail', question_id=comment.answer.question.id)
else:
comment.delete()
return redirect('pool:detail', question_id=comment.answer.question.id)
|
부록[편집 | 원본 편집]
댓글 갯수 나타내기[편집 | 원본 편집]
게시판 같은 곳을 보면 글 옆에 몇 개의 댓글이 달렸나 보여준다.
글의 리스트와 같은 곳에서 댓글 갯수를 나타내고 싶은 곳에 {{question.answer_set.count}}
형태로 달아두면 question의 하위 댓글들 숫자를 볼 수 있다.
{% if question.answer_set.count > 0 %}
을 두어서 답변이 있을 때만 표시하게 하는 방법도 가능하겠다.
대댓글 갯수도 함께 나타내기[편집 | 원본 편집]
그런데, 위에 건 answer의 모델만 세는 것이고, comment의 갯수까지 세진 못한다. 때문에 view를 다음과 같이 변경해보자.
어차피 question객체는담겨 보내지기 때문에 페이징 이후의 question 객체 안의 새로운 속성을 지정하여 대댓글까지 더해보자.
####댓글 갯수 세기
for question in question_list:
question.comment_sum = question.answer_set.count() # question객체의 새로운 속성을 정의하고 각 댓글의 갯수를 부여한다.
for answer in question.answer_set.all(): #question 객체 하위의 answer로 돌려가며 대댓글 갯수를 추가한다.
question.comment_sum += answer.comment_set.count()
이후 탬플릿의 적당한 자리에 {{question.comment_sum}}
를 넣으면 댓글과 대댓글까지 세서 보여준다.
앵커 추가하기[편집 | 원본 편집]
답변이 길어지면 작성한 답을 확인하기 위해 스크롤을 내려야 하는 귀찮음이 있다. 이를 편하게 하기 위해 앵커를 추가한다.
탬플릿에 <a id="answer_{{answer.id}}"></a> (답변등록 후 해당 답변으로 오게 하기 위한 앵커)
따위를 추가하고, 생성, 수정뷰를 수정한다.
from django.shortcuts import ........, resolve_url #실제 url을 추출하기 위해 사용한다.
# redirect('pool:detail', question_id=question.id) 를 아래와 같이 수정한다.
return redirect('{}#answer_{}'.format(
resolve_url('pool:detail', question_id=question.id), answer.id))
이외 아이디어[편집 | 원본 편집]
항상 같은 위치에 있는 버튼 띄워두고 본문, 댓글, 목록 창으로 이동하게 하면 좋겠다.(개드립처럼)
댓글 입력할 때에만 편집창이 나오게 할 수도 있다.(섬머노트)https://summernote.org/examples/ air모드로도 가능할듯.