REST APIs and Tastypie

An Introduction

By Rudy Mutter from Yeti

Follow Along


Game Plan

  • Vocabulary
  • The Basics
  • Customization
  • Under the Hood
  • Advanced Usage
  • Tool Kit


  • What's an API?
  • What's REST?
  • CRUD
  • Example Calls

What's an API?

Application Programming Interface

A protocol for specifying how software should interact with each other.

What's REST?

Representational State Transfer

Web API protocol for HTTP

URIs (Uniform Resource Identifiers) w/ HTTP GET, POST, PUT, DELETE represent resources and objects


Create Read Update Delete

POST -> Create
GET -> Read
PUT -> Update
DELETE -> Delete

Easiest explanation (but there's much more to REST)

Example Calls


    'title': 'My new blog post',
    'author': 'Rudy',
    'post': 'This is a new blog post...'

    'title': 'Changed blog post title',
    'author': 'Rudy',
    'post': 'This is a new blog post...'


The Basics

  • Install
  • Resources
  • Fields
  • Meta
  • Hooking it Up
  • Let's Try it Out


pip install django-tastypie




from tastypie.resources import ModelResource
from import BlogPost

class BlogPostResource(ModelResource):
    class Meta:
        queryset = BlogPost.objects.all()
        resource_name = "blog_post"


ToManyField and ToOneField to easily grab related data

class BlogCategoryResource(ModelResource):
    class Meta:
        queryset = BlogCategory.objects.all()
        resource_name = "blog_category"

class BlogPostResource(ModelResource):
    categories = ToManyField(BlogCategoryResource, 'categories', full=True)

    class Meta:
        queryset = BlogPost.objects.all()
        resource_name = "blog_post"

Hooking it Up

Register your API in, use version numbers to make changes to your API later.

v1_api = Api()

urlpatterns = patterns("",

    url(r'^api/', include(v1_api.urls)),

Let's Try it Out

Try It


Built in pagination for lists

meta: {
    limit: 20,
    next: null,
    offset: 0,
    previous: null,
    total_count: 0


  • Basic Options
  • Authentication
  • Authorization
  • Custom Authorization
  • Filtering
  • Caching
  • Format

Basic Options

class BlogPostResource(ModelResource):
    class Meta:
        allowed_methods = ['get', 'post'] # Limit possible actions
        fields = ['status'] # Explicitly which fields are included
        excludes = ['rating'] # Return all fields except these
        always_return_data = True # Return data when a POST is made
        limit = 20 # Number of objects per 'page'
        ordering = ['-publish_date'] # Default order by
Plenty of more options to be used - ResourceOptions


How does a user log in?

  • Authentication - Default, doesn't require login
  • BasicAuthentication - Basic HTTP Authentication, user:pass base64 encoded
  • ApiKeyAuthentication - Tastypie has built in API Key table for use

class BlogPostResource(ModelResource):
    class Meta:
        authentication = Authentication()


What does a user have access to?

  • Authorization - No permission checking
  • ReadOnlyAuthorization - Default, only permits GET calls
  • DjangoAuthorization - Uses built in Django permission system based on models

class BlogPostResource(ModelResource):
    class Meta:
        authorization = ReadOnlyAuthorization()

Custom Authorization

Allow a user to only read, create, or update objects associated with them.

class UserObjectsOnlyAuthorization(Authorization):
    def read_list(self, object_list, bundle):
        return object_list.filter(user=bundle.request.user)

    def read_detail(self, object_list, bundle):
        return bundle.obj.user == bundle.request.user

    def create_list(self, object_list, bundle):
        return object_list

    def create_detail(self, object_list, bundle):
        return bundle.obj.user == bundle.request.user

    def update_list(self, object_list, bundle):
        allowed = []

        # Since they may not all be saved, iterate over them.
        for obj in object_list:
            if obj.user == bundle.request.user:

        return allowed

    def update_detail(self, object_list, bundle):
        return bundle.obj.user == bundle.request.user

    def delete_list(self, object_list, bundle):
        raise Unauthorized("Sorry, no deletes.")

    def delete_detail(self, object_list, bundle):
        raise Unauthorized("Sorry, no deletes.")


Can list related resources or individual fields

class BlogPostResource(ModelResource):
    class Meta:
        filtering = {
            'categories': ALL_WITH_RELATIONS,
            'status': ['exact'],
            'content': ['contains', 'icontains']

Try It


Limited out of the box options - SimpleCache or NoCache

class BlogPostResource(ModelResource):
    class Meta:
        cache = SimpleCache(60) # Cache this response for 60 seconds


Tastypie comes with multiple serializer options - json, xml, yaml, html, plist, jsonp
When in doubt, just support json.

Under the Hood

  • What is a Bundle?
  • Hydrate & Dehydrate
  • From URI -> Object
  • Object -> Response

What is a Bundle?

A bundle is made up of data and an object

Data - Information about the request being made
Object - The instance we're trying to create, read, update, delete

Hydrate & Dehydrate

Hydrate - Serialized data, ex. from a POST, needs to be deserialized for a data model to use.

Dehydrate - A data model's values are prepared to be serialized, ex. for a JSON response

From URI -> Object(s)

  1. A request comes through the pipes - /api/v1/blog_post/?format=json
  2. Validation is run on your settings, such as Authorization, Authentication, allowed_methods
  3. _detail or _list decided upon multiple or single resources
  4. get_, post_, put_, delete_ decided upon HTTP method
  5. an object is then appropriately associated with the bundle and hydrated

Object(s) -> Response

  1. Filters are applied to the object(s)
  2. The bundle's object(s) are then possibly created, updated, or deleted
  3. Depending on the call, the list of objects are then sorted and paginated
  4. Object(s) are then dehydrated
  5. Respose created with serialized data

Advanced Usage

  • Non-Model Resource
  • Custom URL
  • Custom Filtering
  • Performance

Non-Model Resource

class Version(object):
    def __init__(self, identifier=None):
        self.identifier = identifier

class VersionResource(Resource):
    identifier = fields.CharField(attribute='identifier')

    class Meta:
        resource_name = 'version'
        allowed_methods = ['get']
        object_class = Version

    def detail_uri_kwargs(self, bundle_or_obj):
        kwargs = {}

        if isinstance(bundle_or_obj, Bundle):
            kwargs['pk'] = bundle_or_obj.obj.identifier
            kwargs['pk'] = bundle_or_obj['identifier']

        return kwargs

    def get_object_list(self, bundle, **kwargs):
        return [Version(identifier=settings.VERSION)]

    def obj_get_list(self, bundle, **kwargs):
        return self.get_object_list(bundle, **kwargs)
Try It

Custom URL

def prepend_urls(self):
    return [
        url(r"^(?P<resource_name>%s)/social_share/(?P<pk>\w[\w/-]*)/$" % self._meta.resource_name, self.wrap_view('social_share'), name="api_social_share"),

def social_share(self, request, **kwargs):
    self.method_check(request, allowed=['post'])

        report = self._meta.queryset._clone().get(pk=kwargs['pk'])
    except self._meta.object_class.DoesNotExist:
        return http.HttpNotFound()

    deserialized = self.deserialize(request, request.raw_post_data, format=request.META.get('CONTENT_TYPE', 'application/json'))
    bundle = self.build_bundle(obj=report, request=request, data=dict_strip_unicode_keys(deserialized))


    bundle = self.full_dehydrate(bundle)
    return self.create_response(request, bundle, response_class=http.HttpAccepted)

Custom Filtering

def apply_filters(self, request, applicable_filters):
    latitude = request.GET.get('latitude')
    if not latitude:
        raise BadRequest("Missing latitude parameter")

    longitude = request.GET.get('longitude')
    if not longitude:
        raise BadRequest("Missing longitude parameter")

    long_range = get_longitude_range(float(longitude), float(latitude), 30)
    lat_range = get_latitude_range(float(latitude), 30)
    applicable_filters['location__longitude__range'] = long_range
    applicable_filters['location__latitude__range'] = lat_range

    return super(Resource, self).apply_filters(request, applicable_filters)


class Meta:
    queryset = BlogPost.objects.prefetch_related('user', 'comments').all()

class Meta:
    queryset = BlogPost.objects.annotate(comment_count=Count('comments')).order_by('-comment_count')

Tool Kit

Offical Tastypie Docs

  • Postman
  • JSON View
  • Tastypie Swagger
  • iOS Restkit
  • Sentry


Chrome plugin that gives you a gui to make REST calls.
Saves your history and let's you name them in groups (i.e. by project)

Chrome Store


Chrome plugin that gives you nicely formatted json in your browser.

Chrome Store

Tastypie Swagger

Open source GUI viewer for your tastypie APIs.


Try It

iOS Restkit

Open source library on iOS which helps streamline REST calls. Works well in conjunction with tastypie.



General Django error logging tool. Works great with tastypie as well.

Get Sentry

The End

Thanks for listening!

Rudy Mutter from Yeti