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:- 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 djangorestframeworkCon esto, deberíamos tener una estructura de directorios como la siguiente:
(entornoSurvey)$ ./manage.py startapp api
survey_projAñadir las apps, así como django REST framework al fichero settings.py
/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
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 modelsEs 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.
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)
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, urlAhora creamos un nuevo archivo urls.py para nuestra API, en survey_proj/api/urls.py y creamos la URLs que usaremos:
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns(”,
url(r’^api/’, include(‘api.urls’)),
url(r’^admin/’, include(admin.site.urls)),
)
from django.conf.urls import patterns, include, urlCon esto, estamos esperando poder acceder a urls del estilo:
urlpatterns = patterns(‘api.views’,
url(r’^surveys/$’, ‘survey_list’, name=’survey_list’),
url(r’^survey/(?P< pk>[0-9]+)$’, ‘survey_details’, name=’survey_details’),
)
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 serializersLo 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.
from survey.models import Survey
class SurveySerializer(serializers.ModelSerializer):
class Meta:
model = Survey
fields = (‘title’, ‘question’,’owner’, ‘active’)
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 statusSi miramos detalladamente los métodos, disponemos de dos opciones:
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)
- 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 syncdbPodemos usar curl o herramientas gráficas como la estupenda extensión de Google Chrome Postman REST Client
(entornoSurvey)$ ./manage.py runserver
- 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:
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 statusPara que nuestras URLS accedan a las clases correspondientes, nos faltará un pequeño detalle: modificar el archivo api/urls.py
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)
from django.conf.urls import patterns, include, urlSi volvemos a repetir los pasos de Probar la API, todo debería funcionar igual de bien.
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)
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):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
”’
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)
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, RetrieveUpdateDestroyAPIViewComo 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.
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
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!
¿Te ha gustado este artículo? No te cortes, déjame un comentario y ayúdame a compartirlo 😉
Comentarios
Para comentar necesitas una cuenta de GitHub.
5 comentarios del blog anterior
David Buitrago Arenas · 29 nov 2014
la opción 1 no funciona
Enrique Oriol · 30 nov 2014
¿Qué error obtienes?
debian789 · 10 ene 2015
En la opción 1 cuando se valida si es por 'GET' en la parte que se implementa SurveySerializer es necesario colocar many=True, quedando así; serializer = SurveySerializer(survey, many=True), esto permite que se itere sobre mas de un elemento del modelo retornando el Json en la vista, probado con la versión de Django== 1.7.2 y djangorestframework==3.0.3
Enrique Oriol · 28 ene 2015
Gracias debian789, tienes razón. He actualizado el post resolviendo un par de errores, y de paso, he creado una nueva versión, usando Django 1.7, con algún cambio. La puedes encontrar aquí: http://blog.enriqueoriol.com/2015/01/django-1.7-intro-django-rest-framework.htm
Juan Camilo Martinez Morales · 13 nov 2018
Muy buen post, muchas gracias! La documentación oficial del framework es pesima.