TIL#130 Django Form

Dasom·2021년 2월 21일
0

Django

목록 보기
21/33
post-thumbnail

Django Form 유효성 검사

장고 폼은 파이썬 딕셔너리의 유효성을 검사하는 데 최상의 도구이다.
다른 프로젝트로부터 CSV 파일을 받아 모델에 업데이트하는 장고 앱을 가지고 있다고 가정해 볼 때의 코드이다.

# 나쁜 예 

import csv
import StringIO

from .models import Purchase

def add_csv_purchase(rows):

    rows = StringIO.StringIO(rows)
    records_added = 0
    
    for row in csv.DictReader(rows, delimiter=","):
        purchase.objects.create(**row)
        records_added += 1
    
    return records_added

위의 코드에서는 유효성 검사를 하고 있지 않다. add_csv_purchases() 함수에 유효성 검사 코드를 추가할 수도 있지만 그렇게 되면 매번 데이터가 바뀔 때마다 유효성 검사 코드를 수정하고 관리해 주어야 한다.

이럴 때 장고의 폼을 이용하여 입력 데이터에 대해 유효성 검사를 하는 방식을 이용한다.

import csv
import StringIO

from django import forms

from .models import Pusrchase, Seller


class PurchaseForm(forms.ModelForm):
    
    class Meta:
        model = Purchase
        
    def clean_seller(self):
        seller = self.cleaned_data['seller']
        try:
            Seller.objects.get(name=seller)
        except Seller.DoesNotExist:
            msg = "{0} does not exist in purchase #{1}.".format(
                seller, 
                self.cleaned_data['purchase_number']
            )
            raise forms.ValidationError(msg)
        return seller
        

def add_csv_purchase(rows):

    rows = StringIO.StringIO(rows)
    
    records_added = 0
    errors = []
    
    for row in csv.DictReader(rows, delimiter=","):
    
        form = PurchaseForm(row)
        
        if form.is_valid():
            form.save()
            records_added += 1
        else:
            errors.append(form.errors)

    return records_added, errors

CSRF 보안

장고에는 CSRF(cross-site request forgery protection, 사이트 간 위조 요청 방지)가 내장되어 있다. 이용하기가 편리하며 개발 단계에서 잊어버리고 이용하지 않았을 경우 안내 메시지를 보여주기도 한다.
CSRF 보안을 잠시 꺼 두어도 되는 경우로는 머신들 사이에 이용되는 API사이트를 제작할 때이다. django-tastypie 나 django-rest-framework 같은 API 프레임워크에서는 이러한 처리를 자동으로 해준다.

AJAX 데이터 추가

AJAX 를 사용하여 데이터를 추가할 때는 반드시 장고의 CSRF 보안을 이용해야 한다. 대신에 AJAX 를 통해 데이터를 보낼 때 HTTP 헤더에 X-CSRFToken을 설정해 두도록 한다.

Form 인스턴스 속성 추가

폼의 메서드에 추가로 폼 인스턴스 속성이 필요할 때가 있다. 아래는 user라는 인스턴스 속성을 추가하기 위한 코드이다.

# forms.py
from django import forms

from .models import Taster

class TasterForm(forms.ModelForm):
    class Meta:
        model = Taster
        
    def __init__(self, *args, **kwargs):
        self.user = kwargs.pop('user')
        super(TasterForm, self).__init__(*args, **kwargs)
        
        
# views.py
from django.views.generic import UpdateView

from braces.views import LoginRequireMixin

from .forms import TasterForm
from .models import Taster

class TasterUpdateView(LoginRequireMixin, UpdateView):
    model = Taster
    form_class = TasterForm
    success_url = "/someplace/"
    
    def get_form_kwargs(self):
        kwargs = super(TasterUpdateView, self).get_form_kwargs()
        kwargs['user'] = self.request.user
        return kwargs

폼의 유효성 검사 방법

form.is_valid() 가 호출 될 때

  • 폼이 데이터를 받으면 form.is_valid() 는 form.full_clean() 메서드를 호출한다.
  • form.full_clean() 은 폼 필드들과 각각의 필드 유효성을 하나하나 검사하면서 다음과 같은 과정을 수행한다.
    • 필드에 들어온 데이터에 대해 to_python() 을 이용하여 파이썬 형식으로 변환하거나 변활할 때 문제가 생기면 ValidationError를 일으킨다.
    • 커스텀 유효성 검사를 포함한 각 필드의 유효성 검사를 한다. 문제가 있을 때 ValidationError를 일으킨다.
    • 폼에 clean_() 메서드가 있으면 이를 실행한다.
  • form.full_clean()이 form.clean() 메서드를 실행한다.
  • ModelForm 인스턴스의 경우 form.post_clean()이 다음 작업을 한다.
    • form.is_valid()가 true, false 로 설정되어 있는 것과 관계없이 ModelForm의 데이터를 모델 인스턴스로 설정한다.
    • 모델의 clean() 메서드를 호출한다. ORM을 통해 모델 인스턴스를 저장할 때는 모델의 clean() 메서드가 호출되지는 않는다.

모델폼 데이터는 폼에 먼저 저장된 후 모델 인스턴스에 저장

form.save() 메서드에 의해 적용되기 전까지는 모델 인스턴스로 저장이 되지 않기 때문에 이렇게 분리된 과정 자체를 장점으로 이용할 수 있다.
예를 들면 폼 입력 시도 실패에 대해 더 자세한 사항이 필요하다면 사용자가 입력한 폼의 데이터와 모델 인스턴스의 변화를 둘 다 저장할 수 있다.

# models.py
from django.db import models
  
class ModelFormFailureHistory(models.Model):
    form_data = models.TextField()
    model_data = models.TextField()
  
# views.py
import json
  
from django.contrib import messages
from django.core import serializers
from core.models import ModelFormFailureHistory
  
class FlavorActionMixin(object):
  
    @property
    def success_msg(self):
        return NotImplemented
  
    def form_valid(self, form):
        messages.info(self.request, self.success_msg)
        return super(FlavorActionMixin, self).form_valid(form)
  
    def form_invalid(self, form):
        form_data = json.dumps(form.cleaned_data)
        model_data = serializers.serialize("json", [form.instance])[1:-1]
  
        ModelFormFailureHistory.objects.create(
            form_data=form_data,
            model_data=model_data
        )
        return super(FlavorActionMixin, self).form_invalid(form)

Form.add_error()

from django import forms
  
class IceCreamReviewForm(forms.Form):
    ...
  
    def clean(self):
        cleaned_data = super(TasterForm, self).clean()
        flavor = cleaned_data.get('flavor')
        age = cleaned_data.get('age')
  
        if flavor == 'coffee' and age < 3:
            msg = u'Coffee Ice Cream is not for Babies.'
            self.add_error('flavor', msg)
            self.add_error('age', msg)
               
        return cleaned_data
profile
개발자꿈나무🌲

0개의 댓글