21. 폼(Forms)

data_hamster·2023년 4월 25일

학습주제
폼(Forms)

학습내용
거존 페이지를 활용하여 사용자들에게 투표를 받아보기로 한다.

복습







만약 하다가 헷갈리면 이전 수업을 다시 듣자.
form은 정보를 받아서 제출하는 양식 역할을 한다.
form 내에 action은 제출 시, 이동할 주소를 입력한다.
method는 검색엔진의 경우 get을 사용하지만, 보통은 post를 사용한다.
form 마지막에 input을 만들어주는데, 제출할 것이기 때문에 type="submit"으로 해주고, 그 버튼 이름은 value="Vote"로 해준다.
이제 choice들을 선택지(radio)로 받을 것이기에, ul, li 태그는 모두 지워준다.
input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}"
input 관련 매개변수를 각기 보면
type은 원형의 선택버튼인 radio
name, value는 submit할 때 변수 이름과 그 값에 해당.
id는 한 페이지 내에 고유한 이름.
{{ forloop.counter }} for 문이 돌면서 숫자가 1부터 1씩 증가함.
특이하게 "문자열{{변수}}"를 인식해줌.
이렇게 동적으로 choice의 갯수에 맞춰 input type="radio" 태그를 생성해준다. 또한 id도 동적으로 생성해주고, value도 그 choice.id가 저장된다.
label은 설명해주기 위한 태그인데, for을 사용하여 눌렀을 시 해당 id 값을 갖는 태그로 이동한다
아직 action값은 지정되지 않았다.

<form action="#" method='post'>
	<h1>{{question.question_text}}</h1>
    
    {% for choice in question.choice_set.all %}
    	<input type="radio" name="choice" id="choice{{ forloop.counter }}" value={{ choice.id }}
        <label for="choice{{ forloop.counter }}">
        	{{choice.choice_text}}
        </label>
        <br>
    {% endfor %}
<input type="submit" value="Vote">
</form>


선택지가 없을 경우, for 문이 돌지 않고, 제출버튼만 남아 있다.

for문이 돌면서 input, label, br태그를 생성해준 것을 확인할 수 있다.
id에도 choice1, choice2 가 forloop.counter 통해 구현된 것을 확인.
value로 choice.id 값도 잘 들어감.


제출을 누르자다음과 같은 오류가 뜬다.
너가 이 폼을 제출할 토큰이 없는데 왜 제출을 하려고 하니? 라는 뜻.
이 토큰은 서버에서 그려준 폼에서만 값을 제출할 수 있도록 방어하는 역할을 함.
{% csrf_token %}을 사용하면 자동으로 토큰이 생성.

새로고침 후 보면, 이렇게 히든으로 어떤 값이 생성됨. 장고에서 자동으로 만들어둔 토큰. 이 값을 함께 올려줘야. 내가 허락한 폼에서 값이 올라왔구나 라고 인식.
별다른 동작은 하지 않음.

이제 제출했을 때 제출을 받아줄 url을 구현함. urls.py로 이동해 새로운 path를 만든다.
똑같이 question_id는 받아야 한다. 어떤 질문에 대한 답변인지 알아야 하기 때문이다.
path('<int:question_id>vote/', views.vote, name='vote')

views.py로 넘어간다.
vote 메소드를 정의해준다.
detail 메소드와 마찬가지로 request, question_id를 인자로 받는다.
question도 위와 동일하게 id로 찾고, 이제 선택값을 알아야하는데,
이는 choice_set에서 get 메서드로 pk값을 통해 찾는다.
pk값은 detail.html에서 choice를 이용한 for 문을 확인해보면,
input 태그의 value에 choice.id로 원하는 값이 저장된다.
이를 호출할 name은 choice이다.
저 vote는 폼을 통한 리퀘스트가 발생하였으므로,
리퀘스트에는 dict 타입의 POST가 저장되어 있는데,
키값을 choice로 넣어 찾는다.
이렇게 선택된 choice를 찾았으면,
이 선택된 choice 오브젝트의 vote값을 1 증가시킨다.
증가값은 아직 메모리에만 반영되어 있기 때문에 .save()를 통해 테이블에도 반영시킨다.
그리고 마지막으로 사용자를 어떤 페이지로 보내준다. HttpResponseRedirect를 사용한다.

import 필요함.
괄호 안에 reverse를 이용. polls:index로 보내주면 이전 목록으로 이동한다.

def vote(request, question_id):
	question = get_object_or_400(Question, pk=question_id)
    selected_choice = question.choice_set.get(pk=request.POST['choice'])
    selected_choice.votes += 1
    selected_choice.save()
    return HttpResponseRedirect(reverse('polls:index'))

detail.html로 넘어간다.
form 태그의 action에 이제 입력을 넣어준다. 템플릿 태그를 이용한다.
<form action={% url 'polls:vote' question.id %} method='post'>

vs code 전체 저장 후, 웹페이지에서 선택 후 vote를 누른다.



admin 페이지에서 Votes값이 1 증가한 것을 확인할 수 있다.

만일 아무것도 선택하지 않고 제출을 한다면?

choice를 읽어올 수 없기 때문에 에러가 발생함.

이런 경우에 대한 방어 코드를 넣어보려고 한다.

view에서 choice가 없는 경우. 현재 폼에 그대로 사용자를 남겨 놓으면서 에러메세지만 표현한다. 따라서 except에서 에러메세지와 함께 현재 폼을 그대로 그려주는 작업을 한다. 에러메세지를 그려주는 작업은 다음에 구현한다.

try:
	selected_choice = question.choice_set.get(pk=request.POST['choice'])
except (KeyError, Choice.DoesNotExist):
   return render(request, 'polls/detail.html', {'question': question, 'error_message': '선택이 없습니다.')
else:
	selected_votes += 1
    selected_choice.save()
    return HttpResponseRedirect(reverse('polls:index'))

다시 웹페이지로 돌아가 선택하지 않고 제출한다.

아무것도 발생하지 않는다. (다시 지금 화면을 그려주었기 때문). 에러메세지를 주었지만 따로 표시하고 있지 않기 때문.

detail.html로 넘어가 넘어온 에러메세지가 있을 경우 표시하도록 한다.

	{% if error_message %}
    <p><strong> {{ error_message }} </strong></p>
    {% endif %}

업로드중..

어려운 점

값의 이동이 생각보다 헷갈린다. 눈으로 보기보다 코드를 직접 입력해가며 여러번 작성해보는 것이 좋겠다.

profile
반갑습니다 햄스터 좋아합니다

0개의 댓글