[Django] 장고의 기본 요소 익히기 - 데이터 저장

싱숭생숭어·2023년 4월 30일
0

Django

목록 보기
10/19
post-thumbnail

답변등록 폼

질문 상세 템플릿(question_detail.html)에 다음처럼 답변을 저장할 수 있는 폼(form) 추가

<h1>{{ question.subject }}</h1>
<div>
    {{ question.content }}
</div>

<!--이 아래부분이 새로 추가한 부분-->
<form action="{% url 'pybo:answer_create' question.id %}" method="post">
{% csrf_token %}
<textarea name="content" id="content" rows="15"></textarea>
<input type="submit" value="답변등록">
</form>
  • 답변의 내용을 입력할 수 있는 텍스트창(textarea)와 답변을 저장할 수 있는 "답변등록" 버튼을 추가함

  • 답변 저장을 위한 URL은 form 태그의 action 속성에 {% url 'pybo:answer_create' question.id %}로 지정함

  • form 태그 아래 {% csrf_token %}은 보안에 관련된 항목으로, form으로 전송한 데이터가 실제 웹 페이지에서 작성한 데이터인지를 판단하는 가늠자 역할

    • 만일 해커가 데이터를 전송할 경우엔 서버에서 발생한 csrf_token 값과 해커가 일방적으로 보낸 crsf_token 값이 일치하지 않으므로 블록킹 됨
    • 따라서 form 태그 바로 밑에 {% csrf_token %}태그를 항상 위치시켜야 함 !
    • POST 방식으로 요청 시 form 태그에 crsf_token이 없으면 장고는 오류 발생 시킴

crsf란?

  • CRSF(cross site request forgery)는 웹 사이트 취약점 공격 방지를 위해 사용하는 기술로, 장고가 CRSF 토큰 값을 세션을 통해 발행하고 웹 페이지에서는 폼 전송시 해당 토큰을 함께 전송해 실제 웹 페이지에서 작성된 데이터가 전달되는지를 검증하는 기술
  • crsf_token 사용을 위해서는 CsrfViewMiddleware 미들웨어가 필요한데, 이 미들웨어는 settings.py의 MIDDLEWARE 항목에 디폴트로 추가되어 있으므로 별도의 설정은 필요 없음
    MIDDLEWARE = [
       'django.middleware.security.SecurityMiddleware',
       'django.contrib.sessions.middleware.SessionMiddleware',
       'django.middleware.common.CommonMiddleware',
       'django.middleware.csrf.CsrfViewMiddleware', #CsrfViewMiddleware 미들웨어
       'django.contrib.auth.middleware.AuthenticationMiddleware',
       'django.contrib.messages.middleware.MessageMiddleware',
       'django.middleware.clickjacking.XFrameOptionsMiddleware',
    ]```
     * 만약 csrf_token 기능을 사용하고 싶지 않다면 이 부분을 주석처리하면 됨 !

URL 매핑

질문 상세 템플릿을 위처럼 고친 후 질문 상세 페이지 요청 시

위와 같은 answer_create 별칭을 찾을 수 없다는 오류가 뜬다.

이유는 질문 상세 템플릿(question_detail.html)의 {% url 'pybo:answer_create' question.id %}부분에 pybo:answer_create 별칭을 사용했기 때문 !

오류 해결을 위해 pybo/urls.py에 다음과 같은 URL 매핑을 등록한다.

from django.urls import path

from . import views

app_name = 'pybo'

urlpatterns = [
    path('', views.index, name='index'),
    path('<int:question_id>/', views.detail, name='detail'),
    path('answer/create/<int:question_id>/', views.answer_create, name='answer_create'), #새로 추가한 URL 매핑 부분
]
  • path('answer/create/<int:question_id>/', views.answer_create, 이 부분을 통해 answer_create 별칭에 해당하는 URL 매핑 규칙을 등록

  • 이후 http://locahost:8000/pybo/answer/create/2/ 와 같은 페이지를 요청하면 URL 매핑 규칙에 의해 views.answer_create 함수가 호출될 것


뷰 함수

위의 URL 매핑 규칙에 의해 정의된 views.answer_create함수를 pybo/views.py파일에 아래와 같이 추가

from django.shortcuts import render, get_object_or_404, redirect #redirect 추가
from django.utils import timezone #새로 추가한 부분 
from .models import Question

(... 생략 ...)

def answer_create(request, question_id): #새로 추가한 answer_create 함수
    question = get_object_or_404(Question, pk=question_id)
    question.answer_set.create(content=request.POST.get('content'), create_date=timezone.now())
    return redirect('pybo:detail', question_id=question.id)
  • answer_create 함수의 매개변수 question_id는 URL 매핑에 의해 그 값이 전달됨

    • 만약 http://locahost:8000/pybo/answer/create/2/ 라는 페이지를 요청하면 매개변수 question_id에는 2라는 값이 전달될 것
  • 답변 등록시 텍스트창에 입력한 내용은 answer_create 함수의 첫번째 매개변수인 request 객체를 통해 읽기 가능

    • request.POST.get('content')로 텍스트창에 입력한 내용을 읽기 가능
    • request.POST.get('content')는 POST로 전송된 폼(form) 데이터 항목 중 content 값을 의미함
  • 답변 생성을 위해 question.asnswer_set.create를 사용

    • question.answer_set은 질문의 답변을 의미, answer 모델과 Question 모델이 서로 ForeignKey로 연결되어 있으므로 이처럼 사용이 가능

뷰 함수의 또 다른 방법

위에서 정의한 views.answer_create함수를 생성하는 방법은 다음처럼 Answer 모델을 직접 사용하는 방법이다.

(... 생략 ...)
from .models import Question, Answer #.models에서 Answer import 부분 추가

(... 생략 ...)

def answer_create(request, question_id):
    """
    pybo 답변등록
    """
    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('pybo:detail', question_id=question.id)
  • 위 방식과 아래 방식 어떤 것을 사용해도 결과는 동일

  • 답변을 생성하고 질문 상세 화면을 다시 보여주기 위해 redirect 함수를 사용

  • redirect 함수는 페이지 이동을 위한 함수로, 여기에선 pybo:detail 별칭에 해당하는 페이지로 이동하기 위해 사용함

  • pybo:detail 별칭에 해당하는 URL(예: http://locahost:8000/pybo/answer/create/2/)은 question_id(여기선 2)가 필요하므로 question.id를 인수로 전달


답변 저장

앞서 포스팅한 내용들을 모두 실행한 후 질문 상세 화면을 다시한번 호출하면, 위와 같은 화면이 뜬다 !!

텍스트 창에 아무 값이나 입력하고 답변을 등록해보면, 화면에는 아무 변화가 없다.
왜냐면 아직 등록된 답변을 표시하는 기능을 템플릿에 추가하지 않았기 때문 !!


답변 조회

등록된 답변을 질문 상세 화면에 표시하려면 다음과 같이 질문 상세 템플릿(question_detail.html)을 수정해야함

<h1>{{ question.subject }}</h1>
<div>
    {{ question.content }}
</div>

<!--여기서부터 추가된 내용-->
<h5>{{ question.answer_set.count }}개의 답변이 있습니다.</h5>
<div>
    <ul>
    {% for answer in question.answer_set.all %}
        <li>{{ answer.content }}</li>
    {% endfor %}
    </ul>
</div>

<form action="{% url 'pybo:answer_create' question.id %}" method="post">
{% csrf_token %}
<textarea name="content" id="content" rows="15"></textarea>
<input type="submit" value="답변등록">
</form>
  • 질문에 등록된 답변을 확인가능한 영역을 추가함

  • question.answer_set는 질문과 연결된 답변들이고, question.answer_set.count는 답변의 총 개수를 의미함

위처럼 question_detail.html을 수정하고 다시 질문 상세 화면을 호출하면 다음과 같은 화면을 볼 수 있다 !!!

답변등록 창에 "제가 지금 열심히 장고로 사이트를 만들고 있습니다 !!"를 입력하고 답변등록 버튼을 클릭하니 자동으로 위에 "1개의 답변이 있습니다."와 함께 입력한 답변이 뜬다 !!

이제 답변을 저장하고 확인이 가능해진 것이다 !!!

profile
공부합시당

0개의 댓글