Thursday, May 2, 2013

Использование REST в Django http://proft.me/2010/09/9/ispolzovanie-rest-v-django/

http://proft.me/2010/09/9/ispolzovanie-rest-v-django/

09/09
2010

Использование 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

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

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

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

No comments:

Post a Comment