Django From VS DRF Serializer

guava·2022년 1월 15일
0

파이썬/장고 웹서비스 개발 완벽 가이드 with 리액트 강의를 듣고 정리한 글입니다.

Django의 From과 DRF의 Serializer는 사용법이 유사하다.
Form은 HTML Form을 생성하고 요청을 처리하는데 초점이 맞춰져 있다. 반면에 Serilizer는 다양한 API 요청에 대한 처리(직렬화, 유효성검사 등)가 목적이기 때문에 위젯과 같은 속성은 존재하지 않는다.

이번 포스팅에서는 Form과 Serializer의 사용법에 대해 비교해보고 API를 구현할 때 어떤 차이가 있는지 알아본다.

1. Serializer / ModelSerializer


https://www.django-rest-framework.org/api-guide/serializers/

유효성 검사 및 데이터베이스로의 저장을 지원한다.

데이터 변환 / 직렬화 지원

  • querySet/Model객체 ←→ Native Python 데이터타입, JSON/XML

Django의 Form/ModelForm과 유사

  • Serializer는 뷰 응답을 생성하는 데 범용적이고 강력한 방법을 제공
  • ModelSerializer는 Serializer생성을 위한 Shortcut

2. 비교


2.1. Feature 비교


Form / ModelForm

  • HTML 입력폼을 위한 입력에 대한 유효성 검사
  • 주로 Create/Update에 대한 처리에서 활용 → 장고 admin에서 실제로 활용되고 있다.
  • CreateView/UpdateView CBV를 통한 뷰 처리 → 단일 뷰

Serializer / ModelSerializer

  • 데이터 변환 및 직렬화 지원 (JSON포맷 등)
  • 주로 JSON 포맷 (주된 Web API 포맷) 입력에 대한 유효성 검사
  • List/Create 및 특정 Record(Resource)에 대한 Retrieve/Edit(update or partial_update)/Delete 등에서 활용
  • APIView를 통한 뷰 처리 → 단일 뷰
  • ViewSet을 통한 뷰 처리 → 2개 뷰 → 2개 URL 처리

2.2. 주된 호출 주체 비교


Form

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

Serializer

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

2.3. 클래스 정의 비교


Form/ModelForm : Form은 html폼을 효율적으로 처리하기 위한 목적을 갖는다.

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__'

Serializer/ModelSerializer : Serializer는 다양한 http클라이언트로부터의 API 요청을 처리하기 위한 목적을 갖기에, widget과 같은 인자는 없다.

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__'

2.4. FBV를 통한 요청/응답 비교 – Form vs Serializer


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, '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) # 커스텀 직렬화 루틴이 필요

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)

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)

2.5. 비교) CBV를 통한 요청 및 응답


2.5.1. 장고 기본

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>

2.5.2. DRF의 APIView

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")

2.6. 유효성 검사 수행 시점


Form

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 응답

Serializer

# 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()

2.7. 커스텀 유효성 검사 루틴 - clean_* vs validate_*


clean_*

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

validate_*

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

0개의 댓글