본문 바로가기

Minding's Programming/Django

[Django] 폼(Forms)

728x90
반응형

폼(Forms)

폼은 사용자로부터 데이터를 받을 수 있는 형식을 말한다. 지난 시간([Django] 뷰(views)와 템플릿(templates))에서 다룬 상세 페이지에서 선택지가 단순히 노출되는 것이 아닌 사용자로부터 선택할 수 있게 만들어 보려고 한다.

 

detail.html 수정

<form action="{% url 'polls:vote' question.id %}" method="post">
    {% csrf_token %}
    <h1>{{ question.question_text }}</h1>
    {% if error_message %}
    <p><strong>{{ error_message }}</strong></p>
    {% endif %}
    
    {% 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>

form의 태그는 데이터를 제출할 용도이기 때문에 post로 지정해준다. input type을 radio로 하는 투표 형태의 input 태그를 설정해준다. 만약 선택하지 않고 submit을 했을 경우 error message가 노출된다. 실제로 form이 액션을 취하는 링크는 polls의 vote라는 path를 따른다고 한다.

 

urls.py 수정

from django.urls import path
from . import views

app_name = 'polls'

urlpatterns = [
    path('', views.index, name='index'),
    path('<int:question_id>/', views.detail, name='detail'),
    path('<int:question_id>/vote/', views.vote, name='vote'), 
]

위 html파일에서 vote라는 path를 만들어주기 위해 urls.py도 수정해준다. detail과 같이 question_id라는 이름으로 정수를 전달한다. 해당 링크는 views.py의 vote 함수를 작동시켜 사용자에게 전달한다.

 

views.py 수정

from django.shortcuts import render
from django.http import HttpResponse
from .models import Question
from django.shortcuts import render, get_object_or_404
from django.http import HttpResponseRedirect
from django.urls import reverse
from .models import Choice

...

def vote(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    try:
        selected_choice = question.choice_set.get(pk=request.POST['choice'])
    # Choice.DoesNotExist 사용한 이유: 오류 또는 삭제로 없는 값이 제출되는 것 방어
    except (KeyError, Choice.DoesNotExist):
        return render(request, 'polls/detail.html', {'question': question, 'error_message': '선택이 없습니다.'})
    else:
        selected_choice.votes += 1
        selected_choice.save()
        return HttpResponseRedirect(reverse('polls:index'))

vote라는 함수를 작성했다. 질문을 우선 노출시킨 뒤, 선택된 항목이 있으면 해당 항목(레코드)의 votes를 1 더하고 저장한다. 만약 선택되지 않은 상태라면 에러메시지를 전달해 노출시킨다. detail.html의 상단에 보면 {%if error_message %} 부분이 있는데, 이 메시지가 전달될 때에만 노출된다는 뜻이다.

 

구현된 페이지는 위와 같다. 만약 선택하지 않은채로 Vote 버튼을 누른다면,

'선택이 없습니다.'라는 메시지가 노출되게 된다.

 

다른 서버에서 동시에 같은 폼을 제출했을 때

만약 A서버와 B서버 모두에서 동시에 같은 답변을 제출했다고 생각해보자.

    else:
        selected_choice.votes += 1
        selected_choice.save()

위 코드에서 selected_choice의 값는 A서버와 B서버 모두 같기 때문에 두 서버의 답변 카운트가 정상적으로 반영되지 않을 가능성이 있다. 이를 방지하기 위해서는 아래와 같은 방법을 사용한다.

 

F 메소드 사용

from django.db.models import F

...

    else:
        selected_choice.votes = F('votes') + 1
        selected_choice.save()
        # POST 데이터를 정상적으로 처리했으면, 다른 페이지로 리다이렉트
        return HttpResponseRedirect(reverse('polls:index'))

F()는 해당 코드가 실행될 때 해당 값을 DB에서 불러와 처리한다는 것을 의미한다. 메모리에 저장된 값이 아닌 DB에서 바로 불러오기 때문에, 두 서버에서 동시에 폼을 제출해도 문제 없이 처리할 수 있다.

728x90