폼을 가지고 작업하기

이 문서에 대하여

이 문서는 Django의 폼 처리 기능에 대해 안내합니다. 폼 API의 특정한 부분에 대해 자세히 살펴보려면 The Forms API, Form fields, Form and field validation를 참고하세요.

django.forms은 Django의 폼 처리 라이브러리입니다.

Django의 HttpRequest 클래스를 사용하여 폼 제출을 처리할 수도 있기는 하지만, 폼 라이브러리를 사용하려면 폼에 관련된 많은 일반적인 작업에 신경을 써야 합니다. 그것을 사용하면,

  1. 자동으로 생성된 폼 위젯을 통해 HTML 폼을 표시할 수 있습니다.
  2. 제출된 데이터를 검증 규칙에 비추어 확인할 수 있습니다.
  3. 검증 오류의 경우에는 폼을 다시 표시합니다.
  4. 제출된 폼 데이터를 그에 관련된 파이썬 자료형으로 변환합니다.

개요

폼 라이브러리는 이러한 개념을 다룹니다.

Widget
<input type="text"> 또는 <textarea>와 같이 HTML 폼 위젯에 대응되는 클래스. 위젯을 HTML로 렌더링하는 것을 처리.
Field
유효성 검증을 담당하는 클래스. 예를 들어, EmailField는 데이터가 유효한 이메일 주소인지 확인함.
Form
그 자체에 대한 유효성 검증 규칙 및 HTML로서의 표시 방법을 알고 있는 필드의 모음.
Form Media
폼을 렌더하기 위해 필요한 CSS와 JavaScript 자원.

폼 라이브러리는 데이터베이스 계층, 뷰 및 템플릿과 같은 Django 구성요소로부터 분리되어 있습니다. 의존성을 갖는 것은 Django 설정과, 두 개의 django.utils 도움 함수와 Django의 국제화 훅 뿐입니다(하지만 이 라이브러리를 사용하기 위해서 국제화 기능을 사용하여야하는 것은 아닙니다).

폼 개체

폼 개체는, 폼이 받아들여지기 위해 반드시 갖추어야 하는 폼 필드의 순서와 유효성 검증 규칙의 모음을 캡슐화합니다. 폼 클래스는 django.forms.Form의 하위 클래스로서 생성되며 선언적인 스타일을 사용합니다. Django의 데이터베이스 모델을 사용해보았다면 이러한 스타일에 익숙할 것입니다.

예로서, “contact me” 기능을 구현하기 위해서 폼을 사용하는 것을 고려해봅시다.

from django import forms

class ContactForm(forms.Form):
    subject = forms.CharField(max_length=100)
    message = forms.CharField()
    sender = forms.EmailField()
    cc_myself = forms.BooleanField(required=False)

폼은 Field 개체들로 구성됩니다. 예제에서는 폼에 subject, message, sender 그리고 cc_myself까지 네 개의 필드가 있습니다. CharField, EmailField 그리고 BooleanField의 세 가지 필드 유형을 사용할 수 있습니다. 전체 목록은 Form fields에서 찾을 수 있습니다.

폼을 Django 모델에 직접적으로 추가 또는 수정하는 데에 사용하려면, ModelForm을 사용하여 모델 설명의 중복을 피할 수 있습니다.

뷰에서 폼을 사용하기

뷰에서 폼을 처리하기 위한 일반적인 패턴은 다음과 같습니다.

def contact(request):
    if request.method == 'POST': # 폼이 제출되었을 경우...
        form = ContactForm(request.POST) # 폼은 POST 데이터에 바인드됨
        if form.is_valid(): # 모든 유효성 검증 규칙을 통과
            # form.cleaned_data에 있는 데이터를 처리
            # ...
            return HttpResponseRedirect('/thanks/') # Redirect after POST
    else:
        form = ContactForm() # An unbound form

    return render_to_response('contact.html', {
        'form': form,
    })

다음과 같은 세 코드 경로가 있습니다.

  1. 폼이 아직 제출되지 않았을 경우, 바인드되지 않은 ContactForm이 생성되어 템플릿에 전달됩니다.
  2. 폼이 제출된 경우, 폼의 바인드된 인스턴스가 request.POST를 사용하여 생성됩니다. 제출된 자료가 유효한 경우에는 처리되어 사용자에게 “thanks” 페이지가 리다이렉트됩니다.
  3. 폼이 제출되었지만 유효하지 않은 경우, 바인드된 폼 인스턴스가 템플릿에 전달됩니다.

바인드된(bound) 폼과 바인드되지 않은(unbound) 폼의 차이는 중요합니다. 바인드되지 않은 폼은 그에 연관된 어떠한 데이터도 갖지 않으며, 사용자에게 렌더될 때에는 비어 있거나 기본값을 갖습니다. 바인드된 폼은 제출된 자료를 갖고 있으며, 데이터가 유효한지 사용자에게 알려줄 수 있습니다. 유효하지 않은 바인드된 폼이 렌더될 때는, 사용자에게 무엇이 잘못되었는지 알리는 오류 메시지를 포함할 수 있습니다.

바인드된 폼과 바인드되지 않은 폼 사이의 차이점에 대한 자세한 정보는 Bound and unbound forms를 참고하세요.

폼으로 파일 업로드 다루기

폼을 통해 파일 업로드를 다루는 방법은 Binding uploaded files to a form에서 볼 수 있습니다.

폼에서 데이터를 처리하기

is_valid()True를 반환하였다면, 폼에서 정의된 유효성 검증 규칙에 들어맞는다는 것을 의미하므로, 폼 제출을 안전하게 처리할 수 있습니다. 이떄 request.POST에 접근할 수는 있지만, form.cleaned_data에 접근하는 것을 권장합니다. 이 데이터는 유효성 검증을 거쳤을 뿐만 아니라 적합한 파이썬 유형으로 변환도 되어 있습니다. 위의 예제에서, cc_myself는 불린 값이 될 것입니다. 마찬가지로, IntegerFieldFloatField는 각각 파이썬의 int와 float로 변환됩니다. 읽기 전용인 데이터는 form.cleaned_data에서 사용할 수 없다는 점에 (또한 맞춤 clean() 메소드에 값을 설정하는 것은 아무런 영향을 끼치지 않음에) 주의하시기 바랍니다. 왜냐하면 이러한 필드들은 input 엘리먼트가 아닌 텍스트로 표시되며, 서버에 다시 포스트되지 않기 때문입니다.

위의 예제를 확장하여, 폼 데이터가 어떻게 처리될 수 있는지 살펴봅시다.

if form.is_valid():
    subject = form.cleaned_data['subject']
    message = form.cleaned_data['message']
    sender = form.cleaned_data['sender']
    cc_myself = form.cleaned_data['cc_myself']

    recipients = ['info@example.com']
    if cc_myself:
        recipients.append(sender)

    from django.core.mail import send_mail
    send_mail(subject, message, sender, recipients)
    return HttpResponseRedirect('/thanks/') # Redirect after POST

Django에서 이메일을 보내는 자세한 방법에 대해서는 Sending email을 참고하기 바랍니다.

템플릿을 사용하여 폼을 보이기

폼은 Django 템플릿 언어와 함께 사용하도록 만들어졌습니다. 위의 예제에서, 우리는 문맥 변수 form을 사용하여 ContactForm 인스턴스를 템플릿에 전달했습니다. 템플릿의 간단한 예는 다음과 같습니다.

<form action="/contact/" method="post">{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Submit" />
</form>

폼은 그에 속한 필드만 출력합니다. <form> 태그로 둘러싸고 제출 버튼을 다는 일은 여러분이 직접 해주어야합니다.

폼과 교차 사이트 요청 위조 방지

Django는 사용하기 쉬운 교차 사이트 요청 위조 방지 기능을 탑재하고 있습니다. POST를 통해 폼을 제출할 때 CSRF 방지를 사용하려면 위의 예제와 같이 csrf_token 템플릿 태그를 사용하여야 합니다. 하지만, CSRF 방지는 템플릿에서 폼에 직접 관련되지 않으므로, 이 문서에서 다루는 이후의 예제에서는 이 태그를 생략하겠습니다.

form.as_p는 각각의 폼 필드와 라벨을 함께 문단(역자 주: paragraph, 즉 <p> 태그)으로 감싸서 출력합니다. 예제 템플릿의 출력은 다음과 같습니다.

<form action="/contact/" method="post">
<p><label for="id_subject">Subject:</label>
    <input id="id_subject" type="text" name="subject" maxlength="100" /></p>
<p><label for="id_message">Message:</label>
    <input type="text" name="message" id="id_message" /></p>
<p><label for="id_sender">Sender:</label>
    <input type="text" name="sender" id="id_sender" /></p>
<p><label for="id_cc_myself">Cc myself:</label>
    <input type="checkbox" name="cc_myself" id="id_cc_myself" /></p>
<input type="submit" value="Submit" />
</form>

각 폼 필드는 ID 어트리뷰트로 id_<field-name> 값을 가지며, 함께 오는 라벨 태그가 이를 참조함을 눈여겨보시기 바랍니다. 스크린 리더 소프트웨어와 같이 장애인을 위한 접근성을 제공하는 기술을 위해 중요합니다. customize the way in which labels and ids are generated를 참고하세요.

또한 form.as_table을 사용하여 테이블의 행을 출력할 수 있으며(이 때에도 <table> 태그는 직접 써주어야 합니다), form.as_ul을 사용하여 목록 아이템을 출력할 수 있습니다.

폼 템플릿 커스터마이징

디폴트로 생성된 HTML이 구미에 맞지 않다면, Django 템플릿 언어를 사용하여 원하는 대로 완전히 고칠 수 있습니다. 앞의 예제를 확장하여 보도록 하겠습니다.

<form action="/contact/" method="post">
    {{ form.non_field_errors }}
    <div class="fieldWrapper">
        {{ form.subject.errors }}
        <label for="id_subject">Email subject:</label>
        {{ form.subject }}
    </div>
    <div class="fieldWrapper">
        {{ form.message.errors }}
        <label for="id_message">Your message:</label>
        {{ form.message }}
    </div>
    <div class="fieldWrapper">
        {{ form.sender.errors }}
        <label for="id_sender">Your email address:</label>
        {{ form.sender }}
    </div>
    <div class="fieldWrapper">
        {{ form.cc_myself.errors }}
        <label for="id_cc_myself">CC yourself?</label>
        {{ form.cc_myself }}
    </div>
    <p><input type="submit" value="Send message" /></p>
</form>

각각의 명명된 폼 필드는 {{ form.name_of_field }}를 사용하여 템플릿으로 출력되며, 폼 위젯을 표출하기 위한 HTML을 만들어냅니다. {{ form.name_of_field.errors }}를 사용하여 폼 오류의 목록을 표출하고, 정렬되지 않은 목록(역자 주: unordered list, 즉 ul)으로 렌더링합니다. 이는 다음과 같이 보일 것입니다.

<ul class="errorlist">
    <li>Sender is required.</li>
</ul>

위의 목록은 그 외양을 정의할 수 있도록 errorlist의 CSS 클래스를 갖습니다. 오류의 표출을 더 커스터마이즈하고자 한다면 다음과 같이 루핑을 할 수 있습니다.

{% if form.subject.errors %}
    <ol>
    {% for error in form.subject.errors %}
        <li><strong>{{ error|escape }}</strong></li>
    {% endfor %}
    </ol>
{% endif %}

폼의 필드에 대하여 루핑

폼 필드 각각에 대하여 동일한 HTML을 사용한다면, {% for %} 루프를 사용하여 각 필드에 대하여 중복으로 발생하는 코드를 줄일 수 있습니다.

<form action="/contact/" method="post">
    {% for field in form %}
        <div class="fieldWrapper">
            {{ field.errors }}
            {{ field.label_tag }}: {{ field }}
        </div>
    {% endfor %}
    <p><input type="submit" value="Send message" /></p>
</form>

이 루프 내에서, {{ field }}BoundField의 인스턴스입니다. BoundField는 다음과 같이 템플릿에서 유용한 어트리뷰트를 갖습니다.

{{ field.label }}
필드의 라벨. 예: Email 주소.
{{ field.label_tag }}
필드의 라벨로서, 적절한 HTML <label> 태그로 감싼 것. 예: <label for="id_email">Email 주소</label>
{{ field.value }}
필드의 값. 예: someone@example.com
{{ field.html_name }}
입력 엘리먼트의 이름 필드에 사용될 필드의 이름. 설정되지 않은 경우, 폼 접두어를 취함.
{{ field.help_text }}
필드에 연관된 도움말 텍스트.
{{ field.errors }}
이 필드에 관련된 유효성 검증 오류를 포함하는 <ul class="errorlist">를 출력. {% for error in field.errors %} 루프를 사용함으로써 오류의 표현을 커스터마이즈할 수 있음. 이 경우, 루프의 각 개체는 오류 메시지를 담은 간단한 문자열임.
field.is_hidden
이 어트리뷰트는 필드가 숨김 필드이면 True이고, 그렇지 않으면 False임. 템플릿 변수로서는 그리 유용하지 않지만, 다음과 같은 조건 검사에 유용함.
{% if field.is_hidden %}
   {# Do something special #}
{% endif %}

숨김 필드 또는 보임 필드에 대하여 루핑하기

Django의 기본 폼 레이아웃에 의존하지 않고 수작업으로 템플릿에서 폼의 레이아웃을 잡는다면, 숨김 필드(<input type="hidden">)와 보임 필드를 서로 다르게 취급하고 싶을 수도 있습니다. 한 가지 예로서, 숨김 필드는 아무 것도 보여주지 않으므로, 그 필드의 “옆에” 오류 메시지를 집어넣는다면 사용자에게 혼란을 초래할 것이기 때문에 이러한 필드는 다르게 취급해야 할 것입니다.

Django에는 숨김 필드 및 보임 필드에 대하여 독립적으로 루핑할 수 있는 두 가지 메소드, hidden_fields()visible_fields()가 있습니다. 두 메소드를 사용하도록 앞의 예제를 수정하여보겠습니다.

<form action="/contact/" method="post">
    {# 숨김 필드 #}
    {% for hidden in form.hidden_fields %}
    {{ hidden }}
    {% endfor %}
    {# 보임 필드 #}
    {% for field in form.visible_fields %}
        <div class="fieldWrapper">
            {{ field.errors }}
            {{ field.label_tag }}: {{ field }}
        </div>
    {% endfor %}
    <p><input type="submit" value="Send message" /></p>
</form>

이 예제에서는 숨김 필드에서 발생하는 어떠한 오류에 대해서도 처리를 하지 않습니다. 보통 숨김 필드에서 발생하는 오류는 폼이 조작되었을 가능성을 암시하는데, 그 이유는 일반적인 폼 인터랙션은 숨김 필드를 변경하지 않기 때문입니다. 하지만, 오류 표시를 삽입하는 것도 쉽게 할 수 있습니다.

폼 템플릿의 재사용

만일 여러분의 사이트에서 폼에 대한 동일한 렌더링 로직을 여러 곳에서 사용한다면, 독립적인 템플릿에서 폼의 루프를 줄이고 include 태그를 써서 다른 템플릿에서 재사용하는 방법으로 중복을 줄일 수 있습니다.

<form action="/contact/" method="post">
    {% include "form_snippet.html" %}
    <p><input type="submit" value="Send message" /></p>
</form>

# In form_snippet.html:

{% for field in form %}
    <div class="fieldWrapper">
        {{ field.errors }}
        {{ field.label_tag }}: {{ field }}
    </div>
{% endfor %}

템플릿에 전달된 폼 개체가 문맥상 다른 이름을 갖는다면, include 태그의 with 인자를 써서 별칭을 붙일 수 있습니다.

<form action="/comments/add/" method="post">
    {% include "form_snippet.html" with form=comment_form %}
    <p><input type="submit" value="Submit comment" /></p>
</form>

이러한 일을 자주 수행한다면, 맞춤 inclusion tag를 만드는 것을 고려해볼 수 있습니다.