원래는 Django 자체에 관한것을 계속적으로 포스팅하고 있었지만,  RESTFRAMEWORK를 실제로 사용할 일이 생겨서 정리차원으로 RestFulFramwork 공식 홈페이지에서 제공하는 튜토리얼을 제가 공부한 방식대로 번역한 글입니다.


== 환경설정 ==


1. virtual env 설정.


virtualenv env
source env/bin/activate

2. django와 restfulframwork 설치.


pip install django
pip install djangorestframework
pip install pygments # Syntax Highlight용.

프로젝트를 시작해봅시다.


cd ~
django-admin.py startproject tutorial
cd tutorial

app을 하나 만들어주시고~

python manage.py startapp snippets
rest_framework와 방금만든 snippets를 settings.py안의 INSTALL_APPS에 추가해봅시다.


INSTALLED_APPS = (
    ...
    'rest_framework',
    'snippets.apps.SnippetsConfig',
)

[모델 만들기]


간단하게 snippet 모델을 한번 만들어 보겠습니다.


snippets/models.py에 들어가서 해당 파일을 수정해봅시다.


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',)

그후 makemigration과 migrate를 해줍시다~.


python manage.py makemigrations snippets
python manage.py migrate

[직렬화 클래스 만들기]


WEB으로 API를 만들기 전에 중요한 일은 JSON데이터를 파이썬에서 제공하는 Dictionary객체로 직렬화하고 비직렬화 하는 방법이 제일 중요할겁니다. 가장 기초적이기도 하구요.

Django에서 제공하는 Model의 형식과 비슷한 serializers.py를 선언해서 작업을 수행할 수 있습니다.

snippets 디렉토리에 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
    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')

위의 부분은 serialize / deserialize 되는 필드를 정의하는 겁니다.

create() 및 update() 메서드는 serializer.save()를 호출 할때, 인스턴스가 create 되거나 update되는것을 정의합니다.


serializer 클래스는 Django Form 클래스와 비슷하며, required, max_length 및 default와 같은 여러가지 필드에 유효성 검사 Flag를 포함 합니다.


[ 직렬화 / 비직렬화 방법 ] 


우선 해당 방법을 배우기 전에 django의 shell에 접속합니다.

python manage.py shell
그리고 아래의 코드를 한번 입력해 봅시다.

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을 생성하였습니다.


해당 코드를 한번 serialize 해보겠습니다.


serializer = SnippetSerializer(snippet)
serializer.data
# {'id': 2, 'title': u'', 'code': u'print "hello, world"\n', 'linenos': False, 'language': u'python', 'style': u'friendly'}

위의 시점에서 모델 인스턴스를 Python 에서 제공하는 딕셔너리 데이터 유형으로 변환하였고, 직렬화를 마루리 하기위해서 데이터를 json으로 변환합니다.


content = JSONRenderer().render(serializer.data)
content
# '{"id": 2, "title": "", "code": "print \\"hello, world\\"\\n", "linenos": false, "language": "python", "style": "friendly"}'

자 여기까지는 OK이라고 가정하고 그렇다면 JSON 데이터를 Python 딕셔너리 타입으로 바꾸려면 어떻게해야할까요?


from django.utils.six import BytesIO

stream = BytesIO(content)
data = JSONParser().parse(stream)

우선 JSONParser를 사용해서 해당 stream을 파싱한 후에,

SnippetSerializer에 해당 값을 주입해서 완전한 객체 인스턴스로 복원시킵니다. (아래코드를 보세요)


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>

또한 Django의 쿼리셋을 model대신에 직렬화 할 수도 있습니다.

이럴 때는 간단하게 many=True를 매개변수에 집어넣으면 됩니다.


serializer = SnippetSerializer(Snippet.objects.all(), many=True)
serializer.data
# [OrderedDict([('id', 1), ('title', u''), ('code', u'foo = "bar"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('id', 2), ('title', u''), ('code', u'print "hello, world"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('id', 3), ('title', u''), ('code', u'print "hello, world"'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])]

[ ModelSerializers 사용 ]


SnippetSerializer 클래스는 Snippet 모델에 포함된 많은 정보를 복제하고 있습니다. 그렇지만 실제로 보면 코드가 너무 복잡합니다. 코드의 내용만 좀 다를뿐 중복된 내용도 존재하구 있구요.

Django에서 From 클래스와 ModelForm 클래스를 제공하는 것 처럼 RESTFUL FRAMEWORK도 Serializer클래스와 ModelSerializer 클래스를 모두 지원합니다.


자, 그럼 ModelSerializer 클래스를 사용해서 serializer를 리팩토링 하는 방법을 실제로 한번 해보겠습니다.

파일 snippets / serializers.py 파일을 다시 열고, SnippetSerializer 클래스를 바꿔줍니다.


class SnippetSerializer(serializers.ModelSerializer):
    class Meta:
        model = Snippet
        fields = ('id', 'title', 'code', 'linenos', 'language', 'style')

serializer 가 가지고 있는 장점 중 하나는 serializer 인스턴스의 모든 필드를 검사 할 수 있다는 점입니다. 

Django 쉘을 python manage.py 쉘로 엽니다.


from snippets.serializers import SnippetSerializer
serializer = SnippetSerializer()
print(repr(serializer))

* ModelSerializer 는 아래와 같은 기능을 합니다.

1. 자동으로 필드 셋을 결정해줍니다.

2. create 와 update 메소드를 기본적으로 구현해줍니다.


[ 실제로 Django 의 View에 적용해보기]


snippets/views.py 파일에 아래를 추가합니다.


from django.http import HttpResponse, JsonResponse
from django.views.decorators.csrf import csrf_exempt
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer

API의 루트는 모든 snippet을 나열하거나 새로운 snippet을 만드는것을 지원하는 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 토큰이 없는 클라이언트로부터 이 View로 POST를 할 수 있기를 원한다면 View에다가 csrf_exempt 데코레이터를 추가해줘야 합니다.

이것은 일반적으로 이렇게 구현하면 아니지만, REST Framwork view는 실제로 이러한 방법을 사용하지 않지만 지금은 설명을 위해 사용할것입니다.


또한 개별 snippet에 해당하는 view가 필요하며, snippet을 검색 업데이트 삭제 하는데 사용 되어질 수 있습니다.


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

마지막으로 이러한 view을 연결해봅시다. snippets/urls.py 파일을 만듭니다.


from django.conf.urls import url
from snippets import views

urlpatterns = [
    url(r'^snippets/$', views.snippet_list),
    url(r'^snippets/(?P<pk>[0-9]+)/$', views.snippet_detail),
]

그리고 totorials/urls.py에 해당 라인을 추가합니다.


from django.conf.urls import url, include

urlpatterns = [
    url(r'^', include('snippets.urls')),
]

그렇지만 현재 문제점이 존재하기는 합니다. 정형화된 json을 보내지 않거나 request에 정상적인 데이터를 넣지않으면 500에러가 발생합니다. 해결방법은 다음 포스팅에서 정리하도록 하겠습니다.


[ API 테스팅 시도하기 ]


우선 shell을 닫고 quit()


django를 실행합니다.


python manage.py runserver

Validating models...

0 errors found
Django version 1.8.3, using settings 'tutorial.settings'
Development server is running at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

그뒤 아래 URL로 요청해보면.


http http://127.0.0.1:8000/snippets/

HTTP/1.1 200 OK
...
[
  {
    "id": 1,
    "title": "",
    "code": "foo = \"bar\"\n",
    "linenos": false,
    "language": "python",
    "style": "friendly"
  },
  {
    "id": 2,
    "title": "",
    "code": "print \"hello, world\"\n",
    "linenos": false,
    "language": "python",
    "style": "friendly"
  }
]

ID로도 조회가 가능합니다.~


http http://127.0.0.1:8000/snippets/2/

HTTP/1.1 200 OK
...
{
  "id": 2,
  "title": "",
  "code": "print \"hello, world\"\n",
  "linenos": false,
  "language": "python",
  "style": "friendly"
}

우선은 여기까지로 포스팅을 끝내고 2부를 계속 포스팅하도록 하겠습니다.

Posted by C마노
,