[Django] 장고 Form 과 Serializer 비교
Form / Serializer
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 = PostModelSerializerpost_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()가 호출되고 나서
- .initial_data 필드에 접근
- .validated_data 를 통해 유효성 검증에 통과한 값들이 .save() 시 사용
- .errors -> 유효성 검증 수행 후 오류 내역
- .data -> 유효성 검증 후 갱신된 인스턴스 필드값 사전
Serializer.save(**kwargs) 호출시
- DB에 저장한 관련 instance 리턴해줍니다.
- .validated_data 와 kwargs 사전을 합친 데이터를
1. .update 함수 / .create 함수를 통해 관련 필드에 값을 할당하고 DB로 저장 시도
2. .update() : self.instance 인자를 지정했을 때
3. .create() : self.instance 인자를 지정하지 않았을 때
DRF에서는 유일성 체크를 도와주는 Validators 제공해줍니다.
from rest_framwork.validators import ….
(값에 대한 유효성 검사를 수행하는 호출 가능한 객체)
- UniqueValidator : 1개 필드, 지정 queryset 범위에서의 유일성 여부 체크
- UniqueTogetherValidator : UniqueValidator의 다수 필드 버전
- BaseUniqueForValidator
- UniqueForDateValidator : 지정 날짜 범위에서 유일성 여부 체크
- UniqueForMonthValidator : 지정 월 범위에서 유일성 여부 체크
- 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를 사용합니다.