When you create a new API called Swords, the resource name is Sword and the file name is SwordResource. The endpoint for CRUD operations are:

ActionHTTP methodEndpoint
CreatePOST/swords
ListGET/swords
DetailGET/swords/:id
UpdatePUT/swords/:id
DeleteDELETE/swords/:id

If your resource belongs to a parent model, for example a sword belongs to a team, then your endpoints will be the following (note that swords for the create and list endpoints are nested under the teams resource:

ActionHTTP methodEndpoint
CreatePOST/teams/:id/swords
ListGET/teams/:id/swords
DetailGET/swords/:id
UpdatePUT/swords/:id
DeleteDELETE/swords/:id

Collection

This method is executed when you make a request for a list of a specific resource.

Example: GET /swords.

Create a file called SwordResource that is a subclass of 1 of the following:

  1. GenericResource: subclass this class if you want to execute arbitrary code
  2. DatabaseResource: subclass this class only if your resource is directly associated to a database model. This subclass has many methods defined by default that handles CRUD operations automatically.

Creating a subclass of GenericResource

Create a file called backend/api/resources/SwordResource.py:

from .GenericResource import GenericResource
import requests

class SwordResource(GenericResource):
    @classmethod
    def collection(self, query, meta, user, **kwargs):
        """
        query:
            a dictionary of URL query parameters

        meta:
            a dictionary of meta parameters from the URL; such as _limit and _format

        user:
            a user object that represents the current user who is making the API call

        kwargs:
            a dictionary of other values that are passed down through the API request lifecycle; such
            as parent_model, etc.
        """

        # Fetch a list of Swords from the internet
        response = requests.get('https://www.dropbox.com/s/mnjsxzdsyc39hr3/IkeaStorageJson.json')

        return self.build_result_set(response['swords'], user, **kwargs)

Creating a subclass of DatabaseResource

from .DatabaseResource import DatabaseResource
from examples.models import Sword

class SwordResource(DatabaseResource):
    # Set this class variable to the model that the resource is associated with
    model_class = Sword

    """
    Optionally override collection because the DatabaseResource will handle limits
    and pagination automatically. If you don’t override the collection method, DatabaseResource
    will automatically fetch a list of models from the database for you.
    """
    @classmethod
    def collection(self, query, meta, user, **kwargs):
        """
        Use collection to handle pre-filtering of the results from the database.
        """

        # Filter swords that are weak and belong to the current team
        team = kwargs['parent_model']
        return Sword.objects.filter(team_id=team.id, weak=False)

Here is how we would get a list of swords that are weak and belong to team ID 1:

user = User.objects.get(id=3)
team = Team.objects.get(id=1)

# You typically don’t have to call this function by itself.
# This function is called by making a request to /teams/1/swords.
swords = SwordResource.collection({}, {}, user, parent_model=team)

Create

This method is executed when you make a request to create a new instance of a resource.

Example: POST /swords.

GenericResource

from services.datadog import increment

class SwordResource(GenericResource):
    # code from before

    @classmethod
    def create(self, payload, user, **kwargs):
        """
        payload:
            a dictionary of the request body

            For example, if your request body is { "sword" : { "weak": false } },
            then your payload will be the following dictionary: { 'weak': False }.

            NOTE:
            Payloads must be nested inside a key named after the singular form of the resource name.

        user:
            a user object that represents the current user who is making the API call

        kwargs:
            a dictionary of other values that are passed down through the API request lifecycle;
            such as parent_model, etc.
        """

        # Increment a create metric in DataDog
        increment('swords.create')

        if payload.get('weak'):
            name = 'Weak sword'
        else:
            name = 'Strong sword'

        # Always return a new instance of the resource
        return self({ 'name': name }, user, **kwargs)

DatabaseResource

You typically never have to override the create class method. The instances where it is overrided include a scenario when you want to execute a callback function only if the resource was successfully created.

The DatabaseResource create method will take the keys and values in the payload argument, assign those values to a newly instantiated Sword object, and then save the model to the database.

class SwordResource(DatabaseResource):
    # code from before

    @classmethod
    def create(self, payload, user, **kwargs):
        def _upload(resource):
            # upload something only if the resource was successfully created

        self.on_create_callback = _upload

        return super().create(payload, user, **kwargs)

Member

This method is executed when you make a fetch a single resource using it’s ID. Example: GET /swords/:id.

GenericResource

class SwordResource(GenericResource):
    # code from before

    @classmethod
    def member(self, pk, user, **kwargs):
        """
        pk:
            a string or integer from the URL parameter :id

        user:
            a user object that represents the current user who is making the API call

        kwargs:
            a dictionary of other values that are passed down through the API request lifecycle; such
            as parent_model, etc.
        """

        # Fetch 1 of Sword from the internet
        array = requests.get('https://www.dropbox.com/s/mnjsxzdsyc39hr3/IkeaStorageJson.json')
        sword = filter(lambda x: x['id'] == pk, array)

        return self({ 'name': sword['name'], 'weak': False }, user, **kwargs)

DatabaseResource

You almost never have to override the member method. If you do, see TrainingRunResource for an example.

Update

This method is executed when you want to update a single resource using it’s ID. Example: PUT /swords/:id.

This is almost the same as the create method except it’s an instance method and the ID of the resource you want to update must be included in the URL.

class SwordResource(GenericResource):
    # code from before

    def update(self, payload, **kwargs):
        """
        payload:
            a dictionary of the request body

            For example, if your request body is { "sword" : { "weak": false } },
            then your payload will be the following dictionary: { 'weak': False }.

            NOTE:
            Payloads must be nested inside a key named after the singular form of the resource name.

        kwargs:
            a dictionary of other values that are passed down through the API request lifecycle; such
            as parent_model, etc.
        """

        print('Awesome update!')

        # Always return self
        return self

DatabaseResource

You almost never have to override the update method. If you do, see ModelVersionResource for an example.

Delete

This method is executed when you want to delete a single resource using it’s ID. Example: DELETE /swords/:id.

class SwordResource(GenericResource):
    # code from before

    def delete(self, **kwargs):
        """
        kwargs:
            a dictionary of other values that are passed down through the API request lifecycle; such
            as parent_model, etc.
        """

        print('Deleted!')

DatabaseResource

You almost never have to override the delete method.

Registering parent models

If your resource belongs to a parent model, for example a sword belongs to a team, you must do the following in order for these endpoints to work properly:

  1. Create: POST /teams/:id/swords
  2. List: GET /teams/:id/swords
import importlib

class SwordResource(DatabaseResource):
    # code from before

SwordResource.register_parent_resource(
    getattr(importlib.import_module('api.resources.TeamResource'), 'TeamResource'),
)

Pre-load single associated object

The sword model belongs to a user via the sword.user_id column. If you are fetching a list of 100 swords and in your code you are accessing the methods of the user object via sword.user (e.g. sword.user.name), you will inadvertently make 100 extra API calls to the users database table. Do the following to prevent this:

import importlib

class SwordResource(DatabaseResource):
    # code from before

SwordResource.register_collective_loader_find(
    getattr(importlib.import_module('api.resources.UserResource'), 'UserResource'),
)

Pre-load multiple associated objects

The sword has many opponents that were defeated by it. You can query all the opponents via the swords.opponents.objects.all() method. This will fetch all opponents from the database where opponent.sword_id equals sword.id. If you are accessing the opponents of each sword when fetching a list of 100 swords, you will be making 100 extra API calls to the opponents database. Do the following to prevent this:

import importlib

class SwordResource(DatabaseResource):
    # code from before

SwordResource.register_collective_loader_select(
    getattr(importlib.import_module('api.resources.OpponentResource'), 'OpponentResource'),
)