Programming

; develop a program

Framework

[Django] 장고(Django) 기본요소 - 데이터 저장

Clloud_ 2022. 11. 28. 18:15
반응형

이번 포스팅에서는 장고의 기본 요소 중 하나인 URL 별칭에 대하여 공부를 해보고자 한다.

 


답변 등록 폼

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

 

[파일명: projects\mysite\templates\pybo\question_detail.html]

<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 값과 해커가 일방적으로 보낸 csrf_token 값이 일치하지 않기 때문에 블록킹 될 것이다.

따라서 form 태그 바로 밑에 {% csrf_token %} 태그를 항상 위치시켜야 한다.

 

POST 요청 시 form 태그에 csrf_token이 없으면 장고는 오류를 낸다.

 


CSRF(cross site request forgery)

 

CSRF는 웹 사이트 취약점 공격을 방지를 위해 사용하는 기술이다.

장고가 CSRF 토큰 값을 세션을 통해 발행하고 웹 페이지에서는 폼 전송 시에 해당 토큰을 함께 전송하여 실제 웹 페이지에서 작성된 데이터가 전달되는지를 검증하는 기술이다.

 

csrf_token 사용을 위해서는 CsrfViewMiddleware 미들웨어가 필요한데 이 미들웨어는 settings.py의 MIDDLEWARE 항목에 디폴트로 추가되어 있으므로 별도의 설정은 필요 없다.

 

[파일명: projects\mysite\config\settings.py]

(... 생략 ...)

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
(... 생략 ...)

만약 csrf_token 기능을 사용하고 싶지 않다면 이 부분을 주석 처리하면 된다.

 


URL 매핑

질문 상세 템플릿을 위와 같이 고친 후 질문 상세 페이지를 요청하면 answer_create 별칭을 찾을 수 없다는 오류가 발생한다.

질문 상세 템플릿에 {% url 'pybo:answer_create' question.id %}처럼 pybo:answer_create 별칭을 사용했기 때문이다.

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

 

[파일명: projects\mysite\pybo\urls.py]

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'),
]

answer_create 별칭에 해당하는 URL 매핑 규칙을 등록했다. 

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

 


View 함수

URL 매핑 규칙에 정의된 views.answer_create 함수를 pybo/views.py 파일에 다음처럼 추가한다.

 

[파일명: projects\mysite\pybo\views.py]

from django.shortcuts import render, get_object_or_404, redirect
from django.utils import timezone
from .models import Question

(... 생략 ...)

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('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은 질문의 답변을 의미한다.

Question과 Answer 모델은 서로 ForeignKey 로 연결되어 있기 때문에 이처럼 사용할 수 있다. 

 

답변을 저장하는 또 다른 방법은 다음처럼 Answer 모델을 직접 사용하는 방법이다.

(... 생략 ...)
from .models import Question, Answer

(... 생략 ...)

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 별칭에 해당하는 페이지로 이동하기 위해 redirect 함수를 사용했다.

그리고 pybo:detail 별칭에 해당하는 URL은 question_id가 필요하므로 question.id를 인수로 전달했다.

 


답변 저장

수정한 후 질문 상세 화면을 호출하면 다음과 같은 창이 출력된다.

 

살짝 엉성한 화면이지만 텍스트 창에 아무 값이나 입력하고 답변을 등록해 보면 화면에는 아무런 변화가 없다.

아직 등록된 답변을 표시하는 기능을 템플릿에 추가하지 않았기 때문이다.

 


답변 조회

등록된 답변을 질문 상세 화면에 표시하려면 질문 상세 템플릿을 수정해야 한다.

 

[파일명: projects\mysite\templates\pybo\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.count는 답변의 총개수를 의미한다.
question.answer_set는 질문과 연결된 답변들이다.

이렇게 수정하고 다시 질문 상세 화면을 호출하면 다음과 비슷한 화면이 출력된다.

 


반응형

'Framework' 카테고리의 다른 글

[Framework] 프레임워크(Framework)란?  (0) 2022.11.18