장고 폼은 파이썬 딕셔너리의 유효성을 검사하는 데 최상의 도구이다.
다른 프로젝트로부터 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(cross-site request forgery protection, 사이트 간 위조 요청 방지)가 내장되어 있다. 이용하기가 편리하며 개발 단계에서 잊어버리고 이용하지 않았을 경우 안내 메시지를 보여주기도 한다.
CSRF 보안을 잠시 꺼 두어도 되는 경우로는 머신들 사이에 이용되는 API사이트를 제작할 때이다. django-tastypie 나 django-rest-framework 같은 API 프레임워크에서는 이러한 처리를 자동으로 해준다.
AJAX 를 사용하여 데이터를 추가할 때는 반드시 장고의 CSRF 보안을 이용해야 한다. 대신에 AJAX 를 통해 데이터를 보낼 때 HTTP 헤더에 X-CSRFToken을 설정해 두도록 한다.
폼의 메서드에 추가로 폼 인스턴스 속성이 필요할 때가 있다. 아래는 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.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)
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