Introducción a Django REST Framework

Este POST está diseñado para Django 1.6. Si quieres estar a la última, mira la introducción a Django REST Framework actualizada al nuevo Django 1.7

Django Rest Framework es una herramienta que nos va a facilitar el desarrollo de APIs para nuestra web.
Esto nos permitirá, acceder/modificar/eliminar datos del servidor desde una aplicación móvil, por ejemplo.

Descripción del Tutorial

Para mostrar los principios básicos de Django REST Framework, vamos a crear un pequeño proyecto en el que tenemos un modelo encuesta (survey), y del que queremos proporcionar una API para:

  1. Obtener un listado de las encuestas
  2. Crear una encuesta
  3. Obtener una encuesta concreta
  4. Actualizar una encuesta
  5. Eliminar una encuesta

Creamos nuestro proyecto

A estas alturas ya deberíamos estar familiarizados con las herramientas virtualenvwrapper, pip y demás que se utilizarán a continuación. De lo contrario, puedes visitar creando un proyecto con Django y virtualenv y VirtualenvWrapper.
Creamos el entorno, proyecto y applicación survey donde tendremos nuestro modelo de datos:

miusuario$ mkvirtualenv entornoSurvey
(entornoSurvey)$ pip install django
(entornoSurvey)$ django-admin.py startproject survey_proj
(entornoSurvey)$ cd survey_proj
(entornoSurvey)$ chmod u+x manage.py
(entornoSurvey)$ ./manage.py startapp survey

Descargamos Django REST Framework y creamos un proyecto para la API

(entornoSurvey)$ pip install djangorestframework
(entornoSurvey)$ ./manage.py startapp api

Con esto, deberíamos tener una estructura de directorios como la siguiente:

survey_proj
    /manage.py
    /survey_proj
        /__init__.py
        /urls.py
        /wsgi.py
        /settings.py
    /survey
        /__init__.py
        /admin.py
        /models.py
        /tests.py
        /viewsls.py
    /api
        /__init__.py
        /admin.py
        /models.py
        /tests.py
        /viewsls.py

Añadir las apps, así como django REST framework al fichero settings.py

INSTALLED_APPS = (
    ‘django.contrib.admin’,
    ‘django.contrib.auth’,
    ‘django.contrib.contenttypes’,
    ‘django.contrib.sessions’,
    ‘django.contrib.messages’,
    ‘django.contrib.staticfiles’,
    ‘rest_framework’,
    ‘survey’,
    ‘api’,
)

Modelo de datos de nuestra encuesta

Para poder mostrar datos en nuestra API, primero tendremos que ver que datos queremos mostrar. Cogemos el fichero survey/models.py y lo dejamos así:

from django.db import models
class Survey(models.Model):
    owner = models.CharField(max_length=100)
    title = models.CharField(max_length=50)
    question = models.CharField(max_length=300)
    active = models.BooleanField(default=True)
    created = models.DateTimeField(auto_now_add=True, auto_now=False)
    updated = models.DateTimeField(auto_now_add=False, auto_now=True)

Es decir, nuestra encuesta tendra un propietario (para simplificar las cosas durante el ejemplo este campo será un simple CharField), un título, una pregunta, un booleano que nos dirá si está activo o no, y la fecha de creación y actualización.
Ahora, démosle acceso a la API a nuestra encuesta.

Creando nuestra API REST

Definiendo las URLs

En primer lugar deberemos definir a través de qué urls queremos acceder a nuestra API, por lo que abrimos el archivo survey_proj/survey_proj/urls.py, y lo modificamos:

from django.conf.urls import patterns, include, url
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns(”,
    url(r’^api/’, include(‘api.urls’)),
    url(r’^admin/’, include(admin.site.urls)),
)

Ahora creamos un nuevo archivo urls.py para nuestra API, en survey_proj/api/urls.py y creamos la URLs que usaremos:

from django.conf.urls import patterns, include, url
urlpatterns = patterns(‘api.views’,
    url(r’^surveys/$’, ‘survey_list’, name=’survey_list’),
    url(r’^survey/(?P< pk>[0-9]+)$’, ‘survey_details’, name=’survey_details’),
)

Con esto, estamos esperando poder acceder a urls del estilo:
http://127.0.0.1:8000/api/surveys/
http://127.0.0.1:8000/api/survey/1
http://127.0.0.1:8000/api/survey/32435

Creando un Serializer para nuestro modelo

Los serializers son clases que nos permiten transformar datos de formatos más propios de Django como objetos que extienden de Model o querysets, en formatos más propios del entorno web como puedan ser JSON y XML, y nos permiten hacerlo en ambas direcciones.
En cuanto a la forma de funcionar, tenemos que pensar que los serializers funcionan de un modo muy similar a los Form de Django, pero para nuestra API. De este modo, igual que Django nos proporciona Form y ModelForm, Django REST Framework nos proporciona Serializer y ModelSerializer.
La documentación de Django REST Framework es muy completa, y si os interesa entrar al detalle con los Serializers, es el lugar adecuado. Nosotros, vamos directos al ModelSerializer, que nos ahorrará muchas líneas de código.
Creamos el archivo survey_proj/api/serializers.py y lo rellenamos con:

from rest_framework import serializers
from survey.models import Survey
class SurveySerializer(serializers.ModelSerializer):
    class Meta:
        model = Survey
        fields = (‘title’, ‘question’,’owner’, ‘active’)

Lo que estamos creando es una herramienta que nos permite pasar los campos title, question, owner y active de nuestro modelo Survey a JSON (y otros formatos) y viceversa.

Opción 1: Vistas basadas en funciones

Las vistas basadas en funciones utilizan el decorador @api_view como veremos a continuación.
Abrimos el archivo survey_proj/api/views.py y definimos 2 funciones, una para cada una de las urls que hemos definido en nuestro módulo url (survey_list y survey_details).

from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from survey.models import Survey
from .serializers import SurveySerializer
@api_view([‘GET’, ‘POST’])
def survey_list(request):
    ”’
    List all surveys, or create a new survey
    ”’
    if request.method == ‘GET’:
        survey = Survey.objects.all()
        serializer = SurveySerializer(survey, many=True)
        return Response(serializer.data)
    elif request.method == ‘POST’:
        serializer = SurveySerializer(data=request.DATA)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        else:
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

@api_view([‘GET’,’PUT’, ‘DELETE’])
def survey_details(request, pk):
    ”’
    Get, update, or delete a specific survey
    ”’
    try:
        survey = Survey.objects.get(pk=pk)
    except Survey.DoesNotExist:
        return Response(status=status.HTTP_404_NOT_FOUND)
    if request.method == ‘GET’:
        serializer = SurveySerializer(survey)
        return Response(serializer.data)
    if request.method == ‘PUT’:
        serializer = SurveySerializer(survey, data=request.DATA)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        else:
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
    elif request.method == ‘DELETE’:
        survey.delete()
            return Response(status=status.HTTP_204_NO_CONTENT)

Si miramos detalladamente los métodos, disponemos de dos opciones:

  • survey_list:
    • solo nos ofrece opciones GET y POST. El GET nos devuelve todos los objetos Survey, mientras que el post nos permite crear un nuevo objeto Survey.
  • survey_details:
    • nos ofrece GET de un Survey concreto
    • nos ofrece PUT (actualización) de un Survey concreto
    • nos ofrece DELETE de un Survey concreto

Probando la API

Para probar la API solo nos queda sincronizar la base de datos y arrancar el servidor de demo:

(entornoSurvey)$ ./manage.py syncdb
(entornoSurvey)$ ./manage.py runserver

Podemos usar curl o herramientas gráficas como la estupenda extensión de Google Chrome Postman REST Client

  • Petición get de la lista: http://127.0.0.1:8000/api/surveys/
    • de entrada, debería devolvernos un array vacío: []
  • Post para crear una Survey: http://127.0.0.1:8000/api/surveys/, pasándole como parámetros del POST ‘owner’, ‘title’, ‘question’ y ‘active’
    • debería devolvernos un JSON con la información introducida:

      {
      “title”: “The first survey”,
      “question”: “Are you happy right now?”,
      “owner”: “Marcus”,
      “active”: true
      }

Si volvemos a hacer la petición get, nos devolverá el JSON correspondiente a la survey que acabamos de crear.
Podemos seguir haciendo pruebas, pero creo que la idea se entiende.

Opción 2: Vistas basadas en clases

Aún tenemos una forma algo más elegante de definir los métodos que se ejecutarán al acceder a las urls, y que nos permitirán una mayor reusabilidad de código, como podemos ver, más adelante, en la Opción 3: Vistas con clases genéricas y mixins.
Básicamente, en lugar de usar el decorador @api_view, podemos definir clases que derivan de la case APIView, en lugar de funciones. De ese modo, podremos definir métodos get, post, etc. para ejecutarse en función del tipo de llamada a nuestra URL.
Para nuestro ejemplo, la case view anterior quedaría de la siguiente forma:

from rest_framework import status
from rest_framework.response import Response
from rest_framework.views import APIView
from django.http import Http404
from survey.models import Survey
from .serializers import SurveySerializer

class SurveyList(APIView):
”’
List all surveys, or create a new survey
”’
    def get(self, request, format=None):
        survey = Survey.objects.all()
        serializer = SurveySerializer(survey)
        return Response(serializer.data)
    def post(self, request, format=None):
        serializer = SurveySerializer(data=request.DATA)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        else:
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

class SurveyDetails(APIView):
”’
Get, update, or delete a specific survey
”’
    def get_object(self, pk):
        try:
            return Survey.objects.get(pk=pk)
        except Survey.DoesNotExist:
            raise Http404
    def get(self, request, pk, format=None):
        survey = self.get_object(pk)
        serializer = SurveySerializer(survey)
        return Response(serializer.data)
    def put(self, request, pk, format=None):
        survey = self.get_object(pk)
        serializer = SurveySerializer(survey, data=request.DATA)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        else:
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
    def delete(self, request, pk, format=None):
        survey = self.get_object(pk)
        survey.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

Para que nuestras URLS accedan a las clases correspondientes, nos faltará un pequeño detalle: modificar el archivo api/urls.py

from django.conf.urls import patterns, include, url
from rest_framework.urlpatterns import format_suffix_patterns
from . import views
urlpatterns = patterns(‘api.views’,
    url(r’^surveys/$’, views.SurveyList.as_view(), name=’survey_list’),
    url(r’^survey/(?P< pk>[0-9]+)$’, views.SurveyDetails.as_view(), name=’survey_details’),
) urlpatterns = format_suffix_patterns(urlpatterns)

Si volvemos a repetir los pasos de Probar la API, todo debería funcionar igual de bien.

Detalle: Sufijos

Probablemente os hayais fijado en que en el archivo urls.py hemos añadido una línea:

urlpatterns = format_suffix_patterns(urlpatterns)

Esto viene relacionado con el argumento format=None que hemos pasado a nuestras vistas en la Opción 2 (también podríamos haberlo hecho en la Opción 1).
Añadiendo el suffix_pattern, y pasando el argumento format a las vistas, podemos especificar el tipo de resultado que queremos al llamar a nuestra api, añadiéndole un sufijo a la misma.
De este modo, ahora mismo para obtener la lista de surveys podemos obtener los resultados en JSON en las siguientes urls:
http://127.0.0.1:8000/api/surveys/
http://127.0.0.1:8000/api/surveys/.json
Pero además, de forma automática, podemos obtener un html explicativo de la API en:
http://127.0.0.1:8000/api/surveys/.api
Utilizando el argumento format podríamos diferenciar el resultado y entregarlo estructurado para otros tipos de formato como XML o YAML.

Opción 3: Vistas con clases genéricas y mixins

Una vez hemos entendido como funcionan las vistas basadas en clases, podemos intuir que gran parte del código se repite.
Las operaciones create/retrieve/update/delete que hemos estado usando son muy comunes en cualquier vista que creemos para una API de acceso a modelos. Estos comportamientos comunes han sido implementados a través de mixins.

Usando mixins

Podríamos simplificar nuestra clase SurveyList (no lo hagáis todavía, aún la simplificaremos más, más adelante) de la siguiente forma:

from rest_framework import status, mixins, generics
from rest_framework.response import Response
from rest_framework.views import APIView
from django.http import Http404
from survey.models import Survey
from .serializers import SurveySerializer

class SurveyList(mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView):
”’
List all surveys, or create a new survey
”’
    queryset = Survey.objects.all()
    serializer_class = SurveySerializer
    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)
    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)

Podemos observar como ahora la vista incluye los miins ListModelMixin y CreateModelMixin, y extiende de la vista GenericAPIView. Necesitamos los datos miembro queryset y serializer_class, y para obtener la lista de objetos solo llamamos al método self.list, y de forma análoga, para crearlos llamamos a self.create

Usando clases genéricas basadas en vistas

Con el objetivo de reducir al máximo el código, y dado que hay mecanismos que se repiten habitualmente en las apis (vistas con get para obtener una lista y post para crear nuevos elementos, por ejemplo), Django proporciona clases genéricas de vistas.
Vamos a implementar ahora (ahora sí, manos a la obra) nuestro fichero api/views.py mediante clases genéricas:

from rest_framework.generics import ListCreateAPIView, RetrieveUpdateDestroyAPIView
from survey.models import Survey
from .permissions import IsOwnerOrReadOnly
from .serializers import SurveySerializer
class SurveyMixin(object):
    queryset = Survey.objects.all()
    serializer_class = SurveySerializer
class SurveyList(SurveyMixin, ListCreateAPIView):
    pass
class SurveyDetails(SurveyMixin, RetrieveUpdateDestroyAPIView):
    pass

Como podemos ver, creamos un mixin para incluir el queryset y serializer común para SurveyList y SurveyDetails, que extenderán de éste, así como de ListCreateAPIView y RetrieveUpdateDestroyAPIView respectivamente.
Las vistas genéricas ya usan internamente los mixins de Django REST Framework, por lo que no es necesario hacer absolutamente NADA más para que todo funcione.

Y eso ha sido todo por ahora, espero que haya resultado útil.
¡Saludos!