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:- 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 apiCon 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.pyAñ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(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/32435r'^surveys/$', ‘survey_list’, name=’survey_list’), url(r'^survey/(?P<pk>[0-9]+)$', ‘survey_details’, name=’survey_details’), )
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)Si miramos detalladamente los métodos, disponemos de dos opciones:@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 syncdbUna 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 migrateEn cualquier caso, finalmente ejecutamos el servidor con:
(entornoSurvey)$ ./manage.py runserverPodemos 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/2e03273d60e1d99dee77Si 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 }
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 SurveySerializerPara que nuestras URLS accedan a las clases correspondientes, nos faltará un pequeño detalle: modificar el archivo api/urls.pyclass 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)
from django.conf.urls import patterns, include, url from rest_framework.urlpatterns import format_suffix_patterns from . import views urlpatterns = patterns(‘api.views’, url(Si volvemos a repetir los pasos de Probar la API, todo debería funcionar igual de bien.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): ”’ 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): passComo 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!
¿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.
6 comentarios del blog anterior
Abel Vargas · 22 mar 2015
muy buen tutorial
Enrique Oriol · 23 mar 2015
Gracias!!
Saul Garcia · 29 abr 2016
muy bueno gracias
Francesc · 7 jun 2016
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!
Jose · 31 ene 2017
Muy bueno :)
ipanera · 3 feb 2022
Llevo un tiempo aprendiendo sobre Django y me ha resultado muy útil.
Está muy bien explicado.
Gracias