time

Использование REST в Django

django_restful.png Введение

REST (сокр. англ. Representational State Transfer, "передача состояния представления") — подход к архитектуре сетевых протоколов, обеспечивающих доступ к информационным ресурсам. Был описан и популяризован в 2000 году Ройем Филдингом (Roy Fielding), одним из создателей протокола HTTP. Самой известной системой, построенной в значительной степени по архитектуре REST, является современная Всемирная паутина. источник

Каждая сущность, с которой необходимо работать через REST, должна иметь уникальный url. Например, фильм "Неудержимые" идентифицируется по /film/1/. В REST каждая сущность называется ресурсом. Для манипуляций с ресурсом используются стандартные методы HTTP: GET, POST, PUT, DELETE, которые в REST называются интерфейсы:

GET /film/1/ - запрос фильма с id равным 1. В случае корректного запроса в ответ приходит 200/OK и запрашиваемые данные, в случае ошибки 400/Bad Request;

POST /film/1/ - изменение фильма, новые данные передаются в теле запроса. В случае запроса на изменение в ответ приходит 200/OK, в случаи успешного создания ресурса - 201/Created, в случае ошибки - 400/Bad Request;

DELETE /film/1/ - удаление фильма. В случае корректного запроса в ответ приходит 200/Accepted, если запрашиваемый ресурс отсутствует - 404/Not Found, если существует несколько фильмов с id=1 то вернется 409/Conflict;

Подробнее с REST можно познакомится по приведенным ссылкам:

Установка и использование

Из множества доступных реализаций REST в django я решил остановится на django-piston. Почему именно django-piston? На то есть две причины, во-первых, я встречал много лестных отзывов в западной блогосфере по-поводу piston, во-вторых, проект в котором надо было прикрутить REST писался на ExtJS (уже Sencha), а Matt Dorn описал у себя в блоге наглядный пример как объединить ExtJS и Django через REST. Сам пост и выступление Matt Dorn на одной из конференций можно найти ниже, в блоке Дополнительное чтиво.

В качестве примера напишем простую систему учета новых фильмов в кинотеатре. Начнем-с.

Устанавливаем последнюю версию django-piston

hg clone http://bitbucket.org/jespern/django-piston

В INSTALLED_APPS добавляем 'piston'.

В корне проекта создадим директорию api (не забываем создать файл init.py). В этой же директории создадим файл handlers.py, в котором описываются интерфейсы работы с ресурсом, т.е. все манипуляции (CRUD) с нашей моделью Фильмов. Минимальное содержимое файла:

from piston.handler import BaseHandler
from films.models import Film

class FilmHandler(BaseHandler):
    model = Film
    fields = ('id', 'title', 'genre', 'release')

Для начала работы этого хватает, все остальное добавит базовый класс BaseHandler.

В директории api создадим еще один файл - urls.py, который очень похож на стандартный, только обработчиком назначается не вид, хандлер, определенный выше.

from django.conf.urls.defaults import *
from piston.resource import Resource
from api.handlers import FilmHandler

film_resource = Resource(FilmHandler)

urlpatterns = patterns('',
   url(r'^film/(?P\d+)/$', film_resource),
   url(r'^films/$', film_resource),
)

В urls.py проекта добавим ссылку на urls.py из api:

urlpatterns = patterns('',
   ...
   (r'^api/', include('api.urls')),
)

Создадим приложение films с такой моделью:

# coding=utf-8
from django.db import models

class Film(models.Model):
    title = models.CharField('Название', max_length=200)
    genre = models.CharField('Жанр', max_length=100)
    release = models.DateField('Дата релиза')

    def __unicode__(self):
        return self.title

Добавим новое приложение в INSTALLED_APPS и синхронизируем БД.

Добавим из консоли два тестовых фильма:

curl -i -X POST -d "title=The%20Expendables&genre=action&release=2010-01-28" http://127.0.0.1:8000/api/films/

HTTP/1.0 200 OK
Date: Tue, 07 Sep 2010 21:55:24 GMT
Server: WSGIServer/0.1 Python/2.6.5
Vary: Authorization
Content-Type: application/json; charset=utf-8

{
    "genre": "action", 
    "release": "2010-01-28", 
    "id": 1,
    "title": "The Expendables"
}

curl -i -X POST -d "title=The%20Karate%20Kid&genre=action&release=2010-01-28" http://127.0.0.1:8000/api/films/

HTTP/1.0 200 OK
Date: Tue, 07 Sep 2010 21:57:32 GMT
Server: WSGIServer/0.1 Python/2.6.5
Vary: Authorization
Content-Type: application/json; charset=utf-8

{
    "genre": "action", 
    "release": "2010-01-28", 
    "id": 2,
    "title": "The Karate Kid"
}

Все 200/OK, теперь запросим список фильмов:

curl -i -X GET http://127.0.0.1:8000/api/films/

HTTP/1.0 200 OK
Date: Tue, 07 Sep 2010 21:59:44 GMT
Server: WSGIServer/0.1 Python/2.6.5
Vary: Authorization
Content-Type: application/json; charset=utf-8

[
    {
        "genre": "action", 
        "release": "2010-01-28", 
        "id": 1,
        "title": "The Expendables"
    }, 
    {
        "genre": "action", 
        "release": "2010-01-28", 
        "id": 2,
        "title": "The Karate Kid"
    }
]

Удалим фильм с id = 2

curl -i -X DELETE http://127.0.0.1:8000/api/film/2/

HTTP/1.0 204 NO CONTENT
Date: Tue, 07 Sep 2010 22:05:55 GMT
Server: WSGIServer/0.1 Python/2.6.5
Vary: Authorization
Content-Length: 0
Content-Type: text/plain

Как видите все операции по отображению, добавлению, удалению piston взял на себя. Но piston позволяет переопределить любой метод из CRUD. Для примера переопределим метод create (в файле handlers.py) и добавим возможность сохранения ссылки на imdb при создании фильма. Я использовал фейковую функцию, которая возвращает один и тот же url.

from piston.handler import BaseHandler
from films.models import Film

def get_imdb(title):
    return 'http://www.imdb.com/title/tt1320253/'

class FilmHandler(BaseHandler):
    model = Film
    fields = ('id', 'title', 'imdb', 'genre', 'release')

    def create(self, request, *args, **kwargs):
        if not self.has_model():
            return rc.NOT_IMPLEMENTED

        attrs = self.flatten_dict(request.data)

        try:
            inst = self.queryset(request).get(**attrs)
            return rc.DUPLICATE_ENTRY
        except self.model.DoesNotExist:
            inst = self.model(**attrs)
            inst.imdb = get_imdb(inst.title)
            inst.save()
            return inst
        except self.model.MultipleObjectsReturned:
            return rc.DUPLICATE_ENTRY

Еще один пример работы через браузер с использованием jquery. Файл шаблона можете скачать тут, не забудьте заменить расширение на .html. В url.py проекта добавить такую строчку:

(r'^$', 'django.views.generic.simple.direct_to_template', {'template':'django_piston.html'}),

Скриншот полученной страницы:

django_piston_sample.png

На этом возможности django-piston не ограничиваются:

Накануне запуска поста обнаружил (в твитере у Александра Соловъёва) хороший инструмент для отладки REST API - htty

Новые подходы

Со времени первой публикации прошло уже много времени и появились новые инструменты, среди них:

Дополнительное чтиво

blog comments powered by Disqus