(Django) - Form & ModelForm

duo2208·2022년 1월 5일
3

Django

목록 보기
10/23
post-thumbnail
장고 프로젝트라면 마땅히 폼을 이용해야 할 것이다. 또한 대부분(95%)의 장고 프로젝트는 폼 중에서도 모델폼을 써야 할 것이다. 실제 모델폼은 91%의 장고 프로젝트에서 쓰이고 있다. 그 중 80%의 모델폼은 간단한 로직만으로 되어 있으며, 나머지 20%의 모델폼이 복잡한 로직을 담고 있다.

Form


역할

  • 폼 클래스의 데이터를 렌더링하여 입력폼 HTML 생성
  • 입력폼 값에 대한 유효성 검증 (Validation) 및 값 변환
  • 검증을 통과한 값들을 dict 형태로 제공 ›› form.cleaned_data 으로 제공

+ CSRF (Cross Site Request Forgery)

데이터를 변경하는 HTTP 폼은 언제타 CSRF 보안 (사이트 간 위조 요청 방지) 을 이용하자. 개발 단계에서 잊어버리고 이용하지 않았을 경우 보안 위험성이 있을 수 있다는 친절한 안내 메시지를 보여주기도 한다.
django-rest-framework 같은 API 프레임워크에서는 이러한 처리를 자동으로 다 해준다.

# forms.py
...

<form action="" method="post>
	{% csrf_token %}
    ...
</form>



ModelForm


보통 Model 에 맞춰서 Form을 구성하기 때문에, Model이 바뀌면 Form도 그에 따라 의존되는 코드들을 변갱해 줘야하므로 유지보수가 귀찮다. 그러나 ModelForm을 사용하면 Model에서 사용할 Field만 정의하면 그 정보를 가져와 formField 를 자동으로 구성해주기 때문에 편하다.

특징

  • 장고 Form 상속 (forms.ModelForm)
  • 지정된 모델로부터 필드정보를 읽어들여, 자동으로 폼 필드를 세팅
  • 내부적으로 Model Instance 유지
  • 유효성 검증에 통과한 값들로 지정 Model Instance로의 저장(save) 지원

+ Form 과 ModelForm 의 차이

  • Form : 직접 필드 정의, 위젯 설정이 필요
  • ModelForm : 지정된 모델로부터 필드정보를 읽어들여, 자동으로 폼 필드를 세팅
# forms.py
from django import forms

# Form
class PostForm(forms.Form):
	title = forms.CharField()
	content = forms.CharField(widget=form.Textarea)
    
# ModelForm 
from .models import Post

class PostForm(forms.ModelForm):
	class Meta:
		model = Post
		fields = '__all__'	# 모든 필드를 불러온다.

+ form.save(commit=False)

🚀 (Django) The save() method

모델폼 데이터는 폼에 먼저 저장된 이후 모델 인스턴스에 저장된다.
ModelForm에서 폼 데이터는 두 가지 각기 다른 단계를 통해 저장된다.

  • 첫 번째로 폼 데이터가 폼 인스턴스에 저장된다.
  • 그 다음에 폼 데이터가 모델 인스턴스에 저장된다.

ModelForm 클래스에는 form.save(self, commit=True) 메서드가 구현되어 있는데, DB 저장 여부를 commit flag를 통해서 결정한다.

commit=False 를 사용하면 함수 호출을 지연시켜 DB에 바로 저장하지 않는다(임시 저장 상태).

DB에 실제 데이터를 저장하기 전에 따로 일련의 작업을 하고 싶을 때 사용하기 유용하다. form.save() 메서드에 의해 적용되기 전까지는 ModelForm이 모델 인스턴스로 저장되지 않는 분리된 특징 자체를 장점으로 이용할 수 있는 것이다.

# views.py
from django.shortcuts import render, redirect
from .models import Post
from .forms import PostForm

def post_new(request):
	if request.method == 'POST':
		form = PostForm(request.POST, request.FILES)
		if form.is_valid():
   			# 필수필드인 ip필드 값을 아직 채우지 않았으므로 
   			# DB 세이브를 지연시켜 오류를 방지한다.
			post = form.save(commit=False)  
			post.ip = request.META['REMOTE_ADDR']	# 유저로부터 ip 필드를 입력받음.
 			post.save()
			return redirect('/dojo/')
	else:
		form = PostForm()
 	return render(request, 'dojo/post_form.html', {'form': form,})

+ form.cleaned_data 사용하기

🚀 (Django) Accessing “clean” data

form.is_valid() 를 통해서 검증을 통화한 값들은 dict 형태로 form.cleaned_data 변수명으로 제공된다.

폼의 입력값을 얻고 싶은 경우는form.cleaned_data 로 접근하도록 하자.

➔ Avoid code : request.POST[] 로 접근

# views.py
...
form = CommentForm(request.POST)

if form.is_valid():
	# request.POST 데이터는 폼 인스턴스 초기 데이터를 가져 오므로 	
	# `form.clean()` 메서드를 통해 변경될 가능성이 있어
	# 나쁜 접근이다.
	message = request.POST['message']
	comment = Comment(message=message)
	comment.save()
return redirect(post)

➔ Good code : from.cleaned_data[] 로 접근

# views.py
...
form = CommentForm(request.POST)

if form.is_valid():
	# 폼 인스턴스 내에서 clean함수를 통해 변환되었을 수도 있는 데이터를 가져오므로
	# 좋은 접근이다.
	message = form.cleaned_data['message']
	comment = Comment(message=message)
	comment.save()
return redirect(post)



Form Validation


장고 폼은 파이썬 딕셔너리의 유효성을 검사하는 최상의 도구이다.
유효성 검사를 수행하는데는 2가지의 방법이 있다.

1. validator 함수를 통한 유효성 검사

  • 값이 원하는 조건에 맞지 않을 때 ValidationError 예외를 발생시킨다.
  • 리턴값은 사용되지 않는다.
  • 모델필드 또는 폼필드 정의 시에 지정한다.
  • ⚡ 사용자 정의 Validator 보다 빌트인 Validator 사용을 추천한다.

2. Form 클래스 내의 clean , clean_ 멤버함수를 통한 유효성 검사 및 값 변경

  • 값이 원하는 조건에 맞지 않을 때 ValidationError 예외를 발생시킨다.
  • 리턴값을 통해 값을 반환한다.
  • ⚡ 원하는 포맷으로 값 변경을 원할 때 사용

+ 유효성 검사 호출 로직

유효성 검사는 form.is_valid() 가 호출되는 시점에서 수행된다.
보통 from.is_valid() 가 호출될 때 다음과 같은 절차가 진행된다.

# views.py
def post_new(request):
	if request.method == 'POST':
		form = PostForm(request.POST, request:FILES)
		if form.is_valid():		# 유효성 검사 수행
			form.save()
			# SUCESS 후 처리
	else:
    	form = PostForm()
	# ...
  1. 폼이 데이터를 받으면 form.is_valid()form.full_clean() 메서드를 호출한다.
  2. form.full_clean() 은 폼 필드들과 각각의 필드 유효성을 하나하나 검사하면서 다음과 같은 유효성 검사를 수행한다.
    ㅤ- 필드에 들어온 데이터에 대해 파이썬 형식으로 변환하거나, 변환할 때 문제가 생기면 ValidationError를 일으킨다.
    ㅤ- 커스텀 유효성 검사기(validator)를 포함한 각 필드에 특별한 유효성을 검사한다. 문제가 있을 때 ValidationError를 일으킨다.
    ㅤ- 폼에 clean_() 메서드가 있으면 이를 실행한다.

  3. form.full_clean()form.clean() 메서드를 실행한다.
  4. ModelForm 인스턴스의 경우 form.post_clean() 이 다음 작업을 한다.
    ㅤ- form.is_valid() 가 True나 False로 설정되어 있는 것과 관계없이 ModelForm의 데이터를 모델 인스턴스로 설정한다.
    ㅤ- 모델의 clean() 메서드를 호출한다.

+ form 필드 별로 유효성 검사 함수 추가 적용

(1) forms.py 에 적용

›› form 에서는 리턴값을 따로 처리하지 않고, forms.ValidationError 예외 발생 유무로 처리

# forms.py
import re
from django import forms
from django.forms import ValidationError
  
def phone_number_validator(value):
	if not re.match(r'^010[1-9]\d{7}$'):
		raise forms.ValidationError('3글자 이상 입력하시오.')
                      
class ProfileForm(forms.Form):
	phone_number = forms.CharField(validators=[phone_number_validator])

(2) models.py 에 적용

›› model 클래스 정의 시에 validators 옵션 적용. (권장!! 로직이 분산되지 않는다.)
›› 가급적이면 모든 validators 는 모델에 정의하고, ModelForm 을 통해 모델의 validators 정보를 가져오도록 하자.

# models.py
import re
from django import forms
from django import models
  
def phone_number_validator(value):
	if not re.match(r'^010[1-9]\d{7}$'):
		raise forms.ValidationError('3글자 이상 입력하시오.')
                      
class Profile(forms.Model):
	phone_number = forms.CharField(validators=[phone_number_validator]) 
# forms.py 
from django import forms
from .models import Profile
  
class ProfileForm(forms.ModelForm):
	class Meta:
		model = Profile
  		fields = '__all__'

📌 참고 출처

0개의 댓글