태터데스크 관리자

도움말
닫기
적용하기   첫페이지 만들기

태터데스크 메시지

저장하였습니다.

'Django'에 해당되는 글 3건

  1. 2012.08.02 도깨비: 요청/응답 json 포맷 사용 + mongodb 저장소 붙이기 (6)
  2. 2012.07.12 도깨비(tokebi) 더미 API (5)
  3. 2009.11.19 django 삽질 (3)

도깨비: 요청/응답 json 포맷 사용 + mongodb 저장소 붙이기

Daily life/Hard study 2012. 8. 2. 22:20

지난번에 작업 - http://blog.dgoon.net/449 - 하다 말았던 부분을 이어서 한다. 도깨비에 추가로 할 작업은 다음 두가지이다.

1. 일단 요청/응답 포맷을 json으로 바꾼다.
2. persistent layer를 붙인다(mongodb?)

-----
query string 이나 parameter 는 버린다.
POST 로 요청을 받고 -> Content-type 이 application/json 인지 확인하고 -> body 를 읽어서 json.loads 를 한다.
요청이 가져야 하는 값들은 여기서 나오는 json 에 모두 포함되어 있어야 한다. 몇 가지 사소한 것들을 적어둔다.

a. 요청 타입(POST/GET)은 지난번에 @require_http_methods("POST") 로 강제했다
b. content-type은 request.META.get('CONTENT-TYPE') 에 들어있다.
c. body는 request.read() 를 하면 나온다

필수 데이터가 들어있는지 확인하는 코드(check_required)와 content-type을 확인하는 코드(check-content-type)을 함수로 분리하면 더미 로직은 get/put 둘 모두,

        check_content_type(request, "application/json")
        body = request.read()
        req_dict = simplejson.loads(body)
        check_required(GET_REQUIRED_FIELDS, req_dict.keys())
        print req_dict


이 정도로 간단해진다. 조건이 맞지 않는 경우 check_required, check_content_type 함수들은 직접 예외를 생성해서 던진다. 테스트 요청을 날리던 get.py, put.py 에서는 content-type 만 application/json 으로 바꾸고, urlencode 하던 부분을 simplejson.dumps 로만 바꾸면 된다.

{'app_key': 'deadbeef', 'condition': {}}
[02/Aug/2012 21:06:05] "POST /api/v1/get/ HTTP/1.1" 200 2
{'content': 'THIS_IS_CONTENT', 'tag_list': ['tag1', 'tag2', 'tag3'], 'meta': {}, 'app_key': 'deadbeef'}
[02/Aug/2012 21:06:16] "POST /api/v1/put/ HTTP/1.1" 200 2

이전보다 코드도 짧아지고, tag_list 도 리스트로 제대로 들어온다. 에잉 훨 낫네.

-----
persistent layer를 붙인다. 전에 튜토리얼을 따라가본 몽고디비를 쓰고 싶다. 그때는 c++ 클라이언트였는데, 파이썬이라고 뭐 딱히 다르진 않지 싶다. 더 쉽겠지...

http://www.mongodb.org/display/DOCS/Drivers -> http://www.mongodb.org/display/DOCS/Python+Language+Center

여기서 찾아보면 몇 가지 라이브러리가 있다. PyMongo 를 선택. 일단 언제나처럼 pip,easy_install,apt-get 순서로 패키지가 있나 뒤져본다.

sudo pip install pymongo

빙고. 파이썬 쉘 띄워서 import pymongo 해보니 뭔가 임포트 된다. 이걸 쓰면 되겠다.

http://api.mongodb.org/python/current/tutorial.html

여기 튜토리얼이니 잠깐 읽어보면 어렵지 않아요~ 지난번에 삽질하면서 katy 에 몽고디비 설치는 해 두었으니 그대로 활용하자. db=tokebi, collection=twit 으로 간다. store_mongodb.py 를 만들고, MongoStore 클래스에 몽고디비 접근 구현 코드를 둔다. 인터페이스는 mongo_store.get/put 이며 api_get, api_put 이 받은 요청을 거의 그대로 받는다.


api_get:
        app_key, condition = get_items(req_dict, GET_REQUIRED_FIELDS)             
        store = MongoStore('twit')
        retrieved = store.get(app_key, condition)

api_put:
        app_key, content, tag_list, meta = get_items(req_dict, PUT_REQUIRED_FIELDS)
        store = MongoStore('twit')
        ack = store.put(app_key, content, tag_list, meta)

api_get, api_put 에 각각 위의 코드가 추가되었다. 테스트 요청을 날려보면서 디버깅을 좀 해야 했는데, python get.py localhost 2223 > ~/public_html/get_error.html 등으로 저장한 뒤에 브라우저로 열어보면 좀 쉽다. 몽고디비에 쓰고 읽을 때 필요한 자료구조는 뭘까 하고 튜토리얼을 봤더니,

In PyMongo we use dictionaries to represent documents. ...

저장 단위가 파이썬 맵이므로 구현은 아주 간단하다. 심지어 문서를 꺼내오는 조건도 사전이므로,몽고디비 접근은 거의 자명하다.

#-*- encoding: utf-8 -*-from pymongo import Connection
CONN = Connection()

def doc2dict(d):
    ret = dict(d)
    ret['_id'] = str(ret['_id'])
    return ret

class MongoStore(object):
    def __init__(self, collection):
        self.collection = CONN.tokebi[collection]

    def get(self, app_key, condition):
        cond = condition.copy()
        cond['app_key'] = app_key
        return map(doc2dict, self.collection.find(condition))

    def put(self, app_key, content, tag_list, meta):
        doc = {
            'app_key': app_key,
            'content': content,
            'tag_list': tag_list,
            'meta': meta,
        }
        return self.collection.insert(doc)


api_get, api_put 에서 응답을 적절히 json 포맷으로 만들어 준다면 테스트 요청을 날리는 get.py 에서는 여러개의 문서 리스트를 응답으로 얻게 된다. 받은 결과를 화면에 찍어보면,

dgoon@katy:~/works/tokebi/sample_query$ python get.py localhost 2223
{'Content-type': 'application/json', 'Accept': 'text/plain'}
{'condition': {}, 'app_key': 'deadbeef'}
0
        content THIS_IS_CONTENT
        _id 501a74c272653e2217c9b255
        meta {}
        app_key deadbeef
        tag_list ['tag1', 'tag2', 'tag3']
1
        content THIS_IS_CONTENT
        _id 501a74d472653e222068f2b8
        meta {}
        app_key deadbeef
        tag_list ['tag1', 'tag2', 'tag3']
2
        content 두만강푸른물에
        _id 501a750372653e222068f2b9
        meta {}
        app_key deadbeef
        tag_list [u'\uac15\uc0b0\uc5d0', u'\ub178\ub798']
3
        content 티비유치원 하나둘셋
        _id 501a768072653e225e6cb3c1
        meta {}
        app_key deadbeef
        tag_list ['hana', 'dul', 'set']


이렇게 확인할 수 있다. 한글도 잘 들어가는 것을 확인할 수 있다. 2번 문서의 tag_list에 들어있는 것은,

>>> for k in [u'\uac15\uc0b0\uc5d0', u'\ub178\ub798']:
...     print k
...
강산에
노래

이렇게 유효한 한글이다. PyMongo 튜토리얼에서 설명되어 있는데 - BSON strings are UTF-8 encoded 라고 한다. Unicode문자열은 encode 되어 저장된다고. 그리고 simplejson 으로 직렬화 할때는 다시 유니코드가 되어서 응답을 받은 쪽에서는 한글이 모두 유니코드로 떨어지게 된다. 나중에 좀 헷갈릴 것 같다.


-> http://git.dgoon.net/?p=tokebi.git;a=commit;h=da49398646c0812a0d33d71cbec23e5052f5ca09


-----

추가적으로 해야 하는 작업은 ... 잊고 있었는데, 나 말고 다른 사람도 이걸 쓰게 하려면


1. user_id 같은걸 넣어야 하고,


최신 엔트리를 가져오려면,


2. created_at 이 필요하고,


사람을 가리거나 정렬이 필요하니


3. 적절한 인덱스 가 필요하다.


다음 작업은 이상 3개가 되겠다.

Trackbacks 0 : Comments 6
  1. Favicon of http://ub.cheapshoesel.com/ nike shoes 2013.04.08 05:44 Modify/Delete Reply

    우리가 어디에 있는가가 중요한 것이 아니라 어디로 가야 하느냐가 중요한것이다

  2. Favicon of http://ntu.contact-hotel.com/longchamp.php sac longchamp 2013.04.13 20:52 Modify/Delete Reply

    매우 지원, 아주 좋아, http://ntu.4dmv.com/montblanc.php mont blanc pens.

  3. Favicon of http://www.todsoutletonlinexx.com/ tods shoes 2013.04.23 18:22 Modify/Delete Reply

    당신은 내가사랑할 만한 사람이 아니예요,사랑하지 않으면 안될 사람이예요.

  4. Favicon of http://frk.hairstraightenernx.com ghd straightener 2013.04.29 03:30 Modify/Delete Reply

    좋으면 좋고 싫으면 싫은 거지, 뭐가 이렇게 어렵고 복잡하냐구

  5. Favicon of http://frk.shoesxstorex.com/ jordan 18 2013.04.29 08:16 Modify/Delete Reply

    좋으면 좋고 싫으면 싫은 거지, 뭐가 이렇게 어렵고 복잡하냐구

  6. Favicon of http://2962sacresgion511.com/ChicagoBlackhawksjersey.php Chicago Blackhawks Jersey 2013.07.15 18:36 Modify/Delete Reply

    태양이 바다에 미광을 비추면,나는 너를 생각한다.

Write a comment


도깨비(tokebi) 더미 API

Daily life/Hard study 2012. 7. 12. 22:19


목표: 아이폰에서 읽고 쓸 수 있는 임의의 저장소 인터페이스를(만) 만드는 것


1. Initial commit
  - put: (app_key, content, tag_list, meta_info)
  - get: (app_key, condition)

  일단 일반적인 모양만 정의하고 세세한 사용은 나중에 정의하기 위하여 메타정보, 조건 등을 dictionary 로 받는걸로 한다. POST 요청만 다루며 GET은 예외를 던져버리도록 하자. 일단 인터페이스를 만드는게 목표이므로 실제로 데이터를 저장하는 부분은 고려하지 않는다.


성능은 고려사항이 아니다. 아이폰 앱을 만들 때 붙여놓을 더미가 필요한 것이므로 django로 후다닥 작성한다. django 1.4 기준이다. 혹시 나중에 필요하게 되면 비동기로 다시 작성한다. 개인적으로 eventlet 에 관심이 ...

  app_key: fixed-length binary
  content: binary
  tag_list: list of strings
  meta_info: dictionary
  condition: dictionary

  URL:
    - /api/v1/put
    - /api/v1/get

  이 저장소는 만들어질 아이폰 앱과는 완전히 별개의 코드베이스이므로 독립적인 git 저장소를 생성한다. dgoon's key value storage -> dkv -> 도깨비 -> tokebi 로 한다. 난 부끄러움이 많으니까 github에 공개 저장소를 만들진 않는다. 하지만 비공개 저장소도 싫으니까 개인서버에 조용히 판다. git.dgoon.net

django 프로젝트(tokebi)를 생성하고 기본 세팅. RDB는 사용하지 않을 예정이므로 DATABASES는 깔끔하게 날린다.
나중에 관리 페이지라던가 다른게 생길 수 있으니 api 라는 애플리케이션을 생성해서 거기에 코드를 넣는다.
위에서 정의한대로 필요한 콜은 put, get 두개이다. 이 각각을 받아줄 endpoint 를 설정한다. 정의되지 않은 URL에 대해서는 일단 404를 알아서 만들도록 둔다. secret key 따위 알게뭐야 흥.

----- api/views.py -----
from django.http import HttpResponse

def api_get(request):
    return HttpResponse('OK', status=200)

def api_put(request):
    return HttpResponse('OK', status=200)


----- tokebi/urls.py -----
...
from api.views import api_get, api_put

urlpatterns = patterns('',
    url(r'^api/v1/get/', api_get),
    url(r'^api/v1/put/', api_put),

…)


이렇게 하고 테스트 서버를 띄워서 접속하면 /api/v1/get, /api/v2/put 에 대해서 OK 를 얻을 수 있다. 일단 여기서 Initial commit 을 해 둔다.


2. Test request

필요한 정보가 모두 있는 경우 OK를, 부족한 경우 500 에러를 내도록 한다. 이게 되어야 개발에 사용할 더미로써 가치가 있다.
그러기 위해선 쿼리를 보낼 수 있어야 한다.

put.py, get.py 를 만든다.

  - put.py: 명령행에서 HOST PORT CONTENT TAG_LIST 를 입력받는다. 메타정보는 일단 {} 를 넣는다.
  - get.py: 명령행에서 HOST PORT 를 입력받는다. 조건에 {} 를 넣는다.

두 경우 모두 app_key 는 'deadbeef' 를 하드코딩한다. 요청은 파이썬으로 작성한다. httplib.HTTPConnection(http://docs.python.org/library/httplib.html#httpconnection-objects) 을 사용하면 되겠지.

----- put.py -----
import sys, httplib, urllib

APP_KEY = 'deadbeef'

if '__main__'==__name__:
    if len(sys.argv)<5:
        print('Usage: %s host port content tag_list' % sys.argv[0])
        sys.exit(0)

    host = sys.argv[1]
    port = int(sys.argv[2])
    content = sys.argv[3]
    tag_list = sys.argv[4:]

    conn = httplib.HTTPConnection(host, port)
    body = {
        'app_key': APP_KEY,
        'content': content,
        'tag_list': tag_list,
        'meta': {},
    }
    header = {
        'Content-type': 'application/x-www-form-urlencoded',
        'Accept': 'text/plain',
    }
    conn.request("POST", "/api/v1/put/", urllib.urlencode(body), header)

    res = conn.getresponse()
    res_body = res.read()
    assert(res.status==200 and res_body=='OK')


그런데 python put.py localhost 2223 HAHAHA tag1 tag2 tag3 를 실행해더니 assertion error가 떳다.

[12/Jul/2012 21:06:55] "POST /api/v1/put/ HTTP/1.1" 403 2282
[12/Jul/2012 21:07:07] "GET /api/v1/put/ HTTP/1.1" 200 2

403이 put.py 로 날린 요청, 200 이 브라우저에서 날린 요청이다. 500도 아니고 403 ?? put.py 에서 res_body 를 찍게 해 봤더니,
csrf관련된 내용이 보인다. 말인 즉슨, cross-site request forgery를 방지하기 위한 미들웨어가 있는데 그녀석을 위한 토큰이 포함되어야 한다나 어쩐다나.
나는 템플릿을 쓸 생각이 없고, 요청에 그런걸 만들어 보내는것도 귀찮으므로 csrf 관련 옵션을 비활성화 해버리겠다. settings.py 에서 csrf 로 검색하면 바로 나온다. MIDDLEWARE_CLASSES 에서 django.middleware.csrf.CsrfViewMiddleware 를 주석처리하고 나니 잘 된다.

api_put 을 고쳐서 들어온 body 를 확인하도록 하자. 콘솔에 그냥 print 를 해보면 볼 수 있다.

----- api_put @ views.py -----
def api_put(request):    method = request.META['REQUEST_METHOD']
    print method
    print request.GET
    print request.POST
    return HttpResponse('OK', status=200)

이렇게 하고 put.py 로 한번, 브라우저로 리프레시 한번 하면 콘솔에 아래와 같이 찍힌다.

POST
<QueryDict: {}>
<QueryDict: {u'content': [u'THIS IS CONTENT'], u'tag_list': [u"['tag1', 'tag2', 'tag3']"], u'meta': [u'{}'], u'app_key': [u'deadbeef']}>
[12/Jul/2012 21:27:37] "POST /api/v1/put/ HTTP/1.1" 200 2
GET
<QueryDict: {}>
<QueryDict: {}>
[12/Jul/2012 21:27:40] "GET /api/v1/put/ HTTP/1.1" 200 2

귀찮으니

  a. content, tag_list, meta, app_key 가 모두 있지 않은 경우
  b. method=POST 가 아닌 경우

에 모두 예외를 던져버리자. … 코드를 만들고 나서 생각해보니 method=POST 강제하는건 왠지 데코레이터 있을 것 같다. 검색 ㄱㄱ

https://docs.djangoproject.com/en/dev/topics/http/decorators/

빙고. 그러면 최종적으로 api_put 의 모양은 아래와 같게 된다.

----- api_put @ views.py -----
from django.views.decorators.http import require_http_methods
class MissingRequiredField(BaseException): pass                                    

PUT_REQUIRED_FIELDS = ['app_key', 'content', 'tag_list', 'meta']

@require_http_methods(["POST"])
def api_put(request):                                                             
    try:                                                                          
        params = request.POST
            if (set(PUT_REQUIRED_FIELDS) - set(params.keys())):
                raise MissingRequiredField('MissingRequiredField')                                                                                                        
        content = params['content']                                               
        app_key = params['app_key']                                               
        tag_list = params['tag_list']                                             
        meta = params['meta']                                                     
                                                                                  
        print(app_key, content, tag_list, meta)                                   
                                                                                  
        return HttpResponse('OK', status=200)                                     
    except BaseException, msg:                                                    
        print('Exception raised: %s' % str(msg))                                  
        return HttpResponse(str(msg), status=500)          

그런데, 콘솔 출력을 보면

(u'deadbeef', u'THIS IS CONTENT', u"['tag1', 'tag2', 'tag3']", u'{}')
                       
이렇게 보이는게, tag_list, meta 가 직렬화된 상태로 넘어온 것 같다. … 음 이거 귀찮은데 …
그냥 요청을 json 으로 받을까. 일단 get.py 도 만들고 나서 고민하자. get.py 는 put.py 의 copy&paste로 뚝딱.
api_get 도 api_put 을 복사붙이기로 작성한다.

views.py: http://git.dgoon.net/?p=tokebi.git;a=blob;f=tokebi/api/views.py;h=09dd4ab585b3be18654fd51356f1ccd1d7f50fdc;hb=c4a018b479157c9e27a6a67c912079e3ebe1be6d
get.py: http://git.dgoon.net/?p=tokebi.git;a=blob;f=sample_query/get.py;h=4d10dabf8d1097464fad6d15bed582366480a2ca;hb=c4a018b479157c9e27a6a67c912079e3ebe1be6d
put.py: http://git.dgoon.net/?p=tokebi.git;a=blob;f=sample_query/put.py;h=1f2f5ef3aef9be70e6758094b3fcdd238ec33619;hb=c4a018b479157c9e27a6a67c912079e3ebe1be6d

오늘은 일단 여기까지. 추후에 요청을 json 으로 받도록 해보자. … 는 나중에 하고, 간단한 persistent layer를 붙여서 아이폰에서 put/get 동작시키는걸 먼저 해야겠다.


tags : Django, REST, tokebi, web
Trackbacks 0 : Comments 5
  1. haru 2012.07.13 11:23 Modify/Delete Reply

    취미 프로젝트 하시는 건가요?!

  2. Favicon of http://applefarm.pe.kr etermory 2012.07.24 11:23 Modify/Delete Reply

    나중에 낼름 해가야지. ㅎㅎ

  3. Favicon of http://ad.longchampoutk.com/ longchamp 2013.04.01 19:56 Modify/Delete Reply

    아름다운 여자가 해바라기하는 걸 좋아해요

  4. Favicon of http://da.christianlouboutinfrx.com/ louboutin pas cher 2013.04.02 23:47 Modify/Delete Reply

    아름다운 여자가 해바라기하는 걸 좋아해요

Write a comment


django 삽질

Tech 2009. 11. 19. 15:59
아무래도 한글 데이터를 처리하지 못해서 끙끙대다가 원인을 찾았다. DB(mysql)를 만들때 charset 지정이 엄한걸로(latin...) 되어 있었던 것.

create schema dgoon default charset utf8;

썩을.

'Tech' 카테고리의 다른 글

Links on xml and related techs  (1) 2009.12.09
SICP 근황  (8) 2009.12.01
django 삽질  (3) 2009.11.19
CUDA Driver/Toolkit/SDK 설치하기 (Ubuntu 9.04 Jaunty)  (2) 2009.09.17
Parameter vs Argument  (3) 2009.01.21
GEB: MU puzzle  (4) 2008.10.20
tags : Django, MySQL, utf8, 한글
Trackbacks 0 : Comments 3
  1. Favicon of http://shurain.egloos.com 슈레인 2009.11.20 03:43 Modify/Delete Reply

    이거 거의 모든 유저들이 한 번씩은 겪는 문제임

  2. Favicon of http://etnalry.pe.kr etnalry 2009.11.24 13:16 Modify/Delete Reply

    알고 보면 정말 단순한 문제가 많지. ㅋ

Write a comment