Jump to >

Writing Web API Resources

Overview

WebAPIResource is used to write REST API resources. A resource lives at a specific, stable URL, can represent either an object or a list of objects, and can respond to various HTTP methods (GET, POST, PUT, DELETE).

Subclasses of WebAPIResource are expected to override functions and variables in order to provide specific functionality, such as modifying or creating new objects.

This guide will go over the capabilities and responsibilities of resource subclasses.

Representing Models

Most resources will WebAPIResource.model set to a Model subclass, and WebAPIResource.fields set to a dictionary defining the fields to return in the resource payloads.

Each resource will also include a link dictionary that maps a key (resource name or action) to a dictionary containing the URL (href) and the HTTP method that’s to be used for that URL (method). This will include a special self key that links to that resource’s actual location.

An example of this might be:

'links': {
    'self': {
        'method': 'GET',
        'href': '/path/to/this/resource/'
    },
    'update': {
        'method': 'PUT',
        'href': '/path/to/this/resource/'
    }
}

Resources associated with a model may want to override WebAPIResource.get_queryset() to return a queryset with a more specific query.

By default, an individual object’s key name in the resulting payloads will be set to the lowercase class name of the object, and the plural version used for lists will be the same but with “s” appended to it. This can be overridden by setting WebAPIResource.name and WebAPIResource.name_plural.

Non-Database Models

Resources are not always backed by a database model. It’s often useful to work with lists of objects or data computed within the request.

In these cases, most resources will still want to set WebAPIResource.model to some sort of class and provide a WebAPIResource.fields dictionary. It’s expected that the fields will all exist as attributes on an instance of the model, or that a serializer function will exist for the field.

These resources will then to define a WebAPIResource.get_queryset() that returns a LocalDataQuerySet containing the list of items to return in the resource. This will allow standard resource functionality like pagination to work.

Matching Objects

Objects are generally queried by their numeric object ID and mapping that to the object’s pk attribute. For this to work, the WebAPIResource.uri_object_key attribute must be set to the name in the regex for the URL that will be captured and passed to the handlers for this resource. The WebAPIResource.uri_object_key_regex attribute can be overridden to specify the regex for matching this ID (useful for capturing names instead of numeric IDs) and WebAPIResource.model_object_key can be overridden to specify the model field that will be matched against.

Parents and URLs

Resources typically have a parent resource, of which the resource is a subclass. Resources will often list their children (by setting WebAPIResource.list_child_resources and WebAPIResource.item_child_resources in a subclass to lists of other WebAPIResource instances). This makes the entire tree navigatable. The URLs are built up automatically, so long as the result of get_url_patterns() from top-level resources are added to the Django url_patterns variables commonly found in urls.py.

Child objects should set the WebAPIResource.model_parent_key variable to the field name of the object’s parent in the resource hierarchy. This allows WebAPIResource to build a URL with the right values filled in in order to make a URL to this object.

If the parent is dynamic based on certain conditions, then the WebAPIResource.get_parent_object() function can be overridden instead.

Object Serialization

Objects are serialized through the WebAPIResource.serialize_object() function. This rarely needs to be overridden, but can be called from WebAPIEncoders in order to serialize the object. By default, this will loop through the WebAPIResource.fields variable and add each value to the resulting dictionary.

Values can be specially serialized by creating functions in the form of serialize_<fieldname>_field(). These functions take the object being serialized and must return a value that can be fed to the encoder.

By default, resources will not necessarily serialize the objects in their own payloads. Instead, they will look up the registered resource instance for the model using WebAPIResourec.get_resource_for_object(), and serialize with that. A resource can override that logic for its own payloads by providing a custom WebAPIResource.get_serializer_for_object() method.

Handling Requests

WebAPIResource calls the following functions based on the type of HTTP request:

  • get() - HTTP GET for individual objects.
  • get_list() - HTTP GET for resources representing lists of objects.
  • create() - HTTP POST on resources representing lists of objects. This is expected to return the object and HTTP 201 Created on success.
  • update() - HTTP PUT on individual objects to modify their state based on full or partial data.
  • delete() - HTTP DELETE on an individual object. This is expected to return HTTP 204 No Content on success. The default implementation just deletes the object.

Any function that is not implemented will return an HTTP 405 Method Not Allowed. Functions that have handlers provided should set WebAPIResource.allowed_methods to a tuple of the HTTP methods allowed. For example:

allowed_methods = ('GET', 'POST', 'DELETE')

These functions are passed an HTTPRequest and a list of arguments captured in the URL and are expected to return standard HTTP response codes, along with a payload in most cases. The functions can return any of:

In general, it’s best to return one of the tuples containing an HTTP status, and not any object, but there are cases where an object is necessary.

Commonly, a handler will need to fetch parent objects in order to make some request. The values for all captured object IDs in the URL are passed to the handler, but it’s best to not use these directly. Instead, the handler should accept a **kwargs parameter, and then call the parent resource’s WebAPIResource.get_object() function and pass in that **kwargs. For example:

def create(self, request, *args, **kwargs):
    try:
        my_parent = myParentResource.get_object(request, *args, **kwargs)
    except ObjectDoesNotExist:
        return DOES_NOT_EXIST

Expanding Resources

The resulting data returned from a resource will by default provide links to child resources. If a lot of aggregated data is needed, then instead of making several queries the caller can use the ?expand= parameter. This takes a comma-separated list of keys in the resource names found in the payloads and expands them instead of linking to them.

This can result in really large downloads, if deep expansion is made when accessing lists of resources. However, it can also result in less strain on the server if used correctly.

Faking HTTP Methods

There are clients that can’t actually request anything but HTTP POST and HTTP GET. An HTML form is one such example, and Flash applications are another. For these cases, an HTTP POST can be made, with a special _method parameter passed to the URL. This can be set to the HTTP method that’s desired. For example, PUT or DELETE.

Permissions

Unless overridden, an object cannot be modified, created, or deleted if the user is not logged in and if an appropriate permission function does not return True. These permission functions are:

Browser Caching

To improve performance, resources can make use of browser-side caching. If a resource is accessed more than once, and it hasn’t changed, the resource will return an HTTP 304 Not Modified.

There are two methods for caching: Last Modified headers, and ETags.

Last Modified

A resource can set WebAPIResource.last_modified_field() to the name of a DateTimeField in the model. This will be used to determine if the resource has changed since the last request.

If a bit more work is needed, the WebAPIResource.get_last_modified() function can instead be overridden. This takes the request and object and is expected to return a timestamp.

ETags

ETags are arbitrary, unique strings that represent the state of a resource. There should only ever be one possible ETag per state of the resource.

A resource can set the WebAPIResourec.etag_field to the name of a field in the model.

If no field really works, WebAPIResource.autogenerate_etags can be set. This will generate a suitable ETag based on all fields in the resource. For this to work correctly, no custom data can be added to the payload, and links cannot be dynamic.

If more work is needed, the WebAPIResource.get_etag() function can instead be overridden. It will take a request and object and is expected to return a string.

Mimetypes

Resources should list the possible mimetypes they’ll accept and return in WebAPIResource.allowed_mimetypes. Each entry in the list is a dictionary with list containing a mimetype for resource lists, and item containing the equivalent mimetype for a resource item. In the case of a singleton, item will contain the mimetype. If the mimetype is not applicable to one of the resource forms, the corresponding entry should contain None.

Entries in these lists are checked against the mimetypes requested in the HTTP Accept header, and, by default, the returned data will be sent in that mimetype. If the requested data is a resource list, the corresponding resource item mimetype will also be sent in the Item-Content-Type header.

By default, this lists will have entries with both list and item containing application/json and application/xml, along with any resource-specific mimetypes, if used.

Resource-specific Mimetypes

In order to better identify resources, resources can provide their own custom mimetypes. These are known as vendor-specific mimetypes, and are subsets of application/json and application/xml. An example would be application/vnd.example.com.myresource+json.

To enable this on a resource, set WebAPIResource.mimetype_vendor to the vendor name. This is often a domain name. For example:

mimetype_vendor = 'djblets.org'

The resource names will then be generated based on the name of the resource (WebAPIResource.name_plural for resource lists, WebAPIResource.name for resource items and singletons). These can be customized as well:

mimetype_list_resource_name = 'myresource-list'
mimetype_item_resource_name = 'myresource'

When these are used, any client requesting either the resource-specific mimetype or the more generic mimetype will by default receive a payload with the resource-specific mimetype. This makes it easier to identify the schema of resource data without hard-coding any knowledge of the URI.

Limiting Payload Contents

New in version 0.9.

Often times, the client won’t actually need the full contents of an API payload. Returning a full payload would not only increase the amount of data that needs to be transferred, but would also incur extra processing time on both the server and client, possibly also additional database queries.

Clients can specify a list of fields and/or links that should be returned in the payload by including ?only-fields= or ?only-links= in the URL in any GET requst. These should contain a comma-separated list of fields or link names to include. To prevent any fields/links from being returned, simply leave the list blank.

To limit fields/links in PUT or POST requests, you should instead send a field in the request called only_fields or only_links. The behavior is exactly the same as for GET requests.