KS blog

killins.egloos.com

포토로그



django-rest : Serialization by KillinS

- 어찌보면 rest 아키텍처와 serialization은 직접적인 연관이 없을수도 있지만 어떤 웹 서비스에서든 기본이 되는 개념이고, django-rest 프레임워크에서 제공하는 기능이기 때문에 먼저 django-rest를 이용한 직렬화 기능을 익혀본다.

1. 프로젝트 및 앱 생성

  - 간단한 블로그 프로젝트를 만들어본다. 블로그 모델은 글쓴이, 내용, 분류로 구성될 것이다.
    프로젝트 이름은 resttest, 앱 이름은 blog로 한다.

  - 우선 장고 프로젝트와 앱을 생성한다.

      django-admin.py startproject resttest
      cd resttest
      python manage.py startapp blog

  - 프로젝트 세팅 파일(resttest/settings.py)을 설정한다. DB는 mysql을 사용한다고 가정하고, blog 앱과 rest 앱을 추가한다.

      DATABASES = {
           'default': {
                'ENGINE' : 'django.db.backends.mysql'
                 ....
      }
      ...
      INSTALLED_APPS = {
          ....
          'rest_framework',
          'blog',
      }


2. 모델 생성

  - blog 모델을 생성한다. blog는 작성된 날짜가 자동으로 저장되고, 글쓴이, 내용, 추가내용, 종류를 갖는다. 종류는 공지, 일반, 답글이 있다. 자세한 모델의 구조는 아래 소스파일을 참고.

      from django.db import models
      CONTENT_TYPE = (('NOTICE','NOTICE'),('NORMAL', 'NORMAL'), ('REPLY','REPLY'))
      class Blog(models.Model):
          created = models.DateTimeField(auto_now_add=True)
          title = models.CharField(max_length=50)
          content = models.TextField()
          ps = models.CharField(max_length=100, blank=True, default='')
          ctype = models.CharField(choices=CONTENT_TYPE, default='NORMAL', max_length=20)
          class Meta:
              ordering = ('created',)


  - 생성한  모델을 DB에 반영하여 이상이 없는지 확인한다.

      python manage.py syncdb


3. Serializer 클래스 생성


  - 이제 blog 인스턴스를 JSON과 같은 형식으로 직렬화할 serializer 클래스를 생성한다. django-rest에서는  Serializer 라는 클래스를 제공하고, 이 클래스를 상속받으면 blog 인스턴스를 직렬화하는 클래스를 생성할 수 있다.

  - 아래 코드는 정석적인 방법으로 Serializer를 생성하는 방법이다. (serializers.py)

      from django.forms import widgets
      from rest_framework import serializers
      from blog.models import Blog, CONTENT_TYPE
      class BlogSerializer(serializers.Serializer):
          pk = serializers.Field()
                       # 참고로 Field는 읽기 전용이다.
          title = serializers.CharField(max_length=50)
          content = serializers.CharField(widget=widgets.Textarea, max_length=100000)
          ps = serializers.CharField(require=False, widget=widgets.Textarea, max_length=100000)
          ctype = serializers.ChoiceField(choices=CONTENT_TYPE, default='NORMAL')
      def restore_object(self, attrs, instance=None):
          """
          Create or update a new instance, given a dictionary of deserialized field values.
          Note that if we don't define this method, then deserializing data will simply return a dictionary of items.
          """
          if instance:
              # Update existing instance
              instance.title = attrs.get('title', instance.title)
              instance.content = attrs.get('content', instance.content)
              instance.ps = attrs.get('ps', instance.ps)
              instance.ctype = attrs.get('ctype', instance.ctype)
              return instance
          # Create new instance
          return Blog(**attrs)


  - 모델이 간단할때는 위와 같은 코드 작성에 별 문제가 없지만, 복잡할 경우 문제가 달라진다. 다행스럽게도 django-rest는 훨씬 간단한 Serializer를 제공하는데, ModelSerializer가 바로 그것이다. 아래 소스코드와 같이 작성하면 된다.

       from rest_framework import serializers
       from blog.models import Blog
       class BlogSerializer(serializers.ModelSerializer):
           class Meta:
               model = Blog
               fields = ('id', 'title', 'content', 'ps', 'ctype')

4. 테스트 데이터 생성 및 모델/직렬화 검증


  - serializer 까지 완료되었으면 이제 테스트 데이터를 생성하고, serializer가 정상적으로 동작하는지 한번 확인해본다.

  - 우선 테스트 데이터 생성

      python manage.py shell

      >>> from blog.models import Blog
      >>> from blog.serializers import BlogSerializer
      >>> from rest_framework.renderers import JSONRenderer
      >>> from rest_framework.parsers import JSONParser
      >>> blog = Blog(title='news', content='north korean nuclear bomb test')
      >>> blog.save()
      >>> blog = Blog(title='news2', content='japan earth quake')
      >>> blog.save()


  - 이제 news2 객체를 직렬화 시키고, 이것을 JSON으로 표현되는지 확인한다.

      >>> serializer = BlogSerializer(blog)
      >>> serializer.data
      {'id': 2L, 'title': u'news2', 'content': u'japan earth quake', 'ps': u'', 'ctype': u'NORMAL'}
      >>> content=JSONRenderer().render(serializer.data)
      >>> content
      '{"id": 2, "title": "news2", "content": "japan earth quake", "ps": "", "ctype": "NORMAL"}'

  - 이번에는 반대로 JSON으로 표현된 객체를 deserialize 시켜본다.

      >>> import StringIO
      >>> stream = StringIO.StringIO(content)
      >>> data = JSONParser().parse(stream)
      >>> serializer = BlogSerializer(data=data)
      >>> serializer.object
      <Blog: Blog object>

  - 참고로 특정 인스턴스가 아닌 query set을 한꺼번에 직렬화/반직렬화 시킬수도 있다. many=True 인자를 주면 된다.

      serializer = BlogSerializer(Blog.objects.all(), many=True)

5. 뷰 작성

  - 모델과 직렬화 클래스 작성이 끝났으므로 이제 뷰를 작성한다. 크게 두개의 URL로 나눠서, 첫번째 URL은 GET일 경우 전체 blog를 보내주고, POST면 신규 blog를 등록한다. 두번째 URL은 GET이면 PK에 의해 특정 blog만 보내주고, PUT이면 해당 blog를 업데이트하며, DELETE이면 해당 blog를 삭제한다. 주고받는 데이터는 모두 직렬화된 JSON 구문이다.

  - 우선 views.py 파일에 응답으로 보낼 JSONResponse 클래스를 정의한다. 넘겨야할 데이터를 JSON 형태로 렌더링하는것만 다르고 나머지는 HttpResponse와 동일하다.

      from django.http import HttpResponse
      from django.views.decorators.csrf import csrf_exempt
      from rest_framework.renderers import JSONRenderer
      from rest_framework.parsers import JSONParser
      from blog.models import Blog
      from blog.serializers import BlogSerializer
      class JSONResponse(HttpResponse):
          """
          HTTP response renders contents to JSON
          """
          def __init__(self, data, **kwargs):
              content = JSONRenderer().render(data)
              kwargs['content_type'] = 'application/json'
              super(JSONResponse, self).__init__(content, **kwargs)


  - 첫번째 URL을 처리할 view 함수를 생성한다. GET이면 전체 blog를, POST면 신규 blog 등록을 수행한다.

     @csrf_exempt
     def blog_list(request):
         """
         list all blogs or create new one
         """
         if request.method == 'GET':
             blogs = Blog.objects.all()
             serializer = BlogSerializer(blogs, many=True)
             return JSONResponse(serializer.data)
         elif request.method == 'POST':
             data = JSONParser().parse(request)
             serializer = BlogSerializer(data=data)
             if serializer.is_valid():
                 serializer.save()
                 return JSONResponse(serializer.data, status=201)
             else:
                 return JSONResponse(serializer.errors, status=400)


  - 두번째 URL을 처리할 view 함수를 정의한다. blog 하나를 돌려주거나, update/delete를 수행한다.

      @csrf_exempt
      def blog_detail(request, pk):
          """
          retrieve, update, delete a blog
          """
          try:
              blog = Blog.objects.get(pk=pk)
          except Blogs.DoesNotExist:
              return HttpResponse(status=404)
          if request.method == 'GET':
              serializer = BlogSerializer(blog)
              return JSONResponse(serializer.data)
          elif request.method == 'PUT':
              data = JSONParser().parse(request)
              serializer = BlogSerializer(blog, data=data)
              if serializer.is_valid():
                  serializer.save()
                  return JSONResponse(serializer.data)
              else:
                  return JSONResponse(serializer.errors, status=400)
          elif request.method == 'DELETE':
              blog.delete()
              return HttpResponse(status=204)


  * @csrf_exempt decorator는 Cross Site Requset Forgery를 방지하기 위함이다.

  - 뷰 함수의 정의가 끝났으므로 이제 URL을 view에 연결시키면 된다. /blogs 는 blogs_list 뷰 함수와 연결시키고, /blogs/숫자 는 blogs_detail 뷰 함수와 연결시킨다. 프로젝트의 urls.py 파일을 아래와 같이 수정한다.

      from django.conf.urls import patterns, include, url
      urlpatterns = patterns('blog.views',
          url(r'^blogs/$', 'blog_list'),
          url(r'^blogs/(?P<pk>[0-9]+)/$','blog_detail'),
      )

6. 웹서버 기동 및 테스트

  - 이제 웹서버를 기동하고 테스트해본다. 아파치에 wsgi를 물려서 기동하거나(django 카테고리내 글 참조) 장고 자체 테스트용 웹서버를 기동한다.

  - 웹 브라우저로 접속해보거나, 아래와 같이 curl을 이용하여 테스트해본다.

      curl http://0.0.0.0/blogs/

      curl http://0.0.0.0/blogs/2



* 참고 : http://django-rest-framework.org/tutorial/1-serialization.html




덧글

  • 고별 2013/11/18 18:30 #

    Django REST framework 관련 튜토리얼 글을 너무 잘 보고 있습니다.
    덕분에 국문으로 잘 이해할 수 있었습니다. 감사합니다.
  • KillinS 2014/03/03 12:02 #

    사실 혼자 공부한걸 잊어버리지 않고 나중에 다시 보려고 쓴 글들이라 엉망인데
    그래도 도움이 되셨다니 정말 제가 감사하네요 ㅎ
※ 로그인 사용자만 덧글을 남길 수 있습니다.