터칭 데이터

장고(Django) 폼(Forms) 본문

장고 (Django)

장고(Django) 폼(Forms)

터칭 데이터 2023. 11. 3. 17:33

투표기능을 만들자

 

지금까지 우리가 작성한 상세 페이지는 해당 question의 choice가 무엇이 있는지 조회만 해볼 수 있었는데요.

이제는 question의 상세 페이지에서 choice를 직접 선택해 투표할 수 있는 기능을 만들어봅시다.

 

 

 

 

 

 

먼저 브라우저에서는 위와 같이 뜨는 투표창을 만들었습니다.

 

 

위와 같은 투표창을 만들기 위해

detail.html 템플릿에서 아래와 같이 코드를 작성했습니다.

 

 

갑자기 코드가 늘어났죠?

 

하나하나 설명해드리겠습니다.

 

먼저 빨간색 박스 action은 form에 입력된 데이터가 서버의 어떤 URL로 도착할지 적는 곳입니다. 현재는 실습의 초기단계기 때문에 일부러 #을 써서 비워뒀습니다.

 

파란색 박스 input 태그의 value는 대개는 초기값을 나타냅니다. 예를 들어 input의 타입이 text인 이름을 입력하는 입력란의 value가 홍길동이라면 입력란의 초기값은 홍길동이라는 뜻입니다. 그런데 input의 타입이 radio일 때의 value는 해당하는 선택지를 선택했을 때 전달될 값을 의미합니다. 예를 들어 위에서 커피의 radion input을 선택하고 Vote를 누르면 커피 choice의 id가 전달된다는 뜻입니다. 녹차였다면 녹차 choice의 id가 전달됩니다.

 

초록색 박스에서 label의 for는 input의 id를 지칭합니다.

자세한 설명은 이곳을 참고해주세요.

그리고 forloop.counter 를 볼 수 있는데 장고에서 제공하는 기능으로 반복문이 실행될 때마다 숫자를 1씩 늘립니다. counter는 1부터 시작하고 counter0은 0부터 시작합니다.

 

forloop에는 counter오 counter0이외의 기능들이 있으며

이곳에서 확인해볼 수 있으니 참고해주세요.

 

 

개발자 도구로 더 상세히 들여다보면 input과 label이 for문 loop만큼(question에 있는 choice들의 수만큼) 반복되어 그려졌고 각각의 input과 label의 id와 for가 forloop.counter를 따라 choice+숫자로 속성이 잡혔습니다. 또 input의 value를 보면 12와 13이라 적혀있는데 이는 커피라는 choice와 녹차라는 choice의 id를 의미합니다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

장고와 CSRF 토큰(CSRF Token)

 

그런데 브라우저에서 choice중 하나를 선택해 제출을 하면

 

위와 같이 403에러가 뜨는데요

403에러는 보통 서버에서 서버에서 설정해 둔 권한과 맞지 않는 접속 요청이 들어올 때 뜨는 에러입니다. 한마디로 권한이 없다는 뜻입니다.

 

그런데 자세히보면 CSRF 인증이 실패해 요청을 거절했다고 설명합니다.

CSRF에 대한 설명은 이곳을 참고해주세요.

쉽게 요약하면 인증된 사용자를 이용해 그 사용자의 의도와 무관하게 사이트를 공격하거나 위험한 요청을 하도록 하는 해킹기법입니다.

 

CSRF의 해킹 방어 방법은 여러가지가 있지만 장고가 제공하는 CSRF 방어를 사용하는 방법을 간단히 알아보겠습니다.

 

 

 

 

detail 템플릿에서

{% csrf_token %}을 입력하면

 

 

 

브라우저에서 새로고침을하고 개발자 도구로 살펴보면 새로운 input 태그가 hidden 타입으로 추가되었는데요. value를 살펴보면 난해한 코드가 있습니다. 저 value는 장고에서 자체적으로 만든 토큰입니다. 해당 value를 같이 보내야 장고에서는 내가 허락한 form에서 값이 올라왔다고 판단합니다. 이제는 Vote 버튼을 눌러줘도 에러가 뜨지 않습니다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

폼에서 제출받은 내용(데이터)을 받는 방법

 

detail에서 vote 버튼을 누르니 에러가 나지는 않지만 아무런 변화도 없습니다.

우리가 form의 action 속성을 그냥 '#'으로 줬었기 때문인데요.

 

이제는 choice를 선택하고 vote를 누르면 실제로 그 투표가 DB에 반영되도록하는 코드를 짜보겠습니다.

 

 

 

 

 

url.py에서 위와 같이 http://127.0.0.1:8000/polls/숫자/vote 라는 URL을 뷰(views)의 vote 메서드가 처리하도록 하는 URL 별칭을 vote로 하는 path를 작성합니다.

 

 

 

 

그리고 views.py에서 vote 메서드를 작성합니다.

1. URL에서 전달받은 question_id를 이용해 해당하는 question을 찾습니다.

2. vote로 전달 받은 request에서 POST 방식으로 전달된 name="choice_id"의 value를 이용해 투표로 선택된 choice를 찾아냅니다.

3. 그렇게 찾아낸 selected_choice의 votes필드의 값을 1 늘려주고 메모리상에만 남지 않고 DB에도 반영되도록 save()를 실행시켜줍니다.

4. 작업이 끝났다면 HttpResponseRedirect를 이용해 question 전체 목록이 뜨는 index 페이지로 돌려보내줍니다. reverse['네임스페이스:URL별칭']을 이용했습니다.

 

 

 

 

 

 

detail.html 템플릿에서는 폼(form)의 action 속성을 urls.py에서 작성한대로 '네임스페이스:URL별칭'으로 바꾸고 question.id를 전달하도록 합니다. question.id의 question은 우리가 지난 시간 views의 index 메서드에서 render의 세번째 인자로 context 형식으로 담은 키를 얘기합니다.

 

 

 

 

 

브라우저를 새로고침합니다. (왜냐하면 form의 action 속성이 아직 '#'이기 때문입니다.)

 

커피를 선택하고 Vote 버튼을 누를겁니다.

 

 

버튼을 누르기 전의 커피의 득표 수는 0입니다.

 

 

 

 

 

이제 커피를 선택하고 Vote를 누르면

 

 

 

 

결과가 잘 반영됩니다.

 

 

 

 

 

 

 

 

 

아무 것도 선택하지 않는 경우 방어 코드

 

아무 것도 선택하지 않고 버튼을 누르면

 

 

뷰에서 choice를 읽을 수 없어 에러가 뜹니다.

 

 

 

 

이를 방어하는 코드를 작성해보겠습니다.

 

 

뷰에서 에러가 발생하면 현재 페이지에 머무르게 하기위해 polls/detail.html로 보내되 error_message를 키로 하는 '선택이 없습니다'라는 문자열 값을 render의 context로 전달합니다.

 

 

 

 

 

템플릿에서는 전달 받은 context에서 error_message라는 키가 있다면 에러가 있다는 뜻이므로

error_message를 h1태그에 감싸진 질문의 제목 밑에 error_message키의 값이었던 '선택이 없습니다' 문자열을 출력합니다.

 

 

 

아무 것도 선택하지 않고 버튼을 누르면 위와 같이 의도한대로 출력됩니다.