Skip to content
Aprende a hacer apps móviles con Ionic 2 

Django 1.7 – intro a Django REST Framework

Este POST es una nueva versión de la anterior Introducción a Django REST Framework, actualizada a 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 aber 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 ámbas 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

Una novedad de Django 1.7, es que ahora incorpora migraciones de forma automática ( para los que vienen de Django 1.6, se usa de forma similar a South). Así que si tocamos el modelo survey despues de crear la base de datos, tendremos que generar las migraciones entre modelos, del siguiente modo:

(entornoSurvey)$ ./manage.py makemigrations
(entornoSurvey)$ ./manage.py migrate

En cualquier caso, finalmente ejecutamos el servidor con:

(entornoSurvey)$ ./manage.py runserver

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

Si utilizais Postman REST Client, podéis importar los ejemplos para la petición GET y POST desde la siguiente colección:
https://www.getpostman.com/collections/2e03273d60e1d99dee77

Si testeamos un poco la API que acabamos de crear:

  • 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, many=True)
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 .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!

Published inDjango

5 Comments

  1. Francesc Francesc

    Por si le sirve a alguien, para que funcionara la creación de nuevas encuestas he tenido que cambiar la siguiente linea de survey_proj/api/views.py :

    serializer = SurveySerializer(survey, data=request.DATA)

    por

    serializer = SurveySerializer(survey, data=request.data)

    Gracias por el tutorial!

  2. Jose Jose

    Muy bueno 🙂

Deja un comentario