오늘은 기존에 만든 샘플 앱에 추가 기능을 넣어 봅시다.
먼저 test_app/models.py에 서평에 대한 내용을 담을 모델을 신규로 생성합니다.
...
class TestBook(models.Model):
...
rating=models.FloatField(null=True, blank=True)
def __str(self):
...
class TestBookComment(models.Model):
book = models.ForeignKey(TestBook, on_delete=models.CASCADE)
content = models.TextField(max_length=1000)
scroe = models.IntegerField(default=0)
likes = models.IntegerField(default=0)
dislikes = models.IntegerField(default=0)
def __str__(self):
return self.content
def check_acceptance(self, threshold_ratio=0.5):
total_votes = self.likes + self.dislikes
if total_votes == 0:
return False
like_ratio = self.likes / total_votes
if like_ratio >= threshold_ratio:
return True
else:
return False
TestBook
에 평점을 저장할 변수 rating
을 추가하고, 여러 개의 서평을 달 수 있도록 TestBookComment
테이블 모델 클래스와 1:N 관계를 만들어 주기 위해 ForeignKey 필드를 통해 관계를 정의했습니다. 그리고 나머지 각 변수의 역할은 다음과 같습니다.
content
: 서평 본문 내용score
: 추천도(점수)likes
: 이 서평에 대한 좋아요 수dislikes
: 이 서평에 대한 싫어요 수그리고 check_acceptance()
라는 함수를 만들어 '좋아요' 수와 '싫어요' 수의 비율에 따라 이 서평의 평가점수가 책의 평점 집계에 채택되는지 여부를 확인할 수 있게 했습니다.
다음으로 test_app/templates/book/detail.html
뷰에 서평을 달 수 있는 코드 및 html 마크업을 추가합니다.
/templates/book/detail.html 전문
<h3>{{ book.title }}</h3>
<table border="1">
<th>도서번호</th>
<th>카테고리</th>
<th>가격</th>
<th>등록일자</th>
<tr>
<td>{{ book.id }}</td>
<td>{{ book.category.categoryName }}</td>
<td>{{ book.price }}</td>
<td>{{ book.reg_datetime }}</td>
</tr>
</table>
<h4>서평 목록</h4>
<ul>
{% for comment in book.testbookcomment_set.all %}
<li>
<p>{{ comment.content }} -- {{ comment.score }}</p>
<p>
<button type="button" onclick="">👍</button> : {{ comment.likes }}
<button type="button">👎</button> : {{ comment.dislikes }}
</p>
</li>
{% endfor %}
</ul>
<h4>서평 쓰기</h4>
<form action="{% url 'bookstore:comment' book.id %}" method="post">
{% csrf_token %}
<fieldset class="rate" style="width: 500">
<b>추천도</b>
{% for i in range %}
<input
type="radio"
id="score{{ forloop.revcounter }}"
name="score"
value="{{ forloop.revcounter }}"
/><label
class="half"
for="score{{ forloop.revcounter }}"
title="{{ forloop.revcounter }}점"
>{{ forloop.revcounter }}</label
>
{% endfor %}
</fieldset>
<textarea name="comment" cols="100" rows="10" maxlength="1000"></textarea>
<input type="submit" value="Comment" />
</form>
<a href="{% url 'bookstore:index' %}">홈으로</a>
기존에 작성했던 책의 자세한 정보를 보여주는 테이블 아래에 해당 책에 달려있는 서평 목록과 그 밑에 form
태그를 이용하여 서평을 작성할 수 있는 양식을 추가했습니다.
추천도는 10점부터 1점까지 점수를 줄 수 있도록 라디오 버튼을 10개 만들기 위해 django templates의 for
태그 이용해서 버튼을 만들어 줬습니다.
for
에 대한 설명은 이 포스팅을 참고바랍니다.
참고
django templates의 보안정책으로 인해for
태그는 python의range()
함수를 바로 사용할 수 없습니다. 우선은range
라는 리스트변수로 for 루프를 돌게 작성해두고, 추후 view 코드에서range
변수를 넘겨받아 for 루프를 돌 수 있게 해줍니다.
참고
Django 템플릿 시스템에서 python의 for 반복문과 range 함수를 지원하지 않는 이유는 보안과 템플릿의 분리를 강화하기 위함입니다.
Django 템플릿 시스템은 주로 HTML과 같은 템플릿 코드를 뷰 코드와 분리하여 유지보수를 용이하게 합니다. 따라서 템플릿 코드는 보통 웹 디자이너나 프론트엔드 개발자가 작성하고, 뷰 코드는 백엔드 개발자가 작성하는 것이 일반적입니다. 이러한 템플릿과 뷰의 분리는 보안 측면에서 중요합니다.
python의 for 반복문과 range 함수를 템플릿에서 사용한다면, 템플릿 코드에 python의 로직이 포함되기 때문에 보안상 취약점이 발생할 수 있습니다. 특히, 템플릿 코드에 복잡한 로직이 포함되면 웹 애플리케이션의 취약점이 노출될 수 있으며, 이는 악의적인 사용자에게 악용될 수 있습니다.
다음으로 test_app/templates/book/results.html
파일을 추가하여 서평을 작성하고 제출을 했을 때 노출할 페이지를 만들어 줍니다.
<h1>{{ book.title }}</h1>
<p>서평이 등록되었습니다.</p>
<a href="{% url 'bookstore:detail' book.id %}">돌아가기</a>
이제 test_app/views.py
에 서평을 달 수 있는 뷰 코드를 작성합니다.
def book(request, book_id):
book = get_object_or_404(TestBook, pk=book_id)
return render(request, "book/detail.html", {"book": book, "range":range(10)})
def comment(request, book_id):
book = get_object_or_404(TestBook, pk=book_id)
comment = TestBookComment(
book = book,
content = request.POST["comment"],
score = request.POST["score"]
)
comment.save()
return HttpResponseRedirect(reverse("bookstore:results", args=(book.id,)))
def results(request, book_id):
book = get_object_or_404(TestBook, pk=book_id)
return render(request, "book/results.html", {"book":book})
먼저 book()
함수에 추천도 버튼을 만드는데 사용할 range
변수를 추가해줍니다. 10개의 값이 필요하므로 range 변수에 range(10)
함수를 담아 book/detail.html
로 보내도록 수정해줍니다.
comment()
함수는 detail 뷰에서 서평 및 점수를 작성한 후 submit 버튼을 눌렀을 때 호출되는 함수입니다.
먼저 해당 책이 존재하는지 체크하고 있으면 해당 책의 서평을 저장하는 로직으로 동작합니다. 그 후 bookstore:results
url로 리다이렉트 되도록 설정했습니다.
마지막으로 서평 작성 완료 화면을 노출시키기 위한 result()
함수를 추가해 줍니다.
이제 작성한 내용은 url를 연결시켜주기 위해 test_app/urls.py
를 수정합니다.
...
path("<int:book_id>/comment", views.comment, name="comment"),
path("<int:book_id>/results", views.results, name="results"),
...
다 됐으면 서버를 기동시켜 봅니다.