본문 바로가기
  • 실행력이 모든걸 결정한다
Django

[Django] Form 처리 방식 정리

by 김코더 김주역 2023. 2. 16.
반응형

1. 단순한 폼 처리

- 가장 1차원적인 방법이다.

- request.GET 또는 request.POST를 이용하여 <input> 태그의 name과 일치하는 데이터를 받아올 수 있다.

 

예시

from django.shortcuts import render

def index(request):
    n = request.POST['n']
    a = request.POST['a']
    
    try:
        n = int(n)
    except (ValueError):
        n = 0

    try:
        a = int(a)
    except (ValueError):
        a = 0

    context = {
        'n':n,
        'rangeA':range(a),
    }
    return render(request, 'users/index.html', context)

def givevar(request):
    return render(request, 'users/givevar.html')

 

</users/templates/users/givevar.html>

{% csrf_token %}는 form을 POST방식으로 전송할 때, 해커가 데이터를 위조하지 못하도록 보호해주는 키워드이다.

<form action="{% url 'users:index' %}" method="POST">
  {% csrf_token %}
  n : <input type="text" name="n"><br>
  a : <input type="text" name="a"><br>
  <input type="submit" value="제출">
</form>

 

 

 

2. 장고의 폼 클래스

- 장고에서는 폼 클래스를 통해 <form>을 만들 수도 있다.

- 폼 클래스는 폼을 기술하고 폼이 어떻게 작동하고 어떻게 보이는지를 결정한다.

- 폼 클래스의 필드도 클래스다. 필드는 폼 데이터를 저장하고 있으며, HTML의 <input>에 매핑된다. 그리고 폼이 제출되면 바로 유효성 검사를 실시하는데, 빈 데이터나 틀린 데이터를 넣으면 유효성 검사에 실패한다.

- 폼 객체는 보통 뷰 함수에서 생성한다.

- 폼 객체에는 데이터가 없을 수도 있다. 데이터가 없는 폼을 언바운드 폼이라고 하며, 사용자에게 보여질 때 비어있거나 기본값으로 채워진다. 반면에, 제출된 데이터를 갖고 있는 바운트 폼은 데이터의 유효성 검사를 하는 데 사용된다.

 

 

 

3. 일반 폼

Form 클래스를 상속받아 정의하는 방식이다.

 

1) 폼 클래스의 생성

- 폼 클래스는 django.forms.Form 클래스를 상속받아야 한다.

- 예를 들어, 아래 폼 클래스는 your_name이라는 하나의 필드를 가진다.

from django import forms

class NameForm(forms.Form):
    your_name=forms.CharField(label='Your name', max_length=50)

위의 폼 클래스는 기본적으로 아래와 같이 렌더링될 것이다.

<label for="id_your_name">Your name: </label>
<input type="text" name="your_name" maxlength="50" required id="id_your_name">
  • <label>의 for 속성과 <input>의 id 속성은 폼 클래스의 auto_id 속성의 디폴트값에 의해 앞에 'id_'가 붙게 된다. auto_id 속성에는 다른 문자열을 지정할 수도 있고 아예 False를 줘버릴 수도 있다.
  • Form 객체의 your_name 필드의 label 속성은 <label>의 레이블 텍스트와 매핑되었다. label 속성을 생략하더라도 장고가 적절한 레이블 텍스트로 채워준다. 
  • max_length 속성은 브라우저에서 사용자가 지정된 글자 수 이상을 입력하는 것을 막아주며, 장고가 폼 데이터를 받았을 때 데이터의 길이에 대한 유효성 검사도 수행하도록 한다.
  • <form> 태그나 submit 버튼은 개발자가 직접 템플릿에 넣어줘야 하는데, 이 부분은 잠시 후에 [(5) 템플릿 코드 작성]에서 다루기로 한다.

 

 

2) 위젯 클래스

- 대부분의 폼 필드는 디폴트 위젯 클래스를 갖고 있다.

- 예를 들어, CharField 필드 타입은 TextInput이 디폴트 위젯 클래스며, HTML의 <input type="text">와 매핑된다. 위젯을 <textarea>로 변경하려면 아래와 같이 widget 속성에 Textarea 클래스를 지정하면 된다.

your_name=forms.CharField(label='Your name', max_length=50, widget=forms.Textarea)

 

 

3) 유효성 검사 메소드

- 폼 클래스는 모든 필드에 대해 유효성 검사를 수행해주는 is_valid() 메소드를 갖는다.

- 모든 필드가 유효성 검사에 통과하면 is_valid() 메소드는 True를 반환하고 모든 폼 데이터를 cleaned_data 속성에 넣는다. cleaned_data는 딕셔너리 형태를 가진다.

- is_valid() 메소드가 False를 반환하더라도 유효성 검사에 통과한 필드는 cleaned_data 속성에 담긴다. 이러한 특성을 이용하여, 일부 필드에서 에러가 발생해서 다시 폼 데이터를 입력해야 하더라도, 에러가 없는 다른 필드들은 직전에 제출된 폼 데이터로 채워지도록 구현할 수 있다. 이 부분을 지금 [(4) 뷰에서 폼 클래스 처리]에서 구현해볼 것이다.

 

 

4) 뷰에서 폼 클래스 처리

- 장고에서는 폼을 보여주는 뷰와 제출된 폼을 처리하는 뷰를 하나의 뷰로 통합하여 처리하는 것을 권장한다. GET 방식으로 요청을 받은 경우에는 폼을 보여주도록 처리하고, POST 방식으로 요청을 받은 경우에는 제출받은 폼을 처리하도록 구현하면 된다.

def yourname(request):
    if request.method == 'POST':
        form=NameForm(request.POST)
        if form.is_valid():
            new_name=form.cleaned_data['your_name'] # cleaned_data에 있는 데이터 가져오기
            # new_name을 이용하여 추가 처리
            return HttpResponseRedirect('/home/') # 유효성 검사에 통과했으니 다음 뷰로 리다이렉트
    else: # GET 요청 시 빈 폼 객체를 생성
        form=NameForm()
    
    return render(request, 'name.html', {'form':form})

- 다시 한번 언급하지만, is_valid() 메소드가 False를 반환하더라도 유효성 검사에 통과한 필드는 form의 cleaned_data 속성에 담긴다. 위 코드에서 is_valid() 메소드가 False를 반환하면 마지막 라인의 render() 함수를 호출하기 때문에, 템플릿 시스템으로 전달되는 컨텍스트 변수 'form'에는 직전에 제출된 폼 데이터로 채워진다.

- 유효성 검사 전에 cleaned_data에 접근하면 에러가 발생한다.

- 생성 폼이 아닌 수정 폼인 경우에는 폼 클래스의 instance 인자에 수정 대상 모델 객체를 지정해주기만 하면 된다.

- 참고로, 폼 객체의 errors 속성으로 유효성 검사 오류 내역을 볼 수 있는데, errors.as_data() 메소드를 통해 오류 객체를 반환할 수도 있고, errors.as_json() 메소드를 통해 오류 내역을 직렬화된 JSON 형식으로 반환할 수도 있다. 직렬화되지 않은 JSON을 얻고 싶다면 errors.get_json_data() 메소드를 사용하도록 하자.

 

 

5) 폼의 각 필드에 대한 HTML 출력 확인하기

- BoundField의 속성 또는 메소드를 통해 폼의 각 필드를 액세스할 수 있다.

form['your_name'] # <input type="text" name="your_name" maxlength="50" required id="id_your_name">
form['your_name'].label_tag() # <label for="id_your_name">Your name: </label>
form['your_name'].label # Your name
form['your_name'].value() # '' (<input> 태그의 value 속성)

BoundField 클래스의 더 많은 속성과 메소드를 알고싶다면 아래 공식문서를 참고하자.

https://docs.djangoproject.com/en/4.1/ref/forms/api/#more-granular-output

 

Django

The web framework for perfectionists with deadlines.

docs.djangoproject.com

 

 

6) 템플릿 코드 작성

- 폼 클래스의 렌더링 결과를 가져오기 위해 {{ form }} 구문을 사용했다.

<form action="/name/" method="post">
    {% csrf_token %}
    {{ form }}
    <input type="submit" value="제출" />
</form>

- {{ form }} 이외에도 3가지 옵션이 더 있으니 참고하면 좋을 것이다.

  • {{ form.as_table }} : 디폴트 옵션으로, <tr> 태그로 감싸서 렌더링 (그 외의 테이블 관련 태그는 직접 추가해야 함)
  • {{ form.as_p }} : <p> 태그로 감싸서 렌더링
  • {{ form.as_ul }} : <li> 태그로 감싸서 렌더링 (<ul> 태그는 직접 추가해야 함)

 

일반 폼에서 제공하는 더 많은 속성 및 메소드들을 살펴보고 싶다면 아래에 링크한 공식 문서를 참고하자.

https://docs.djangoproject.com/en/4.1/ref/forms/api/

 

Django

The web framework for perfectionists with deadlines.

docs.djangoproject.com

 

 

7) 일반 폼의 단점

- 폼이 모델과 관련되는 경우에는 모델의 필드와 폼의 필드 간 매핑 룰을 알아야하기 때문에 Form 보다는 ModelForm이 더 편리하다. 매핑 룰은 아래 공식 문서에서 확인할 수 있다.

https://docs.djangoproject.com/en/4.1/topics/forms/modelforms/#field-types

 

Django

The web framework for perfectionists with deadlines.

docs.djangoproject.com

 

 

 

4. 모델 폼

모델 정의를 기초로 해서 만드는 폼으로, 폼 필드는 장고가 알아서 정의해준다.

 

1) ModelForm 클래스 상속 방식

- 개발자가 django.forms.ModelForm 클래스를 상속 받아 모델 폼을 정의하면, 장고는 모델에 정의된 필드를 참조해서 모델 폼을 만드는 방식이다.

- 기초가 되는 model과 폼에 표시될 fields 속성을 Meta 내부 클래스에 정의하면 된다.

class SnackForm(forms.ModelForm):
    class Meta:
        model=Snack
        fields=('name', 'image', 'url', 'description')
        # fields = '__all__' # 모델에 정의된 모든 필드를 폼에 포함하고 싶을 때
        # exclude = ['description'] # 지정된 필드를 제외한 모든 필드를 폼에 포함하고 싶을 때 fields 속성 대신에 사용

 

 

2) modelform_factory 함수 방식

- django.forms.models.modelform_factory 함수를 통해 모델 폼을 만드는 방식이다. 이 함수는 model을 기반으로 ModelForm 클래스를 만들어 반환해준다.

- forms.py 파일에 작성하는 것이 일반적이다.

SnackForm = modelform_factory(Snack, fields='__all__')

 

modelform_factory 함수의 인자 설명

modelform_factory(model, form=ModelForm, fields=None, exclude=None, 
    formfield_callback=None, widgets=None, localized_fields=None, 
    labels=None, help_texts=None, error_messages=None, field_classes=None)
  • formfield_callback : 모델의 필드를 받아서 폼 필드를 리턴하는 콜백 함수
  • widgets : 모델 필드와 위젯을 매핑한 사전
  • localized_fields : 로컬 지역값이 필요한 필드 리스트
  • labels : 모델 필드와 레이블을 매핑한 사전
  • help_texts : 모델 필드와 설명 문구를 매핑한 사전
  • error_messages : 모델 필드와 에러 메시지를 매핑한 사전
  • field_classes : 모델 필드와 폼의 필드 클래스를 매핑한 사전

※ 참고로, fields 또는 exclude 항목 중 하나는 반드시 지정해야 한다.

 

 

3) 제네릭 뷰 방식

- 명시적으로 모델 폼을 정의하지 않아도 제네릭 뷰가 내부적으로 적절한 모델 폼을 만들어준다.

- 폼 처리를 위한 제네릭 뷰에는 FormView, CreateView, UpdateView, DeleteView가 있다. 이들에 대한 설명은 아래 포스팅의 [5-3) Generic Edit View]에서 다뤘다.

https://kimcoder.tistory.com/581

 

[Django] 클래스형 뷰와 제네릭 뷰 소개

장고에서는 뷰를 클래스로도 작성할 수 있으며, 함수형 뷰보다는 클래스형 뷰가 더 이점이 많다. 이번 포스팅에서는 클래스형 뷰와 제네릭 뷰에 대한 감을 잡아보자. 1. 클래스형 뷰의 장점 - 다

kimcoder.tistory.com

 

 

 

5. 폼셋

- 일반 폼을 여러 개 묶어서 한 번에 보여주는 방식이다.

- BaseFormSet 클래스를 상속받아 작성할 수도 있지만, 보통은 formset_factory 함수를 통해 정의한다.

- 폼셋에서도 initial 파라미터를 통해 초기 데이터를 지정할 수 있다. initial은 주로 딕셔너리 리스트로 지정한다.

- 폼셋에는 폼의 개수 등을 관리하는 관리 폼이 추가로 포함되어 있다.

 

1) formset_factory 함수

주어진 폼 클래스를 베이스로 FormSet 클래스를 만들어 반환한다.

formset_factory(form, formset=BaseFormSet, extra=1, can_order=False, 
    can_delete=False, max_num=None, validate_max=False, min_num=None, 
    validate_min=False, absolute_max=None, can_delete_extra=True, renderer=None)
  • form : 폼셋을 만들 때 베이스가 되는 폼
  • formset : 폼셋을 만들 때 상속받기 위한 부모 클래스다. 기능 추가 및 변경을 위해 BaseFormSet을 상속받은 클래스도 지정 가능하다.
  • extra : 초기값이 지정된 폼 외에 추가로 나타낼 빈 폼의 개수
  • can_order : 폼셋에 포함된 폼들의 순서를 변경할 수 있는지 여부
  • can_delete : 폼셋에 포함된 폼들의 일부를 삭제할 수 있는지 여부
  • max_num : 나타낼 빈 폼의 최대 개수
  • max_min : 나타낼 빈 폼의 최소 개수
  • validate_max : 유효성 검사를 수행할 때 max_num에 대한 검사의 실시 여부
  • validate_min : 유효성 검사를 수행할 때 min_num에 대한 검사의 실시 여부

 

2) 템플릿에서의 폼셋 사용

- 폼셋은 컨텍스트 사전에 넣어두고 템플릿에서 보여주면 된다.

context['formset'] = formset_factory(SnackForm, extra=3)

- 폼셋에 들어 있는 각 폼을 다루는 경우에는 {{ formset.management_form }}을 반드시 추가해야 한다. 이 관리 폼이 폼셋에 들어 있는 폼의 개수 등을 장고에게 알려주기 때문이다. 그리고, 각 폼의 모든 필드를 {% for %} 태그로 순회하는 경우가 아니라면 {{ form.id }}을 반드시 추가해야 한다. 장고의 템플릿 엔진이 어느 폼을 처리하고 있는지 식별해야 하기 때문이다.

{{ formset.management_form }}
{% for form in formset %}
	{{ form.id }}
{% endfor %}

- formset.total_form_count 메소드 : 폼셋에 들어 있는 폼의 총 개수를 반환

- formset.initial_form_count 메소드 : 초기 데이터가 들어 있는 폼의 개수를 반환

- formset.extra, formset.max_num, formset.min_num 등 폼셋 정의 시 지정된 속성들을 참조할 수 있다.

 

FormSet에서 제공하는 속성과 메소드에 대해 더 알고 싶다면 아래 공식 문서를 참고하자.

https://docs.djangoproject.com/en/4.1/topics/forms/formsets/

 

Django

The web framework for perfectionists with deadlines.

docs.djangoproject.com

 

 

 

6. 모델 폼셋

- 모델 폼을 여러 개 묶어서 한 번에 보여주는 방식이다.

- 모델 폼과 폼셋을 합친 개념으로, 내부적으로 modelform_factory()와 formset_factory()를 호출한다.

- BaseModelFormSet 클래스를 상속받아 작성할 수도 있지만, 보통은 modelformset_factory 함수를 통해 정의한다.

- 주어진 모델을 베이스로 FormSet 클래스를 만들어 반환한다.

modelformset_factory(model, form=ModelForm, formfield_callback=None, 
    formset=BaseModelFormSet, extra=1, can_delete=False, can_order=False, 
    max_num=None, fields=None, exclude=None, widgets=None, validate_max=False, 
    localized_fields=None, labels=None, help_texts=None, error_messages=None, 
    min_num=None, validate_min=False, field_classes=None, absolute_max=None, 
    can_delete_extra=True, renderer=None, edit_only=False)

- 템플릿에서의 폼셋 사용 방법은 [5-2) 템플릿에서의 폼셋 사용]을 참고하자.

 

 

 

7. 인라인 폼셋

- 두 모델 간의 관계가 N:1일 때, N에 대한 모델 폼셋을 보여주는 방식이다.

- BaseInlineFormSet 클래스를 상속받아 작성할 수도 있지만, 보통은 inlineformset_factory 함수를 통해 정의한다.

inlineformset_factory(parent_model, model, form=ModelForm, formset=BaseInlineFormSet, 
    fk_name=None, fields=None, exclude=None, extra=3, can_order=False, can_delete=True, 
    max_num=None, formfield_callback=None, widgets=None, validate_max=False, 
    localized_fields=None, labels=None, help_texts=None, error_messages=None, 
    min_num=None, validate_min=False, field_classes=None, absolute_max=None, 
    can_delete_extra=True, renderer=None, edit_only=False)

fk_name 인자는 부모 모델에 대한 외래 키가 둘 이상일 때 지정한다.

- 템플릿에서의 폼셋 사용 방법은 [5-2) 템플릿에서의 폼셋 사용]을 참고하자.

 

 

 

8. 파일 업로드(멀티파트) 폼

- 모델에서 ImageField 또는 FileField 타입인 필드가 존재하는 폼이다.

 

1) media 파일 처리

- media 파일은 사용자가 업로드한 파일을 의미한다. media 파일을 처리하기 위한 설정 및 모델 필드 지정 방법은 아래 포스팅의 [3. media 파일 처리]에서 설명했다.

https://kimcoder.tistory.com/347

 

[Django] Form 처리 / 정적 파일과 미디어 파일 처리

1. static 파일 처리 Django의 웹페이지에 css를 적용하거나 이미지를 추가할 때는 일반적인 파일 경로로 불러와서는 안된다. 왜냐하면 기본적으로 Django에 들어오는 요청은 모두 Controller에서 가로채

kimcoder.tistory.com

 

 

2) 컨텍스트 사전에 파일 업로드 폼 저장

- 폼에 데이터를 바인딩 할 때 폼 데이터뿐만 아니라 파일 데이터도 같이 바인딩해야 한다. self.request.POST, self.request.FILES 부분이 핵심이다.

class SnackCV(LoginRequiredMixin, CreateView):
    model=Snack
    fields = ('name', 'image', 'url', 'description')
    ...

    def get_context_data(self, **kwargs): # 템플릿 시스템으로 넘겨줄 컨텍스트 변수에 대한 작업
        context=super().get_context_data(**kwargs)
        if self.request.POST:
            context['modelForm'] = SnackForm(self.request.POST, self.request.FILES)
        else:
            context['modelForm'] = SnackForm()
        return context

참고로, Form.is_multipart()을 통해 멀티파트 폼의 여부를 확인할 수 있다.

 

 

3) 템플릿 코드

- 이제 컨텍스트 사전에 넣은 멀티파트 폼인 modelForm을 템플릿에서 꺼내서 사용하면 된다.

- <form>의 enctype을 반드시 "multipart/form-data"로 지정해줘야 한다.

<form method="post" class="post-form my-3" enctype="multipart/form-data">
    {% csrf_token %}
    {{ modelForm.as_p }}
    <input type="submit" value="제출" />
</form>

아래와 같이 modelForm이 잘 출력된다.

 

반응형

댓글