django-yapi 0.3.4
A mini-framework for Django for creating RESTful APIs.
License: BSD License

===============
django-yapi
===============

YAPI == Yet/Why Another API Framework

This is a mini-framework for creating RESTful APIs in Django.


Installation
============

1. Download dependencies:
    - Python 2.6+
    - Django 1.5+
    
2. ``pip install django-yapi`` or ``easy_install django-yapi``



Configuration
=============

settings.py
-----------

1. Add "yapi" to your INSTALLED_APPS setting like this::

    INSTALLED_APPS = (
        # all other installed apps
        'yapi',
    )
      
2. Add logger handler::

    LOGGING = {
        'version': 1,
        'disable_existing_loggers': False,
        'handlers': {
            # all other handlers
            'log_file_yapi': {
                'level': 'DEBUG',
                'class': 'logging.handlers.RotatingFileHandler',
                'filename': os.path.join(os.path.join(os.path.dirname( __file__ ), '..'), 'logs/yapi.log'),
                'maxBytes': '16777216', # 16megabytes
             },
        },
        'loggers': {
            # all other loggers
            'yapi': {
                'handlers': ['log_file_yapi'],
                'propagate': True,
                'level': 'DEBUG',
            }
        }
    }
    
3. Make sure you have a 'HOST_URL' setting containing the address in which the app is deployed:
    ``HOST_URL = 'http://localhost:8000'`` (example)
    
4. If you want to use the middleware that enables CORS, you have to configure it by adding the following settings::

    MIDDLEWARE_CLASSES = (
        ...
        'yapi.middleware.XsSharing'
    )

    YAPI = {
    	...
    
        'XS_SHARING_ALLOWED_ORIGINS': '*', # The allowed domains
        'XS_SHARING_ALLOWED_METHODS': ['POST','GET','OPTIONS', 'PUT', 'DELETE'] # ... and methods
    }
    
5. If you wish to disable ENTIRELY (be careful! :P) CSRF Validation, add the following settings::

    YAPI = {
    	...
    	
        'CSRFValidation': False
    }

Logs
----

Create a 'logs' folder in your project's root folder (if you don't have one already).
Your project folder should look something like this::

    myproject/
        __init__.py
        settings.py
        urls.py
        wsgi.py
    logs/
    manage.py

Database
--------

Run ``python manage.py syncdb`` to create the yapi models.


API Namespace
=============

Now, you will have to decide the URL "namespace" from which your API will be available and add it to
your top-level ``urls.py``.

Yapi's **convention** is that everything that regards to the API (urls, handlers, serializers, etc) should be in a
package named "api" inside the respective app package. This way, the API namespace should point to a urls.py
inside an "api" package in your project's main package::

    myapp/
        api/
            __init__.py
            handlers.py
            serializers.py
            urls.py
        __init__.py
        models.py
        urls.py
        views.py
    myproject/
        api/
            __init__.py
            urls.py
        __init__.py
        settings.py
        urls.py
        wsgi.py
    logs/
    manage.py

Add namespace to the top-level ``urls.py``::

    # myproject/urls.py
    # ============

    urlpatterns = patterns('',
       # all other url mappings
       url(r'^api', include('myproject.api.urls', namespace='api')),
    )
    
In this example, we have an app called "myapp" which has an API. In order for it to be "acessible", its URLs must be
added to the top-level API namespace::

    # myproject/api/urls.py
    # ============
    
    urlpatterns = patterns('',
        # all other api url mappings
        url(r'^/myapp', include('myapp.api.urls', namespace='myapp')),
    )
    
Resources
=========

A "Resource" maps an URL to the code that will handle the requests made to it.

By convention, Resource handlers reside in a file called ``handlers.py`` in the api package::

    # myapp/api/handlers.py
    # ============

	from yapi.resource import Resource
    from yapi.response import HTTPStatus, Response

    class ResourceIndex(Resource):
        """
        API endpoint handler.
        """
    
        # HTTP methods allowed.
        allowed_methods = ['GET']
    
        def get(self, request):
            """
            Process GET request.
            """
                                
            # Return.
            return Response(request=request,
                            data={ 'hello': 'world' },
                            serializer=None,
                            status=HTTPStatus.SUCCESS_200_OK)
                            
Now we map the handler to a given URL::

    # myapp/api/urls.py
    # ============
    
    from django.conf.urls import patterns, url
    from yapi.resource import Resource

    from handlers import ResourceIndex

    urlpatterns = patterns('',
        url(r'^/?$', ResourceIndex.as_view(), name='index'),
    )
    
This way, if put ``http://localhost:8000/api/myapp`` in the address bar of your browser, you should get a JSON object
in return containing ``{ 'hello': 'world' }``.

Basic Schema
------------

From the example above we can see how easy it is to write a Resource class. You just need to set the ``allowed_methods``
array with the HTTP verbs that the handler supports and then, for each allowed verb, write the respective method.

Yapi's **convention** is to use POST/GET/PUT/DELETE to CREATE/READ/UPDATE/DELETE.

- ``POST`` -> ``def post(request)``
- ``GET`` -> ``def get(request)``
- ``PUT`` -> ``def put(request)``
- ``DELETE`` -> ``def delete(request)``

**IMPORTANT:** In this example there isn't any additional value being passed by the URL, therefore the only data received
by the methods is the standard Django ``request``. Make sure to include in the method any other additional parameter
that may be passed by the URL.

Authentication & Authorization
------------------------------

If the resource should only be accessible via authenticated users, then a variable ``authentication`` should be set
with an array of the valid authentication types. Yapi ships with the following authentication methods:

- ``yapi.authentication.SessionAuthentication`` -> Validates if the request is made by a browser with a valid Django session (i.e. user is logged in to the site)
- ``yapi.authentication.ApiKeyAuthentication`` -> Validates if the request is made with a valid ``api_key`` provided as a GET parameter.

When several authentication methods are accepted, **the request is considered authenticated as soon as one checks**
(e.g. SessionAuthentication fails, but APIKeyAuthentication validates). If the user is authenticated, it is added to
the ``request`` object and can be accessed by ``request.auth['user']``.

If the resource should only be acessible by authenticated users that match a specifc ruleset, then ``permissions``
should be set with an array of all the authorization credentials required. Yapi ships with the following authorization
methods:

- ``yapi.permissions.IsStaff`` -> Checks if user has Staff permission.

**In order for the authorization to be validated all authorization classes must check**.

If we wanted to make the Resource in the example above only available to authenticated staff users, it would look
something like this::

    # myapp/api/handlers.py
    # ============

    from yapi.authentication import SessionAuthentication, ApiKeyAuthentication
    from yapi.resource import Resource
    from yapi.response import HTTPStatus, Response

    class ResourceIndex(Resource):
        """
        API endpoint handler.
        """
    
        # HTTP methods allowed.
        allowed_methods = ['GET']
        
        # Authentication & Authorization.
        authentication = [SessionAuthentication, ApiKeyAuthentication]
        permissions = [IsStaff]
    
        def get(self, request):
            """
            Process GET request.
            """
                                
            # Return.
            return Response(request=request,
                            data={ 'hello': 'world' },
                            serializer=None,
                            status=HTTPStatus.SUCCESS_200_OK)
                            
Request Body
------------

When the request is a ``POST`` or a ``PUT``, it is assumed that there is a request body and, if it isn't present or fails
parsing, the request fails.

**IMPORTANT** Currently, the only format accepted for the request body is a JSON payload.

The request body is parsed into a native Python ``dict`` and can be acessible in ``request.data``.
    
Resource Listing
----------------

In trying to follow some HATEOAS principles, we suggest that the API's root URL should return a listing of the available
resources and respective URLs::

    # myproject/api/resources.py
    # ============
    
    from django.conf import settings
    from django.core.urlresolvers import reverse

    def get_api_resources_list(user):
        return {
            'url': settings.HOST_URL + reverse('api:index'),
            'resources': {
                'myapp': {
                    'url': settings.HOST_URL + reverse('api:myapp:index')
                }
            }
        }
        
Now, add it to the API's root URL::

    # myproject/api/urls.py
    # ============
    
    urlpatterns = patterns('',
        # all other api url mappings
        url(r'^/?$', ResourcesListHandler.as_view(), name='index'),
        url(r'^/myapp', include('myapp.api.urls', namespace='myapp')),
    )
    
And write the respective handler::

    # myproject/api/handlers.py
    # ============
  
  	from yapi.resource import Resource
    from yapi.response import HTTPStatus, Response
    from resources import get_api_resources_list

    class ResourcesListHandler(Resource):
        """
        API endpoint handler.
        """
    
        # HTTP methods allowed.
        allowed_methods = ['GET']
    
        def get(self, request):
            """
            Process GET request.
            """
                                
            # Return.
            return Response(request=request,
                            data=get_api_resources_list(request.auth['user']),
                            serializer=None,
                            status=HTTPStatus.SUCCESS_200_OK)
                            
Don't forget to keep this list updated everytime you make changes to your resources.

Response
=========

``yapi.response.Response`` is the prefered way of returning a call to a given handler (Django's ``HTTPResponse`` also works)

- ``request`` -> The request that originated this response.
- ``data`` -> The raw response data (a Python *dict*, with all data in native types)
- ``serializer`` -> The serializer that will be used to serialize the data.
- ``status`` -> The HTTP status code of the response (preferably from ``yapi.response.HTTPStatus``)
- ``pagination`` (optional) -> When the response data is a QuerySet, this states if the response should be paginated or not. Default is True.
- ``filters`` (optional) -> When the response data is a QuerySet and it was filtered by given parameters, they are provided in this field.

Serializers
-----------

When the response ``data`` is a *complex* Python object, it must first be serialized to native Python types. This way,
each for every resource that may be returned, a serializer that implements ``yapi.serializers.BaseSerializer`` must be
written.

Basically, a ``to_simple(self, obj, user=None)`` method has to be implemented.

- ``obj`` -> The object instance that will be serialized.
- ``user`` (optional) -> The user that made the request. This is useful when the instance representation varies according to the user/permissions.

Lets look at an example for serializing a user::

    from apps.api.serializers import BaseSerializer

    class UserSerializer(BaseSerializer):
        """
        Adds methods required for instance serialization.
        """
        
        def to_simple(self, obj, user=None):
            """
            Please refer to the interface documentation.
            """
            # Build response.
            simple = {
                'email': obj.email,
                'name': obj.name,
                'last_login': obj.last_login.strftime("%Y-%m-%d %H:%M:%S")
            }
        
            # Return.
            return simple

In this case, an example response could be::

    return Response(request=request,
                    data=request.auth['user'],
                    serializer=UserSerializer,
                    status=HTTPStatus.SUCCESS_200_OK)