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:
- Obtener un listado de las encuestas
- Crear una encuesta
- Obtener una encuesta concreta
- Actualizar una encuesta
- 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
}
- debería devolvernos un JSON con la información introducida:
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 SurveySerializerclass 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!