[Django] 장고 Form 과 Serializer 비교

Form / Serializer

Donnis Dev-note
12 min readMar 17, 2020

Serializer / ModelSerializer

데이터 변환 / 직렬화 지원해줍니다.
- QuerySet / Model 객체 <-> Native Python 데이터타입, Json / XML 등
Django Form/ModelForm 과 유사합니다.
- 입력값에 대한 유효성 검사, 에러 발생, 통과 값에 대한 DB저장
- Serializer는 View 응답을 생성하는 데 범용적이고 강력한 방법을 제공하며ModelSerializer는 Serializer 생성을 위한 Shortcut

기능 비교

Form / ModelForm

HTML 입력폼을 통한 입력에 대한 유효성 검사 수행
주로 Create / Update 처리에서 활용됩니다. (장고 admin에서 활용)
CreateView/UpdateView CBV를 통한 뷰 처리 -> 단일 뷰

Serializer / ModelSerializer

데이터 변환 및 직렬화 지원 (JSON 포맷 등)
주로 JSON 포맷(WEB API 포맷) 입력에 대한 유효성 검사
List/Create 및 특정 레코드에 대한 Retrieve/Edit/Delete 등에서 활용됩니다.
APIView를 통한 뷰 처리는 단일 뷰로
ViewSet을 통한 뷰 처리는 2개의 뷰로, 2개의 URL로 처리됩니다.

호출 주체 비교

Form

일반적으로 웹브라우저 상(POST)
— HTML Form Submit
— JavaScript에 의한 비동기 호출
— Android/iOS 앱에 의한 요청/응답도 가능 (http(s) 프로토콜 요청/응답이기 때문)

Serializer

다양한 Client에 대한 Data 위주의 http(s) 요청
— Web/Android/iOS 등

클래스 정의 비교

Form (html form을 효율적으로 처리하기 위해)

from django import formsclass PostForm(forms.Form):
email = forms.EmailField()
content = forms.CharField(widget=forms.Textarea)
created_at = forms.DateTimeField()
class PostModelForm(forms.ModelForm):
class Meta:
model = Post
fields = '__all__'

Serializer (api 요청을 처리하기 위해, widget 없음)

from rest_framework import serializersclass PostSerializer(serializers.Serializer):
emial = serializers.EmailField()
content = serializers.CharField(max_length=200)
created_at = serializers.DateTimeField()
class PostModelSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = '__all__'

FBV 요청 / 응답 비교

Form

def post_create(request):
if request.method == 'POST':
form = PostForm(request.POST, request.FILES)
if form.is_valid():
post = form.save()
return redirect(post)
else:
form = PostForm()
return render(request, 'post_form.html', {
'form' : form,
})
def post_list_or_create(request):
if request.method == 'POST':
form = PostForm(request.POST, request.FILES)
if form.is_valid():
post = form.save()
return JsonResponse(post)
return JsonResponse(form.errors)
else:
qs = Post.objects.all()
return JsonResponse(qs)

Serializer

def post_list_or_create(request):
if request.method == 'POST':
serializer = Postserializer(data=request.POST)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=201)
return Response(serializer.errors, status=400)

else:
qs = Post.objects.all()
serializer = PostSerializer(qs, many=True)
return Response(serializer.data)
class postListCreateAPIView(APIView):
def get(self, request):
serializer = PostSerializer(Post.objects.all(), many=True)
return Response(serializer.data)

def post(self, request):
serializer = PostSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=201)
return Response(serializer.errors, status=400)

post를 create 하고, list를 조회하는 코드입니다.
Form, Serializer의 코드가 굉장히 유사하다는 것을 알 수 있습니다.

CBV 요청 / 응답 비교

장고 기본 (Form)

# List, Create 따로따로 만들어서 url에 매핑해줌post_list = ListView.as_view(model=Post)
post_new = CreateView.as_view(model=Post, form_class=PostModelForm)
#urls.py
urlpatterns = [
path('', post_list),
path('new/', post_new),
]
# post_new.html
<form action="" method="post" enctype="multipart/form-data">
{% csrf_token %}
<table>
{{ form.as_table }}
</table>
<input type="submit" />
</form>

DRF의 APIView

# APIView는 단일뷰를 통해서 get과 post 요청을 처리class PostListCrerateAPIView(ListCreateAPIView):
queryset = Post.objects.all()
serializer_class = PostModelSerializer
post_list_create = PostListCreateAPIView.as_view()#urls.py
urlpatterns = [
path('api/post/', post_list_create),
]

유효성 검사 수행 시점 비교

Form

def post(self, request, *args, **kwargs):
form = self.get_form() # POST 데이터의 Form 객체 생성
if form.is_valid(): # 유효성 검사
return self.form_valid(form) # DB에 저장
else:
return self.form_invalid(form) # 오류 HTML 응답

Serializer

# CreateModel
def create(self, request, *args, **kwargs): # POST 요청들어오면
serializer = self.get_serializer(data=request.data) # POST 데이터를 통해서 Serializer 인스턴스 생성
serializer.is_valid(raise_exception=True) # 유효성 검사, 실패시 예외 발생
self.perform_create(serializer) # DB로 저장
headers = self.get_success_headers(serializer.data) # 헤더 저장
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) # 응답

Form과 달리 Serializer는 실패시 예외처리를 하는 방식으로 되어있습니다.

커스텀 유효성 검사 루틴

Form ( clean )

def clean_title(self):
value = self.cleaned_data.get('title', '')
if 'hello' not in value:
raise forms.ValidationError('제목에 hello를 포함하세요.')
return value

Serializer ( validate )

rest_framework_exceptions import ValidationErrordef validate_title(self, value):
if 'hello' not in value:
raise ValidationError('제목에 hello를 포함하세요.')
return value

clean_title() 함수 안에서 self.cleaned_data.get()으로 값을 가져오는 Form과 달리 Serializer는 validate_title() 함수의 파라미터로 값을 가져와서 검사합니다. 에러 또한 form에 있는 ValidationError가 아닌 rest_framework_exceptions import ValidationError을 사용합니다.

Serializer는 Form과 컨셉/ 사용법이 유사하지만 생성자에도 차이가 있습니다.

data= 인자가 주어지면 .is_valid()가 호출되고 나서

  1. .initial_data 필드에 접근
  2. .validated_data 를 통해 유효성 검증에 통과한 값들이 .save() 시 사용
  3. .errors -> 유효성 검증 수행 후 오류 내역
  4. .data -> 유효성 검증 후 갱신된 인스턴스 필드값 사전

Serializer.save(**kwargs) 호출시

  1. DB에 저장한 관련 instance 리턴해줍니다.
  2. .validated_data 와 kwargs 사전을 합친 데이터를
    1. .update 함수 / .create 함수를 통해 관련 필드에 값을 할당하고 DB로 저장 시도
    2. .update() : self.instance 인자를 지정했을 때
    3. .create() : self.instance 인자를 지정하지 않았을 때

DRF에서는 유일성 체크를 도와주는 Validators 제공해줍니다.

from rest_framwork.validators import ….

(값에 대한 유효성 검사를 수행하는 호출 가능한 객체)

  1. UniqueValidator : 1개 필드, 지정 queryset 범위에서의 유일성 여부 체크
  2. UniqueTogetherValidator : UniqueValidator의 다수 필드 버전
  3. BaseUniqueForValidator
  4. UniqueForDateValidator : 지정 날짜 범위에서 유일성 여부 체크
  5. UniqueForMonthValidator : 지정 월 범위에서 유일성 여부 체크
  6. UniqueForYearValidator : 지정 년 범위에서 유일성 여부 체크

UniqueValidator

모델 필드에 unique=True 지정시, 자동 지정됩니다.

  • queryset (필수)
  • message : 유효성 검사 실패 시 에러 메시지
  • lookup : 디폴트 ‘exact’

(Serializer 필드에 직접 validators 지정이 가능)

slug = SlugField(
max_length=100,
validators=[UniqueValidator(queryset=Post.objects.all())])

UniqueForDateValidator / Month / Year

  • queryset (필수)
  • field (필수) : 이 필드가 유니크한 값을 가지는지
  • date_field(필수) : 날짜 필드
  • message
class PostSerializer(serializers.Serializer):
class Meta:
validators = [
UniqueForYearValidator(
queryset = Post.objects.all(),
field='slug',
date_field='published')]

유효성 검사 실패시 ValidationError 예외 발생시

rest_framework.exceptions.ValidationError 를 사용하고
장고 기본에서는 django.forms.exceptions.ValidationError를 사용합니다.

--

--