Typically when you build a Django site and you need to expose your application's data in the form of a RESTful API you would use something like the excellent Django REST Framework. However if you are hosting your site on Google App Engine there is an alternative called Google Cloud Endpoints that you should check out.
Cloud Endpoints is a robust solution built on Google architecture that powers various Google APIs. It takes care of a lot of the hard work for you such as authentication via OAuth 2 and JSON templating and provides an explorer you can use to test your API in the browser.
An Endpoints API is a remote procedure call (RPC) service that provides remote methods accessible to external clients. Each Endpoints API consists of an RPC service class that subclasses the ProtoRPC remote.Service class, and one or more methods. When you define a method, you must also define message classes for the requests coming into that method and the responses returned by it. A message class performs a mapping function so the incoming data can be extracted and supplied to the service method properly, or supplied properly to the outgoing response - (http://goo.gl/PrLm89)
Suppose you have your site up and running on App Engine, the project has two
Django models called Book
and PublicationYear
and you
need to create an API endpoint to list all books published in a particular year.
Here is how you could go about it using Cloud Endpoints.
To get started create a new directory where your Django apps reside and call it
api
. In here create three files: messages.py
, services.py
and
books_api.py
. Finally create the handler for the API in your app.yaml
file
and ensure the DJANGO_SETTINGS_MODULE
environment variable is set.
- url: /_ah/spi/.*
script: api.books_api.application
env_variables:
DJANGO_SETTINGS_MODULE: 'settings'
In messages.py
create the ProtoRPC message classes. In this example a Book
class should be created (if listing of publication years was also required, a
PublicationYear
class would also be needed). Rule of thumb is one Django model
= one message class.
from protorpc import messages
class Book(messages.Message):
"""Book ProtoRPC message
Book fields needed to define the schema for methods.
"""
title = messages.StringField(1)
author = messages.StringField(2)
ebook_available = messages.BooleanField(3)
publication_year = messages.IntegerField(4)
#...
class BookCollection(messages.Message):
"""Collection of Books."""
books = messages.MessageField(Book, 1, repeated=True)
year = messages.IntegerField(2)
In books_api.py
create each endpoint. Below the book listing view is provided.
"""Books API implemented using Google Cloud Endpoints."""
import settings
import endpoints
from protorpc import messages
from protorpc import message_types
from protorpc import remote
from books.models import Book
from api import services
from api.messages import BookCollection
package = 'Api'
@endpoints.api(name='books', version='v1',
allowed_client_ids=[settings.GOOGLE_OAUTH2_CLIENT_ID,
endpoints.API_EXPLORER_CLIENT_ID],
scopes=[endpoints.EMAIL_SCOPE, ])
class BooksApi(remote.Service):
"""Books API v1."""
PUBLICATION_YEAR_RESOURCE = endpoints.ResourceContainer(
message_types.VoidMessage,
year=messages.IntegerField(1, variant=messages.Variant.INT32))
@endpoints.method(PUBLICATION_YEAR_RESOURCE, BookCollection,
path='list', http_method='GET',
name='books.list')
def book_list(self, request):
"""List view endpoint."""
if hasattr(request, 'year'):
book_list = Book.objects.filter(
publication_year__year=request.year)
else:
book_list = Book.objects.all()
books = services.ApiUtils.serialize_books(book_list)
return BookCollection(books=books)
application = endpoints.api_server([BooksApi], restricted=False)
The endpoints
and protorpc
packages are provided by the GAE Python SDK.
Authentication is required to access this endpoint, shown by the inclusion of
the allowed_client_ids
param. Make sure your OAuth settings are in your
project settings.py
file for this to work. endpoints.API_EXPLORER_CLIENT_ID
can be included as an option if you wish to use the API explorer at
http://yourappname.appspot.com/_ah/api/explorer
.
The resource container PUBLICATION_YEAR_RESOURCE
passed to the endpoint allows
for the publication year to be provided as part of the request querystring, for
example /_ah/api/books/v1/list?year=2005
will return all books published in
2005.
In services.py
create any additional utility classes required. In the code
snippet above ApiUtils.serialize_books()
is referenced.
from django.forms.models import model_to_dict
from api.messages import Book
class ApiUtils(object):
"""Utility API functions."""
@staticmethod
def serialize_books(books):
"""Function to serialize a queryset of Book models.
Args:
books: a queryset
Returns:
A list of Book ProtoRPC messages
"""
items = []
for book in books:
item = model_to_dict(book, fields=[
'title', 'author', 'ebook_available'])
item['publication_year'] = book.publication_year.year
items.append(Book(**item))
return items
In ApiUtils you could also add some authorization functions if required, i.e.
does the authenticated user have an @yourdomain.com
email address or are they
in an approved list of users. Example:
user = endpoints.get_current_user()
if not user.email().endswith('@yourdomain.com'):
raise endpoints.UnauthorizedException('Unauthorized user.')
That's all that is required to get an API up and running using your existing Django models.
You could improve this solution by using the Django paginator
(django.core.paginator.Paginator
) rather than returning all the books in one
request. You would need to create a PAGINATION_TOKEN_RESOURCE
and pass this
into the endpoint so the user can provide a page number / token query param for
each request.