파이썬/장고 웹서비스 개발 완벽 가이드 with 리액트 강의를 듣고 정리한 글입니다.
Django의 From과 DRF의 Serializer는 사용법이 유사하다.
Form은 HTML Form을 생성하고 요청을 처리하는데 초점이 맞춰져 있다. 반면에 Serilizer는 다양한 API 요청에 대한 처리(직렬화, 유효성검사 등)가 목적이기 때문에 위젯과 같은 속성은 존재하지 않는다.
이번 포스팅에서는 Form과 Serializer의 사용법에 대해 비교해보고 API를 구현할 때 어떤 차이가 있는지 알아본다.
https://www.django-rest-framework.org/api-guide/serializers/
유효성 검사 및 데이터베이스로의 저장을 지원한다.
from django import forms
class 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__'
from rest_framework import serializers
class PostSerializer(serializers.Serializer):
email = serializers.EmailField()
content = serializers.CharField(max_length=200)
created_at = serializers.DateTimeField()
class PostModelSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = '__all__'
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, 'myapp/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) # 커스텀 직렬화 루틴이 필요
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)
from rest_framework.response import Response
from rest_framework.views import APIView
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)
from django.views.generic import ListView, CreateView
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),
]
<form action="" method="post" enctype="multipart/form-data">
{% csrf_token %}
<table>
{{ form.as_table }}
</table>
<input type="submit" />
</form>
from rest_framework.generics import ListCreateAPIView
class PostListCreateAPIView(ListCreateAPIView):
queryset = Post.objects.all()
serializer_class = PostModelSerializer
post_list_create = PostListCreateAPIView.as_view()
# 앱/urls.py
urlpatterns = [
# 단일 URL에서 list/create 요청 처리
path('api/post/', post_list_create),
]
(rev3_env) daniel@DESKTOP-L8C3ML9:~/askdjango_study/django-with-react-rev3/askdjango$ http http://localhost:8000/api/post/
HTTP/1.1 200 OK
Allow: GET, POST, HEAD, OPTIONS
Content-Length: 1147
Content-Type: application/json
Date: Sat, 06 Mar 2021 10:17:44 GMT
Referrer-Policy: same-origin
Server: WSGIServer/0.2 CPython/3.8.5
Vary: Accept, Cookie
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
[
{
"author": 1,
"author_username": "daniel",
"created_at": "2021-02-28T14:47:32.775808Z",
"id": 1,
"is_public": true,
"message": "안녕하세요 첫번째 포스팅",
"updated_at": "2021-03-02T13:55:31.646227Z"
},
# ...
(rev3_env) daniel@DESKTOP-L8C3ML9:~/askdjango_study/django-with-react-rev3/askdjango$ http POST http://localhost:8000/api/post/
HTTP/1.1 400 Bad Request
Allow: GET, POST, HEAD, OPTIONS
Content-Length: 76
Content-Type: application/json
Date: Sat, 06 Mar 2021 10:19:35 GMT
Referrer-Policy: same-origin
Server: WSGIServer/0.2 CPython/3.8.5
Vary: Accept, Cookie
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
{
"author": [
"This field is required."
],
"message": [
"This field is required."
]
}
# ...
(rev3_env) daniel@DESKTOP-L8C3ML9:~/askdjango_study/django-with-react-rev3/askdjango$ http POST http://localhost:8000/api/post/ message="world" user_agent="httpie"
HTTP/1.1 201 Created
Allow: GET, POST, HEAD, OPTIONS
Content-Length: 130
Content-Type: application/json
Date: Sat, 06 Mar 2021 10:22:18 GMT
Referrer-Policy: same-origin
Server: WSGIServer/0.2 CPython/3.8.5
Vary: Accept, Cookie
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
{
"created_at": "2021-03-06T10:22:18.932735Z",
"id": 7,
"is_public": false,
"message": "world",
"updated_at": "2021-03-06T10:22:18.932767Z"
}
user_agent 필드는 요청 Header의 User-Agent 헤더를 활용하는 것이 좋습니다.
→ request.META.get("HTTP_USER_AGENT")
class ProcessFormView(View):
def get(self, request, *args, **kwargs):
return self.render_to_response(self.get_context_data())
def post(self, request, *args, **kwargs):
form = self.get_form() # POST 데이터를 통해 Form 객체 생성
if form.is_valid(): # 유효성 검사를 수행. 실패하면 False를 반환
return self.form_valid(form) # DB로의 저장을 수행
else:
return self.form_invalid(form) # 오류 HTML 응답
# rest_framework/mixins.py
class CreateModelMixin(object):
def create(self, request, *args, **kwargs): # POST 요청 à CREATE 요청이 들어오면,
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) # 응답을 합니다.
def perform_create(self, serializer): # CREATE 커스텀은 이 함수를 재정의하세요.
serializer.save()
clean_*
vs validate_*
from django import forms
class PostForm(forms.Form):
title = forms.CharField()
def clean_title(self):
value = self.cleaned_data.get('title', '')
if 'django' not in value:
raise forms.ValidationError('제목에 필히 django가 포함되어야 합니다.')
return value
from rest_framework.exceptions import ValidationError
class PostSerializer(serializers.Serializer):
title = serializers.CharField(max_length=100)
def validate_title(self, value):
if 'django' not in value:
raise ValidationError('제목에 필히 django가 포함되어야 합니다.')
return value