2023/09/19 [TIL] DRF Serializer로 데이터 Json 형식으로 변환하여 데이터 보내기 / Article List (Read)

장현웅·2023년 9월 18일
0

🥚 DRF (Django REST framework)


Django 기반 웹 어플리케이션에서 RESTful API를 쉽게 개발할 수 있도록 도와주는 파이썬 라이브러리

- REST (Representational State Transfer)

웹 개발에서 사용되는 특별한 방식 또는 규칙의 모음

  • HTTP와 웹의 기존 기술 활용
    REST는 웹의 기존 기술과 특히 HTTP 프로토콜을 활용한다. HTTP는 웹에서 데이터를 주고받는 데 사용되는 통신 규약인데, 이를 통해 클라이언트(브라우저)와 서버 간에 정보를 요청하고 응답하는데 REST는 이 HTTP 프로토콜을 사용하여 데이터와 서비스를 주고받는 방법을 정의한다.
  • Stateless (상태 없음)
    REST API는 상태를 저장하지 않는 방식으로 동작한다. 이것은 각각의 HTTP 요청이 서로 독립적이고 연관되지 않는다는 의미로, 서버는 각 요청을 처음부터 다루며, 요청과 요청 사이에 상태 정보를 저장하지 않는다. 이것은 서버가 간단하게 요청을 처리할 수 있고, 서버와 클라이언트 간의 통신이 유연하다는 장점이 있다.

이 REST 제약 조건들을 잘 준수함을 'Restful' 하다고 표현한다.

예를 들면, 웹에서 정보를 요청할 때!! 브라우저는 서버에 요청을 보내고, 서버는 요청을 받아서 응답을 보낸다. 이 때 서버는 클라이언트의 이전 요청에 대한 정보를 저장하지 않는다. 모든 요청은 독립적으로 처리되며, 서버는 이전 요청의 상태를 유지하지 않는다.

REST의 주요 목표는 웹 서비스를 단순하게 만들고, 새로운 기능을 쉽게 추가하는 등 확장이 가능하게 하며, 효율적으로 데이터를 주고받을 수 있는 구조를 제공하는 것이다. 이를 통해 개발자들이 편하게 웹 서비스를 개발하고, 클라이언트와 서버간의 통신을 효과적으로 관리할 수 있게 된다.

- RESTful API (Representational State Transferful Application Programming Interface)

RESTful API는 웹 서비스를 만드는 방법 중 하나로, 웹에서 데이터를 주고받을 때 사용하는 규칙이다.

간단하게 말하면, 웹 서비스를 쉽게 만들고 데이터를 관리하는 데 사용하는 웹 개발 규칙이다.

RESTful API는 클라이언트(웹 브라우저)와 웹 서버간의 통신에 사용하는 프로토콜을 기반으로 동작 한다.

자원(Resource) 즉 모든 데이터를 고유한 주소인 URI(Uniform Resource Identifier)로 표현한다. 예를 들어, 각각의 포스트가 하나의 고유한 URL을 가지고 있고, 모두 다른 URL 패턴을 가진다.

HTTP 메서드(GET, POST, PUT, DELETE 등)를 사용하여 자원(데이터)에 대한 CRUD(Create, Read, Update, Delete) 작업을 수행한다.

- 자원 (Resource)

RESTful API에서는 사용자, 제품, 주문, 댓글 등과 같은 모든 것이 자원(데이터)으로 표현되며, 각 자원은 고유한 URL로 식별된다.

- API (Application Programming Interface)

다른 소프트웨어 애플리케이션과 상호작용하기 위한 인터페이스나 규약.

API는 애플리케이션 간에 데이터를 교환하고 특정 기능을 사용할 수 있게 해준다.

메뉴판과 같은 데이터와 기능의 창구라고 생각하면 된다.

이를 통해 다른 애플리케이션에서 생성된 데이터를 검색하거나 업데이트할 수 있고, 다른 회사 또는 서비스 제공 업체와 API(메뉴판)를 통합하여 다른 곳의 서비스를 사용할 수 있다.

예를 들어, 소셜 미디어 플랫폼의 API를 사용하여 사용자가 소셜 미디어 계정과 상호작용할 수 있다.

이전에 연습했던 클라이언트와 서버의 통신 방법은 renderHttpResponse로 프론트엔드 부분도 다뤘다면,

이제부터는 고유 백엔드부분만 다뤄보기 위해서, views.py에서도 함수 대신 클래스를 적극 활용하고, 데이터베이스의 데이터를 다루는 방식에도 더 편리한 방법을 사용할 것이다.

🐣 퓨어 장고 VS DRF


DRF를 사용하기 전 일반 장고에서는 HttpResponse로 사용자에게 어떠한 응답을 보내주거나, 모델 데이터를 key:value 형태로 변수에 저장해서, render함수로 template와 함께 클라이언트로 보내줬다.

from django.shortcuts import render
from article.models import Article

def index(request):
	if request.method == 'GET':
    	all_articles = Article.objects.all()
        return render('index.html',{'all_articles': tem_all_articles})

rander나 HttpResponse는 프론트엔드도 다루기 때문에 DRF에서는 백엔드만의 영역인 DRF의 Response 클래스를 사용하여 응답 객체를 반환한다.

from rest_framework.response import Response


def index(request):
	return Response('데이터 넘겨주기')

# [Error] .accepted_renderer not set on Response

[Error] .accepted_renderer not set on Response



API 구현 중, DRF에서 응답 객체를 생성할 때, 해당 응답 객체의 렌더러(Renderer)가 설정되지 않았거나 @api_view 데코레이터를 추가하지 않았을 때 발생하는 오류

렌더러(Renderer) 클래스

서버가 클라이언트에게 데이터를 어떤 형식으로 반환할지를 결정한다.

렌더러 클래스를 설정하지 않으면 DRF가 어떤 형식으로 데이터를 반환해야 하는지 알 수 없기 때문에 이 오류가 발생한다.

@api_view 데코레이터

DRF를 사용할 Django 앱의 뷰 함수에 이 데코레이터를 추가하면 함수 기반의 뷰를 DRF의 API 뷰로 변환할 수 있다.

DRF의 APIView 클래스를 상속받은 API 뷰를 사용할 때는 이 데코레이터를 추가해 줄 필요가 없다.

@api_view 데코레이터는 해당 뷰 함수가 어떤 HTTP 요청 메서드를 처리할지 지정한다.

브라우저블 API라고도 하며, 생성된 API를 브라우저에서 사용 및 조작을 할 수 있게끔 해준다.

🐤 API 만들기


처음은 클래스 기반 뷰로 넘어가기 전에,
함수 기반 뷰를 DRF의 API 뷰로 사용하기 위해
@api_view데코레이터를 사용해서 DRF의 Response 클래스를 사용하여 응답 객체를 생성하는 것부터 해보자.

from rest_framework.response import Response
from rest_framework.decorators import api_view

@api_view(['GET'])
def index(request):
	return Response('데이터 넘겨주기')

🐥 Response 클래스 응답 객체 알아보기


Response 클래스

  • 첫 번째 인자로 클라이언트에게 반환할 데이터 또는 응답 본문을 받는다.

    일반적으로 이 첫 번째 인자에는 Python 객체(문자열, 리스트, 딕셔너리 등)를 전달하며, DRF는 이 데이터를 적절한 미디어 타입으로 직렬화하여 클라이언트에게 반환한다.

    주로 JSON 형식의 데이터를 반환하려면 딕셔너리 형태의 데이터를 전달하고, DRF는 이를 JSON으로 직렬화한다.

  • 두 번째 인자로는 Http status code를 받는다.

    이는 선택적으로 지정할 수 있는 매개변수이고, 기본적으로 200 OK 상태 코드가 사용된다. 다른 상태 코드를 설정하려면 매개변수 stutus를 사용하면 된다.

DRF에서는 JSON 랜더러가 자동으로 기본 설정 및 활성화가 되어 있다. 그것을 권장하기 때문에, 아래와 같이 랜더러의 추가 별도 설정이 필요하지 않은 데이터 타입들은 자동으로 JSON 직렬화가 되어 반환된다.

  • 텍스트 문자열
  • 리스트
  • 딕셔너리
  • XML
  • HTML
@api_view(['GET'])
def index(request):
	string_data = "string"
	return Response(string_data)

# "string"
@api_view(['GET'])
def index(request):
	list_data =  ["리스트", "가능"]
	return Response(list_data)

# ["리스트","가능"]
@api_view(['GET'])
def index(request):
	dic_data =  {"딕셔너리":"가능"}
	return Response(dic_data)

# {"딕셔너리": "가능"}
@api_view(['GET'])
def index(request):
	list_datas =   ["string", ["리스트","안에"], {"여러":"타입"}, {"자료형":"가능"}]
	return Response(list_datas)

# ["string", ["리스트","안에"], {"여러":"타입"}, {"자료형":"가능"}]
  • 이번엔 한번 모델의 필드와 필드값을 딕셔너리 형태로 보내보자.
from rest_framework.response import Response
from rest_framework.decorators import api_view
from articles.models import Article

@api_view(['GET'])
def index(request):
	all_articles = Article.objects.all()
	first_article = all_articles[0]
	article_data = {
        "title":first_article.title,
        "content":first_article.content,
        "created_at":first_article.created_at,
        "updated_at":first_article.updated_at,
    }
	return Response(article_data)

# {
      "title": "새로운 글",
      "content": "아하하",
      "created_at": "2023-09-18T10:35:42.213422Z",
      "updated_at": "2023-09-18T10:35:42.213422Z"
   }

딕셔너리 같이 간단한 데이터 형식을 Response 클래스로 반환해줄 때는 Response 객체가 알아서 JSON 직렬화가 되어 string으로 변환되어 반환된다.

🐥 데이터베이스에 저장되어있는 모델 인스턴스 데이터를 Response 클래스로 클라이언트에게 Response 응답 객체 반환하기


Article이라는 모델이 있다고 하자.

from django.db import models

class Article(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    
    def __str__(self):
    	return str(self.title)

데이터베이스에는 2개의 데이터가 저장되어 있다.

위의 두 데이터들을 ORM문을 작성하여 가져와서 Response객체로 클라이언트에게 보내보자.

from rest_framework.response import Response
from rest_framework.decorators import api_view
from articles.models import Article

@api_view(['GET'])
def index(request):
	all_articles = Article.objects.all()
	return Response(all_articles)

# [Error]  Object of type Article is not JSON serializable : Article 유형의 객체는 JSON 직렬화가 가능하지 않습니다. 

[Error] Object of type Article is not JSON serializable : Article 유형의 객체는 JSON 직렬화가 가능하지 않습니다.


print(all_articles) : <QuerySet [<Article: 새로운 글>, <Article: 이번엔 제목들만 다 뽑아볼까>]>

Response 클래스로 클라이언트에게 모델 인스턴스 데이터를 보내주기 위해서는 JSON 직렬화라는 작업이 필요해보인다.

그럼 이번에는 모델의 필드 중 하나의 값을 반환해보자.

@api_view(['GET'])
def index(request):
	all_article_titles = Article.objects.values('title')
	first_title = all_article_titles[0]
	second_title = all_article_titles[1]
    
	# return Response(first_title)
	# {"title": "새로운 글"}

	# return Response(second_title)
    # {"title": "이번엔 제목들만 다 뽑아볼까"}

	# return Response([first_title, second_title])
	# [{"title": "새로운 글"}, {"title": "이번엔 제목들만 다 뽑아볼까"}]
    
    # return Response(all_article_titles)
    # [{"title": "새로운 글"}, {"title": "이번엔 제목들만 다 뽑아볼까"}]
    
    # return Response({"첫 번째 아티클 제목":all_article_titles[0]['title'], "두 번째 아티클 제목" : all_article_titles[1]['title']})
    # {"첫 번째 아티클 제목": "새로운 글","두 번째 아티클 제목": "이번엔 제목들만 다 뽑아볼까"}
    
# 따로 딕셔너리를 만들어주거나 보내줄 것들을 만들어주는게 귀찮고 반복적이다. 개발자들은 자동화에 대해서 분명 생각해봤을 것이다.

# 그래서 나온게 장고 rest_framework이고 이때 쓰는 것이 serialize 기능이다.

Response 클래스로 데이터베이스에서 가져온 모델 인스턴스 자체를 반환할 때 JSON 랜더러가 자동으로 기본 설정 및 활성화되어 있음에도 JSON 직렬화가 자동으로 되지 않는 이유는,

DRF가 모델 인스턴스를 직렬화하는 방법을 알지 못하기 때문이다.

DRF에서 기본적으로 제공하는내장 직렬화기능에는 데이터베이스 모델의 인스턴스를 JSON 직렬화하는 기능을 제공하지 않아서 따로 명시적으로 직렬화를 수행해야한다.

일반적으로 모델 인스턴스 자체를 반환해줄 때와 같이 복잡한 데이터 구조나 관계형 데이터를 다룰 때는 JSON으로 직렬화하기 위해서 DRF의 Serializer를 사용하여 데이터를 직렬화한 후 Response 객체에 넣어 반환해야 한다.

🐓 Serializer & serialize.py


Serializer란?

Django Rest Framework(DRF)에서 모델 데이터를 직렬화하고 역직렬화하는 데 사용되는 클래스

직렬화란?
모델의 데이터를 JSON 또는 다른 형식으로 변환하는 과정

역직렬화란?
클라이언트에서 받은 데이터를 모델 객체로 변환하는 과정

serialize.py

Serializer 클래스를 정의한다.
Serializer 클래스를 사용하여 모델 인스턴스를 직렬화하고 역직렬화하고, 클래스 내 로직을 커스텀하여 필드 유효성 검사, 데이터 가공 등의 작업들을 가능케 한다.

🐔 모델 데이터 보내보기


[ serialize.py ]

from rest_framework import serializers
from articles.models import Article

class ArticleSerializer(serializers.ModelSerializer):
    class Meta:
        model = Article
        fields = "__all__"

# fields = "__all__" : 모든 필드를 직렬화

[ views.py ]

from rest_framework.response import Response
from rest_framework.decorators import api_view
from articles.models import Article
from articles.serializers import ArticleSerializer


@api_view(['GET'])
def index(request):
    all_articles = Article.objects.all()
    first_article = all_articles[0]
    serializer = ArticleSerializer(first_article)
    return Response(serializer)

# [Error] You passed a Serializer instance as data, but probably meant to pass serialized `.data` or `.error`. representation.

[Error] You passed a Serializer instance as data, but probably meant to pass serialized .data or .error. representation. : Serializer 인스턴스를 데이터로 전달했지만 직렬화된 '.data' 또는 '.error'를 전달하려고 했을 것이다.

serializer에는 현재 ArticleSerializer 클래스의 인스턴스가 들어가 있다. 해당 모델의 필드를 정의하고 어떤 필드를 어떻게 직렬화 또는 역직렬화하는지에 대해 정의되어있다.

.data 속성을 사용하면 이 ArticleSerializer 인스턴스 객체에 저장된 직렬화된 데이터를 가져올 수 있다.
실제 객체를 직렬화한 데이터는 serializer.data에 저장되어있다.

이렇게 얻은 데이터를 Response 객체에 전달해서 클라이언트에게 반환된다.

@api_view(['GET'])
def index(request):
    all_articles = Article.objects.all()
    first_article = all_articles[0]
    serializer = ArticleSerializer(first_article)
    return Response(serializer.data)

#  {"id": 1,"title": "새로운 글","content": "아하하","created_at": "2023-09-18T19:35:42.213422+09:00","updated_at": "2023-09-18T19:35:42.213422+09:00"}
print(serializer)

#
ArticleSerializer(<Article: 새로운 >):
    id = IntegerField(label='ID', read_only=True)
    title = CharField(max_length=100)
    content = CharField(style={'base_template': 'textarea.html'}) 
    created_at = DateTimeField(read_only=True)
    updated_at = DateTimeField(read_only=True)
print(serializer.data)
#
{'id': 1, 'title': '새로운 글', 'content': '아하하', 'created_at': '2023-09-18T19:35:42.213422+09:00', 'updated_at': '2023-09-18T19:35:42.213422+09:00'}

한 개의 모델 데이터를 보내봤으니 여러 모델 데이터를 보내보자.

@api_view(['GET'])
def index(request):
    all_articles = Article.objects.all()
    all_serializer = ArticleSerializer(all_articles)
    return Response(all_serializer.data)

# [Error] Got AttributeError when attempting to get a value for field `title` on serializer `ArticleSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `QuerySet` instance.
Original exception text was: 'QuerySet' object has no attribute 'title'.
print(all_serializer)
#
ArticleSerializer(<QuerySet [<Article: 새로운 >, <Article: 이번
 제목들만  뽑아볼까>]>):
    id = IntegerField(label='ID', read_only=True)
    title = CharField(max_length=100)
    content = CharField(style={'base_template': 'textarea.html'}) 
    created_at = DateTimeField(read_only=True)
    updated_at = DateTimeField(read_only=True)

[Error] Serializer ArticleSerializer에서 title 필드 값을 가져오려고 할 때 AttributeError가 발생했다. 직렬 변환기 필드의 이름이 잘못 지정되어 'QuerySet' 인스턴스의 속성이나 키와 일치하지 않을 수 있다. 원래 예외 텍스트는 'QuerySet' 개체에 '제목' 속성이 없다.

ArticleSerializer에 대한 인스턴스를 생성할 때 여러 개의 객체에 대한 직렬화를 위해서는 many=True 옵션을 추가해줘야한다.

from rest_framework.response import Response
from rest_framework.decorators import api_view
from articles.models import Article
from articles.serializers import ArticleSerializer


@api_view(['GET'])
def index(request):
    all_articles = Article.objects.all()
    all_serializer = ArticleSerializer(all_articles, many=True)
    print(all_serializer)
    return Response(all_serializer.data)

# [{"id": 1,"title": "새로운 글","content": "아하하","created_at": "2023-09-18T19:35:42.213422+09:00","updated_at": "2023-09-18T19:35:42.213422+09:00"
,{"id": 2,"title": "이번엔 제목들만 다 뽑아볼까","content": "리스트에 다 넣어볼까","created_at": "2023-09-18T20:19:35.662614+09:00","updated_at": "2023-09-18T20:19:35.662614+09:00"}]
print(all_serializer.data)

#
[OrderedDict([('id', 1), ('title', '새로운 글'), ('content', '아하
하'), ('created_at', '2023-09-18T19:35:42.213422+09:00'), ('updated_at', '2023-09-18T19:35:42.213422+09:00')]), OrderedDict([('id', 
2), ('title', '이번엔 제목들만 다 뽑아볼까'), ('content', '리스트 
에 다 넣어볼까'), ('created_at', '2023-09-18T20:19:35.662614+09:00'), ('updated_at', '2023-09-18T20:19:35.662614+09:00')])]
[19/Sep/2023 16:50:23] "GET /articles/index/ HTTP/1.1" 200 7483
[OrderedDict([('id', 1), ('title', '새로운 글'), ('content', '아하
하'), ('created_at', '2023-09-18T19:35:42.213422+09:00'), ('updated_at', '2023-09-18T19:35:42.213422+09:00')]), OrderedDict([('id', 
2), ('title', '이번엔 제목들만 다 뽑아볼까'), ('content', '리스트 
에 다 넣어볼까'), ('created_at', '2023-09-18T20:19:35.662614+09:00'), ('updated_at', '2023-09-18T20:19:35.662614+09:00')])]

🐔 마무리 핵심


Serializer는 템플릿, 뷰 함수 쪽에 우리가 해야할 일을 덜어주는 역할인 것 같다. 딕셔너리 형태로 (역)직렬화 하는 작업을 담당하고, (역)직렬화 정의는 Serializer에서 하고, 그 데이터는 .data속성 값에 넣어둔다.

리스트의 형태로 여러개를 직렬화하여 넘겨줬다.

여기까지 json을 돌려주는 백엔드를 만들고, Serializer를 통해 데이터 조회까지 해봤다.

이 형태로 데이터를 제공해주면 프론트엔드는 이것을 원하는 형식으로 그려주면 된다.

이렇게 된 메뉴판 API가 있으니 이걸 열어두면 날씨 정보 제공 API처럼 횟수당 얼마로 파는 등의 사업도 가능하다.

다음 시간에는 Serializer로 클라이언트로부터 데이터를 받아서 데이터베이스에 데이터를 생성하는 것을 해보겠다.


🥚🐣🐤🐥🐓🐔

0개의 댓글