-
[Django REST framework] 튜토리얼 1 : Serializer코딩/🟢 DJANGO 2022. 2. 25. 17:32
DRF 란?
Django REST framework 의 줄임말.
Django 안에서 Restful API 서버를 쉽게 구축할 수 있는 라이브러리
(REST란 HTTP url과 HTTP method(CRUD)를 사용해서 API 가독성을 높인 구조화된 시스템아키텍쳐 (= 프레임워크)
이때 REST는 3가지 구성.
1. 자원
2. 행위
3. 표현
1. 자원 (HTTP URL)
: 서버에 존재하는 고유한 ID를 가진 자원서버
2. 행위 (HTTP method)
: 클라이언트가 자원을 조작하기 위해 사용하는 방식
- GET, POST, PUT, PATCH, DELETE
3. 표현 (Representation)
: 클라이언트가 서버로 요청을 보낸뒤 서버가 응답할때 보내주는 자원의 형식
- JSON, XML 등
이러한 REST 구조를 따르는 API를 Restful API라고 함.
프론트와 백엔드가 구분이 되면서 백엔드는 요청에 따른 데이터만 전달해주는 방식으로 발전됨.
JSON, XML 형식의 데이터를 프론트엔드에게 전달시켜주는 API 서버를 쉽게 만들수 있다.
그럼 JSON은 무엇일까?
JSON 이란 데이터 송수신을 자바스크립트 객체로써 수행할 수 있도록 하는, 가벼운 문자열 표현된 데이터.
Key-Value 패턴으로 표현이 되는 JSON은 말그대로 텍스트로 되어있음.
또한 프로그램 언어와 플랫폼에 독립적이라서 서로다른 시스템간의 객체를 교환에도 좋다.
그러나 객체를 교환하기 위해서는 직렬화라는 과정이 필요하다.
직렬화(Serializer)가 과정이 왜 필요할까?
데이터 구조를 송수신하거나 저장하기 위해, 전송에 적합한 형식으로 변환하는 과정을 말한다.
JSON은 문자열로 직렬화.
즉, 데이터를 전송하기 전에 최대한 데이터를 빠르게 읽을 수 있게 만드는 작업이 필요하다.
- 어떻게 그럼 빠르게 읽게 할 수 있을까?
클라이언트와 디비의 언어가 만약에 다르다면, 각각 해석해야하니 속도가 느릴테니,
공통의 언어로 표현되는 것이 제일 빠른 방법이다.
- 그럼 공통의 언어에 해당되는 건 어떤 것들이 있을까?
서버가 데이터를 저장하는 방식은 크게 2가지라고한다.
1. 값형식 데이터 : int, float, char, string
2. 레퍼런스 형식 데이터 : 메모리 번지를 저장해두고, 해당 번지에 비로소 값이 있음
이때, '저장/전송 가능한 데이터' 는 당연하게도 "값 형식 데이터"만 전송 가능하다.
자세한 내용은 밑에 있는 Jason Wang님의 의견을 살펴보자.
더보기더보기Jason Wang님의 의견과 지붕뚫고높이차의견을 살펴보자.
OKKY | 직렬화 하는 이유가?
자바책에는 객체를 파일에 저장하거나 파일에서 꺼내오기 위해서 또는 객체를 네트워크를 통해 전송하기 위해서는 미리 객체를 직렬화해야한다고 되어 있네요. 웹 어플 소스 분석하다보면 서
okky.kr
지붕뚫고높이차 님이 아주 자세하고 정확한 설명을 해 주셨습니다.
저는 좀더 언어 관점에서 설명드리겠습니다.
Java 든 C# 이든 C++ 이던 간에 데이터의 메모리 구조는 크게 다음 2가지로 나뉩니다.
- 값 형식 데이터: integer, float(single), charactor(또는 char 의 집합인 string) 등
- 오브젝트(레퍼런스) 형식 데이터: 메모리 번지(주소, Address)값 --> 주소값을 최종적으로 따라가면 값 형식 데이터를 참조 하게 됨. (C/C++) 또는 언어 차원에서 이 과정을 생략해줌 (C#, JAVA) --> 클래스의 인스턴스는 해당 프로세스의 메모리 상에서만 유효한 번지 주소를 갖는 오브젝트(레퍼런스) 데이터.
이 중에 '저장/전송 가능한 데이터' 는 당연하게도 값 형식 데이터만 전송 가능합니다.
오브젝트(레퍼런스) 형태의 참조 데이터(메모리 번지 주소 데이터)는 상식적으로도 파일 저장이나 네트워크 전송이 불가능합니다.일례로 32비트 시스템에서 Class A 의 인스턴스를 만들었고, 그 참조/주소값이 0x00121212 이었습니다.
그리고 이 참조/주소값 자체도 강제로 파일에 포함 시켜 저장하였습니다.
하지만 다음에 프로그램(서비스)를 다시 Start 시키고 이전에 저장했던 파일에서 0x00121212 참조/주소를 다시 읽어와도 클래스 A 의 인스턴스는 부활 할 수 없으며 이해할 수 도 없는 쓰레기 값일 뿐입니다.
네트워크 전송도 마찬가지로 받는 상대방 입장에서는 전달자가 사용한 참조/주소값 자체는 무의미 합니다. 서로 물리적으로 사용중인 메모리 공간(OS의 가상메모리 포함)은 일치하지 않기 때문입니다.
비트와 바이트와 메모리, 언어 등의 관점에서 이야기를 해보니 이렇습니다.
조금 더 이해를 돕고자 JAVA 언어의 관점에서 설명해 보겠습니다.
자바는 내부적으로 오브젝트(또는 Reference) 형식의 데이터를 많이 사용합니다.
그리고 오브젝트의 주소 메모리 번지 값 접근/편집을 일반적인 JAVA 코딩에 쓰지 않습니다.
(언어 차원에서 내부적으로 해결 해 줌)JAVA 의 클래스 설계에는 오브젝트 안에 오브젝트가 또 들어있을 수 있습니다. (인스턴스 포함 관계)
그것은 오브젝트 안에 내부적으로 다른 오브젝트를 참조할 수 있는 주소값이 담긴 것을 의미합니다.
이 주소값의 실체를 다 끌어와서 Primitive 한 값 형식 데이터로 전부 변조하는 작업을 바로 직렬화(Serialization)라 합니다.
--> XML, JSON 등의 데이터 구조를 떠올리면 이해가 빠를것입니다.
--> C/C++ 을 해보셨다면 좀 더 이해가 빠를 것입니다. (포인터 데이터를 모두 실제 값의 묶음 형식으로 전달, NPOD 데이터를 POD 데이터로 전달, 그리고 한방에 memcpy!)그리고 직렬화 된 데이터 형식은 언어에 따라 텍스트로 된 데이터 또는 바이너리 등의 모양을 띄게 됩니다. (어차피 텍스트든 바이너리든 결국 둘 다 Primitive 한 값들의 집합임)
결국 직렬화가 된 데이터는 최종적으로 오브젝트 타입이 없습니다. 모든것이 Primitive 한 값 형식의 데이터 묶음이며, 이것은 파일 저장이나 네트워크 전송시 파싱 할 수 있는 유의미한 데이터가 되는 것입니다. (데이터 중복을 줄이기 위한 테이블화가 일어나는지는 확인 필요. 어차피 이 부분은 언어마다, 규약마다 다를 것)그리고 또하나 특징은 현존하는 컴퓨터 머신들의 메모리 설계상 큰 데이터 덩어리를 순차적으로 읽어 오는 것이 가장 빠르기 때문에 직렬화 된 데이터는 RDBMS 구조랑은 완전 다르게, 일직선의 연속적인 값들의 집합인 형태를 띄게 됩니다. (대게 그렇습니다. 이것도 언어/규약 마다 다를 수 있습니다)
그래서 이렇게 전송/저장 가능한 데이터를 만드는 행위에 '직렬화(Serialization)' 라는 이름이 붙게 되었습니다.
정리하면 직렬화는 보통 파일 저장이나, 패킷 전송시에 '파싱할 수 있는 데이터를 만들기 위해' 사용됩니다.
+@ 로 프로세스 간에 데이터 전송에도 직렬화된 데이터가 사용 되는 이유는
대부분의 OS 가 현재 가상메모리를 운영 중이며 대부분의 OS 의 프로세스 구현은 서로 다른 가상메모리주소공간(Virtual Address Space, VAS) 를 갖기 때문에 역시 마찬가지로 오브젝트 타입의 참조값(결국 주소값)데이터 인스턴스를 직접 줄 수 없어서 직렬화된 데이터로의 교환을 주로 사용합니다.바로 이 직렬화(Serializer)를 지원하는 것이 DRF이다.
Serializer 는 직렬화를 담당하는 클래스.
이를 이용해서 DB data를 JSON data를 변환 (ORM, non ORM)
queryset, model instance 등의 복잡한 데이터를 python data type으로 변환시켜준다.
python data type으로 변환하면 json과 xml 컨텐츠 타입으로의 변환을 쉽게 만들어준다.
직렬화 과정을 자세히 살펴보면 이와 같다.
먼저 네트워크 속 데이터는 본질적으로 "네트워크 데이터는 연속된 바이트의 흐름일 뿐" 이다.
따라서 구조체를 바이트 배열로 만들어야하는데 이를 직렬화라고 한다.
즉 전송을 위한 데이터변환이라고 생각하자.
그리고 이러한 데이터를 받는 측에서는 사용하기 위해서 다시 구조체를 만들어야하기때문에
전송된 데이터를 읽을 수 있게 하는 것이 역직렬화 DeSerializer라고 한다
직렬화는 models를 serializer로 json 형태로 변환하는 것 외에도
- API 디버깅을 쉽게 만들어주며
- 코드를 정리해서 보안 이슈를 해결
- 데이터 validation도 해주는 역할을 한다고 한다.
> Reference .
https://www.django-rest-framework.org/tutorial/1-serialization/
1 - Serialization - Django REST framework
This tutorial will cover creating a simple pastebin code highlighting Web API. Along the way it will introduce the various components that make up REST framework, and give you a comprehensive understanding of how everything fits together. The tutorial is f
www.django-rest-framework.org
https://velog.io/@jcinsh/Django-DRF-0-%EA%B0%9C%EC%9A%94
https://joel-dev.site/67
https://leffept.tistory.com/298?category=950490이제 실습하면서 Serialization을 익혀보자.
기본적인 프로젝트 초기세팅후에 완료한 후 앱을 생성하자.
1. pip install list
pip install django pip install djangorestframework pip install pygments # "restframework 코드 하이라이팅" 를 위해 필요하다.
2. snippets 앱 생성
python manage.py startapp snippets
3. settings.py에 restramework, snippets tutorial/settings.py 추가
INSTALLED_APPS = [ ... 'rest_framework', 'snippets', ]
4. Snippet model snippets/models.py 추가 & migration
from django.db import models from pygments.lexers import get_all_lexers from pygments.styles import get_all_styles LEXERS = [item for item in get_all_lexers() if item[1]] LANGUAGE_CHOICES = sorted([(item[1][0], item[0]) for item in LEXERS]) STYLE_CHOICES = sorted([(item, item) for item in get_all_styles()]) class Snippet(models.Model): created = models.DateTimeField(auto_now_add=True) title = models.CharField(max_length=100, blank=True, default='') code = models.TextField() linenos = models.BooleanField(default=False) language = models.CharField(choices=LANGUAGE_CHOICES, default='python', max_length=100) style = models.CharField(choices=STYLE_CHOICES, default='friendly', max_length=100) class Meta: ordering = ['created']
python manage.py makemigrations snippets python manage.py migrate snippets
보통 여기서 뷰로 넘어갔지만 직렬화 과정이 추가된다.
5. Serializer class 만들기 tutorial/serializers.py 에 추가
from rest_framework import serializers from snippets.models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES class SnippetSerializer(serializers.Serializer): id = serializers.IntegerField(read_only=True) title = serializers.CharField(required=False, allow_blank=True, max_length=100) code = serializers.CharField(style={'base_template': 'textarea.html'}) linenos = serializers.BooleanField(required=False) language = serializers.ChoiceField(choices=LANGUAGE_CHOICES, default='python') style = serializers.ChoiceField(choices=STYLE_CHOICES, default='friendly') def create(self, validated_data): """ 유효한 데이터가 들어오면, Snippet 인스턴스를 만들고 반환함 """ return Snippet.objects.create(**validated_data) def update(self, instance, validated_data): """ 유효한 데이터가 들어오면, Snippet 인스턴스를 업데이트하고 반환함 """ instance.title = validated_data.get('title', instance.title) instance.code = validated_data.get('code', instance.code) instance.linenos = validated_data.get('linenos', instance.linenos) instance.language = validated_data.get('language', instance.language) instance.style = validated_data.get('style', instance.style) instance.save() return instance
직렬 변환기 클래스의 첫 번째 부분은 직렬화/역직렬화되는 필드를 정의한다.
- create( ) 와
- update( ) 메소드는
serializer.save() 호출 시 완전한 인스턴스가 생성되거나 수정되는 방법을 정의한다.
이는 검색가능한 API가 표시되어지는 방식을 제어하는데 특히 유용하다.
나중에 살펴볼 것이지만, ModelSerializer 클래스를 사용해서 시간을 절약할 수도 있지만,
지금은 명시적으로 Serializer라고 정의하고 넘어가자.
6. Serializer를 이용해보자.
python manage.py shell
작업할 코드 snippet을 만들어보자.
from snippets.models import Snippet from snippets.serializers import SnippetSerializer from rest_framework.renderers import JSONRenderer from rest_framework.parsers import JSONParser snippet = Snippet(code='foo = "bar"\n') snippet.save() snippet = Snippet(code='print("hello, world")\n') snippet.save()
이제 우리는 데이터베이스에 적재한 snippet 인스턴스를 가지고 놀 수 있다.
이 인스턴스 중 하나를 선택해서 serializing을 해보자.
serializer = SnippetSerializer(snippet) serializer.data # {'id': 2, 'title': '', 'code': 'print("hello, world")\n', 'linenos': False, 'language': 'python', 'style': 'friendly'}
7. 이 시점에서 모델 인스턴스를 Python 기본 데이터 유형으로 변환
모델 인스턴스 -> Python 기본데이터 유형
content = JSONRenderer().render(serializer.data) content # b'{"id": 2, "title": "", "code": "print(\\"hello, world\\")\\n", "linenos": false, "language": "python", "style": "friendly"}'
8. 역직렬화도 비슷하다.
1) 먼저 스트림을 Python 기본 데이터 유형으로 구문을 해석한다.
: 스트림 -> Python 기본데이터 유형
import io stream = io.BytesIO(content) data = JSONParser().parse(stream)
2) 그런 다음 해당 기본 데이터 유형을 완전히 채워진 개체 인스턴스로 복원합니다.
: Python 기본데이터 유형 -> 모델 인스턴스
serializer = SnippetSerializer(data=data) serializer.is_valid() # True serializer.validated_data # OrderedDict([('title', ''), ('code', 'print("hello, world")\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]) serializer.save() # <Snippet: Snippet object>
이제 우리는 model 인스턴스 대신에 querysets 또한 serialize 할 수 있다!
이를 위해선 단순히 Serializer 인수에 many=True만 하면 된다.
serializer = SnippetSerializer(Snippet.objects.all(), many=True) serializer.data # [OrderedDict([('id', 1), ('title', ''), ('code', 'foo = "bar"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('id', 2), ('title', ''), ('code', 'print("hello, world")\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('id', 3), ('title', ''), ('code', 'print("hello, world")'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])]
9. ModelSerializer 사용.
우리 SnippetSerializer클래스는 Snippet모델에도 포함된 많은 정보를 복제하고 있다.
코드를 좀 더 간결하게 유지할 수 있다면 좋을 것입니다.
ModelSerializer 클래스를 사용하여 직렬 변환기를 리팩토링 방법을 살펴본다.
파일 snippets/serializers.py 에 SnippetSerializer 클래스를 다음으로 바꿀 수 있다 .
class SnippetSerializer(serializers.ModelSerializer): class Meta: model = Snippet fields = ['id', 'title', 'code', 'linenos', 'language', 'style']
이를 통해 Serializer의 한 가지 좋은 속성은 해당 표현을 print해서
직렬 변환기 인스턴스의 모든 필드를 검사할 수 있다는 것이다.
- Serializer를 통한 인스턴스의 모든 필드 검사
from snippets.serializers import SnippetSerializer serializer = SnippetSerializer() print(repr(serializer)) """ ModelSerializer를 통해 Snippets 모델 검사 """ # SnippetSerializer(): # id = IntegerField(label='ID', read_only=True) # title = CharField(allow_blank=True, max_length=100, required=False) # code = CharField(style={'base_template': 'textarea.html'}) # linenos = BooleanField(required=False) # language = ChoiceField(choices=[('Clipper', 'FoxPro'), ('Cucumber', 'Gherkin'), ('RobotFramework', 'RobotFramework'), ('abap', 'ABAP'), ('ada', 'Ada')... # style = ChoiceField(choices=[('autumn', 'autumn'), ('borland', 'borland'), ('bw', 'bw'), ('colorful', 'colorful')...
ModelSerializer클래스는 단순히 Serializer 클래스 생성하기 위한 것이다.
- 자동으로 결정된 필드 집합입니다.
- create( )및 update( )메서드 에 대한 간단한 기본 구현입니다 .
10. Serializer 를 사용하여 일반 Django View 작성
새 직렬 변환기 클래스를 사용하여 일부 API 보기를 작성하는 방법을 살펴보겠습니다.
당분간은 REST 프레임워크의 다른 기능을 사용하지 않을 것이며 뷰를 일반 Django 뷰로 작성할 것입니다.
1) snippets/views.py 다음을 추가합니다.
from django.http import HttpResponse, JsonResponse from django.views.decorators.csrf import csrf_exempt from rest_framework.parsers import JSONParser from snippets.models import Snippet from snippets.serializers import SnippetSerializer
API의 루트는 모든 기존 스니펫을 나열하거나 새 스니펫을 생성하는 것을 지원하는 View가 될 것입니다.
@csrf_exempt def snippet_list(request): """ List all code snippets, or create a new snippet. """ if request.method == 'GET': snippets = Snippet.objects.all() serializer = SnippetSerializer(snippets, many=True) return JsonResponse(serializer.data, safe=False) elif request.method == 'POST': data = JSONParser().parse(request) serializer = SnippetSerializer(data=data) if serializer.is_valid(): serializer.save() return JsonResponse(serializer.data, status=201) return JsonResponse(serializer.errors, status=400) @csrf_exempt def snippet_detail(request, pk): """ Retrieve, update or delete a code snippet. """ try: snippet = Snippet.objects.get(pk=pk) except Snippet.DoesNotExist: return HttpResponse(status=404) if request.method == 'GET': serializer = SnippetSerializer(snippet) return JsonResponse(serializer.data) elif request.method == 'PUT': data = JSONParser().parse(request) serializer = SnippetSerializer(snippet, data=data) if serializer.is_valid(): serializer.save() return JsonResponse(serializer.data) return JsonResponse(serializer.errors, status=400) elif request.method == 'DELETE': snippet.delete() return HttpResponse(status=204)
CSRF 토큰이 없는 클라이언트에서 이 View에 POST할 수 있기를 원하기 때문에 csrf_exempt 로 표시해야 합니다.
우선은 비효율적인 방법이지만 합리적인 동작을 배우기 위해 이런 View를 사용한 것이다.
- snippets/urls.py 생성 후 view와 연결
from django.urls import path from snippets import views urlpatterns = [ path('snippets/', views.snippet_list), path('snippets/<int:pk>/', views.snippet_detail), ]
- 루트 url 에도 명시
from django.urls import path, include urlpatterns = [ path('', include('snippets.urls')), ]
11. Web API 에 대한 첫번째 시도 테스트
python manage.py runserver Validating models... 0 errors found Django version 4.0,1 using settings 'tutorial.settings' Starting Development server at http://127.0.0.1:8000/ Quit the server with CONTROL-C.
여기서 주목해야하는 것은 Validation models를 하는 것.
'코딩 > 🟢 DJANGO' 카테고리의 다른 글
[Django REST framework] 튜토리얼 3 : 클래스 기반 View (0) 2022.02.26 [Django REST framework] 튜토리얼 2 : 요청 및 응답 (0) 2022.02.26 [DJANGO] ORM : Aggregation, Annotate (0) 2021.11.10 [DJANGO] 카카오 소셜로그인 구현 (0) 2021.10.03 [DJANGO] 인증과 인가(4) : Login decorator (0) 2021.09.25