장고 Form

guava·2021년 12월 18일
0

파이썬/장고 웹서비스 개발 완벽 가이드 with 리액트 강의를 듣고 정리한 글입니다.

1. Form의 주요 역할


  1. HTML 입력폼을 생성한다.
  2. 입력폼 값에 대한 유효성 검증 (Validation) 및 값을 변환
  3. 검증을 통과한 값들을 dict 형태로 제공한다.

2. Django Style의 Form 처리


하나의 URL에서 2가지 역할을 수행한다.

  1. (GET 요청) 빈 폼을 보여주는 역할 (New/Edit 입력 폼을 보여준다.)
  2. (POST 요청) 폼을 통해 입력된 값을 검증하고 저장한다.
    • 데이터를 입력받아서 (request.POST, request.FILES) 유효성 검증을 수행한다.
    • 검증 성공 시, 해당 데이터를 저장하고 success url로 이동한다.
    • 검증 실패 시, 오류 메시지와 함께 입력 폼을 다시 보여준다.

코드 예시

Form / ModelForm 클래스 정의

일반폼 또는 모델폼을 정의한다. Model이 데이터베이스와 인터렉션 하듯이, Form은 HTML Form과 관계되어 있다.

  • GET요청 시 응답할 HTML 폼을 생성해준다.
  • is_valid()를 활용해서 Validation 과정을 수행하고 결과를 반환할 수 있다.
  • 검증을 통과한 값들을 사전 타입으로 제공할 수 있다.

모델폼

  • 장고 Form을 상속
  • 지정된 Model로부터 필드 정보를 읽어들여서 Form Fields를 세팅한다.
  • 내부적으로 Model Instance를 유지한다.
  • 유효성 검증에 통과한 값들을 지정 Model Instance로의 저장을 지원한다.
  • Validation을 모델 / 폼 별도로 정의할 필요가 없다. 모델에서 지정한 Validation을 활용한다.
# forms.py
from django import forms
from .models import Post

# 모델 폼 예시
class PostForm(forms.ModelForm):

    class Meta:
        model = Post
        fields = ['title', 'content']  # fields는 명시적으로 정의하는 것을 추천

# 일반 폼 예시
class PostForm(forms.Form):
    title = forms.CharField()
    content = forms.CharField(widget=forms.Textarea)

    def save(self, commit=True):
        # ModelForm.save 인터페이스를 흉내내어 구현
        post = Post(**self.cleaned_data)
        if commit:
            post.save()
        return post

ModelForm.save(commit=True)

모델폼은 유효성 검증을 통과한 값들을 지정 Model Instance로의 저장을 지원한다.

정확히는 save()메서드를 통해서 Form의 cleaned_data를 Model Instance 생성에 사용하고, 그 Instance를 반환한다.

save()메서드는 commit인자(default=True)를 받는다. commit 인자는 모델 인스턴스의 save()를 호출할지 결정하는 파라미터다.

@login_required
def post_new(request):
    if request.method == 'POST':
        form = PostForm(request.POST, request.FILES)
        if form.is_valid():
            post = form.save(commit=False)  # 모델 인스턴스의 save()를 호출하지 않는다.
            post.user = request.user  # 유저 정보를 저장한다.
            post.save()  # 유저 정보 저장 후 모델 인스턴스의 save()를 호출하도록 한다.
            return redirect(post)
    else:
        form = PostForm()
    return render(request, 'instagram/post_form.html', {
        'form': form
    })

View에서 Form 활용 예시

request.method : 요청의 종류 ("GET" 또는 "POST"로서 모두 대문자다)
request.POST: POST 인자 목록 (QueryDict 타입)
request.GET: GET 인자 목록 (QueryDict 타입)
request.FILES: POST 인자 중에서 파일 목록 (MultiValueDict 타입)
request.body: 순수 body 데이터 (json요청 등). POST, GET, FILES는 http의 body부분을 해석해 생성한 객체인데 반해 request.body는 순수한 body 데이터를 그대로 제공한다.
request.META: http 헤더를 해석한 데이터

# views.py
from .forms import PostForm

def post_new(request):
    if request.method == 'POST':
        # POST 요청일 때 처리
        # request.POST, request.FILES를 제공받는다.
        form = PostForm(request.POST, request.FILES)
        if form.is_valid(): # 인자로 받은 값에 대해 유효성 검증이 성공하면 True
            # 검증에 성공한 값들을 사전타입(cleaned_data)으로 제공받는다.
            # 필요에 따라 이 값을 데이터베이스에 저장한다.
            post = form.save()  
            # success url로 이동하도록 하기
            return redirect(post)  # Post에 get_absolute_url이 구현되어 있기에 객체만 넘기면 된다.
    else:
        # GET 요청일 때 빈 폼을 생성한다.
        form = PostForm()
    # 만약 검증에 실패하면 form.errors에 오류 정보가 저장된다.
    return render(request, 'instagram/post_form.html', {
        'form': form
    })
<!-- post_form.html -->
<form action="" method="post" enctype="multipart/form-data">
  {% csrf_token %}
  <table>
    {{ form }}
  </table>
  <input type="submit" value="저장" />
</form>

3. 모델 폼


ModelForm?

  • 장고 Form을 상속
  • 지정된 Model로부터 필드 정보를 읽어들여서 Form Fields를 세팅한다.
  • 내부적으로 Model Instance를 유지한다.
  • 유효성 검증에 통과한 값들을 지정 Model Instance로의 저장을 지원한다.

모델 폼 정의

# 일반 폼 예시
class PostForm(forms.Form):
    title = forms.CharField()
    content = forms.CharField(widget=forms.Textarea)

    def save(self, commit=True):
        # ModelForm.save 인터페이스를 흉내내어 구현
        post = Post(**self.cleaned_data)
        if commit:
            post.save()
        return 

# 모델 폼 예시
class PostForm(forms.ModelForm):

    class Meta:
        model = Post
        fields = 

post

4. Form Validation


유효성 검사

유효성 검사는 form.is_valid()가 호출되는 시점이 수행된다.
유ㅜㅎ

유효성 검사 호출 로직

  1. form.full_clean() 호출
    • 각 필드 객체 별로 필드객체.clean()호출을 통해 각 필드 Type에 맞춰서 유효성 검사 수행
    • Form 객체 내에서
      • (사용자 정의) 필드 이름 별로 Form객체.clean_필드명() 함수가 있다면 호출해서 유효성 검사
      • (사용자 정의) Form객체.clean() 함수가 있다면 유효성 검사
  2. 에러 유무에 따라 form.isvalid()에서 True/False를 리턴
  3. clean, clean_멤버함수 를 통해 유효성 검사를 수행하고 결과 값을 반환한다.

코드 예시

# views.py
@login_required
def post_new(request):
    if request.method == 'POST':
        form = PostForm(request.POST, request.FILES)
        if form.is_valid():  # 유효성 검사 수행 후 True/False를 반환
            post = form.save(commit=False)
            post.user = request.user
            post.save()
            return redirect(post)
    else:
        form = PostForm()
    return render(request, 'instagram/post_form.html', {
        'form': form
    })
    
# forms.py
class PostForm(forms.ModelForm):

    class Meta:
        model = Post
        fields = ['message', 'photo', 'tag_set', 'is_publish']
        
    def clean(self):  # Form객체.clean() 함수
        # ...
        pass

    def clean_message(self):  # Form객체.clean_필드명() 함수 
        message = self.cleaned_data.get('message')
        if message:
            message = re.sub(r'[a-zA-Z]+', '', message)
        return message

Validator

값이 원하는 조건에 맞지 않으면 ValidationError 예외를 발생시킨다. (결과 값을 반환하지는 않는다.)

모델 필드 또는 폼 필드를 정의 시에 지정한다.

빌트인 Validators를 사용하기를 추천한다.
사용자 정의 Validator는 마이그레이션 시 문제가 발생할 수 있다.

빌트인 validators

  • RegexValidator
  • EmailValidator
  • URLValidator
  • validate_email
  • validate_slug
  • validate_unicode_slug
  • validator_ipv4_address,validator_ipv6_address, validator_ipv46_address
  • validator_comma_separated_integer_list
  • int_list_validator
  • MaxValueValidator
  • MinValueValidator
  • MaxLengthValidator
  • MinLengthValidator
  • DecimalValidator
  • FileExtensionValidator : 파일 확장자 허용 여부
  • validate_image_file_extension : 이미지 확장자 여부 (Pillow 설치 필수)

사용자 정의 validator 지정하기

# validator
def phone_number_validator(value):
    if not re.math(r'010[1-9]\d{7}$`):
        raise ValidationError('{} is not an phone number'.format(value))

# model
class Profile(model.Model):
    phone_number = models.CharField(max_length=11, validators=[phone_number_validator])

# Model Form
class ProfileForm(forms.ModelForm):
    class Meta:
        model = Profile
        fields = '__all__'

# basic Form
class ProfileForm(forms.Form):
    phone_number = forms.CharField(validators=[phone_number_validator])

0개의 댓글