Django : python으로 웹서비스 개발하기(11) - 템플릿 활용 및 폼 사용하기

harry jang·2023년 7월 29일
0

Django

목록 보기
11/12
post-thumbnail

오늘은 기존에 만든 샘플 앱에 추가 기능을 넣어 봅시다.

  • 서평(comment) 달기
  • 평점 등록
  • 서평에 좋아요, 싫어요 기능

먼저 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"),
...

다 됐으면 서버를 기동시켜 봅니다.

자세히 보기 뷰

서평 작성 결과 뷰

자세히 보기 뷰(서평추가)

profile
software engineer

0개의 댓글