django-tastypie-extendedmodelresource 0.22
An extension of TastyPie's ModelResource to easily support nested resources, and more.
License: BSD
Keywords: REST RESTful tastypie django resource nested extension

==========================================
 Django TastyPie's Extended ModelResource
==========================================

The ``ExtendedModelResource`` is an extension for TastyPie's ``ModelResource`` that adds some interesting features:

* Supports easily using resources as *nested* of another resource, with proper authorization checks for each case.
* [This feature has already been included in the official TastyPie] Supports using a custom identifier attribute for resources in uris (not only the object's pk!)


Requirements
============

Required
--------
* Latest django-tastypie from repository (0.9.12-alpha or hopefully greater) and its requirements.

Optional
--------
* Django 1.4.1 for the sample project.


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

Clone repository and do:

    python setup.py install

Or just do

    pip install django-tastypie-extendedmodelresource

to get the latest version from `pypi <http://pypi.python.org/pypi/django-tastypie-extendedmodelresource>`_.


*Nested* resources
==================

Here we explain what we mean by *nested resources* and what a simple use case would be.

Rationale
---------

Imagine you have a simple application which has users, each of which can write any number of entries. Every entry is associated to a user. For example ::

    from django.contrib.auth.models import User
    from django.db import models


    class Entry(models.Model):
        user = models.ForeignKey(User, related_name='entries')
        # ... more fields

The 'standard' TastyPie models for this would be ::

    from django.contrib.auth.models import User
    from tastypie.resources import ModelResource
    
    from myapp.models import Entry


    class UserResource(ModelResource):
        class Meta:
            queryset = User.objects.all()
            
    class EntryResource(ModelResource):
        class Meta:
            queryset = Entry.objects.all()


This gives you full CRUD ability over users and entries, with uris such as ``/api/user/`` and ``/api/entry/``.

Now imagine you want to be able to easily list all the entries authored by a given user, with a uri such as ``/api/user/<pk>/entries``. Additionally, you would like to be able to POST to this uri and create an entry automatically associated to this user. This is why nested resources are for.

If a resource such as the ``EntryResource`` is to be accessed as ``/api/user/<pk>/<something>`` where ``<something>`` is custom-defined (for example ``entries``), then we say the ``EntryResource`` is being used as **nested** of the ``UserResource``. We also say that ``UserResource`` is the **parent** of ``EntryResource``.

The standard TastyPie's ``ModelResource`` would force you to write a function overriding the urls of the ``UserResource`` and adding a method to handle the entry resource (see `Nested Resources <http://django-tastypie.readthedocs.org/en/latest/cookbook.html#nested-resources>`_). Using ``ExtendedModelResource`` it is as easy as ::

    from django.contrib.auth.models import User
    from tastypie import fields

    from extendedmodelresource import ExtendedModelResource
    from myapp.models import Entry


    class UserResource(ExtendedModelResource):
        class Meta:
            queryset = User.objects.all()

        class Nested:
            entries = fields.ToManyField('api.resources.EntryResource', 'entries')


    class EntryResource(ExtendedModelResource):
        user = fields.ForeignKey(UserResource, 'user')

        class Meta:
            queryset = Entry.objects.all()
            
And that's it!


How authorization is handled
----------------------------
If a resource does not have a nested resource, the authorization is handled the same way as in the standard TastyPie. You define an ``Authorization`` class and associate it to the resource. This class may implement the ``is_authorized`` and ``apply_limits`` methods.

For an ``ExtendedModelResource`` with nesteds, all the authorization when using the nested as such is handled from the authorization class **of the parent resource**. For each resource used as nested, the ``Authorization`` class of the parent can implement two methods:

* ``is_authorized_nested_<attribute>``
* ``apply_limits_nested_<attribute>``

where ``<attribute>`` is the name of the attribute parameter in the ``ApiField`` that declares the resource as nested. These functions work identically to the original ones, except that they also receive a ``parent_object`` parameter which will contain the parent object.

For our users and entries example, an ``Authorization`` can be something like::

    from tastypie.authorization import Authorization
    
    
    class UserResourceAuthorization(Authorization):
        """
        Our Authorization class for UserResource and its nested.
        """
    
        def is_authorized(self, request, object=None):
            # Only 'newton' is authorized to view the users
            if 'newton' in request.user.username:
              return True
    
            return False
    
        def apply_limits(self, request, object_list):
            return object_list.all()
    
        def is_authorized_nested_entries(self, request,
                                         parent_object, object=None):
            # Is request.user authorized to access the EntryResource as
            # nested?
            return True
    
        def apply_limits_nested_entries(self, request, parent_object,
                                       object_list):
            # Advanced filtering.
            # Note that object_list already only contains the objects that
            # are associated to parent_object.
            return object_list.all()

Caveats
-------
* ``ExtendedModelResource`` only supports one level nesting.
* Resources used as nested can also be registered in an **Api** instance, but need not to. That is, there can be resources used **only** as nested and not exposed otherwise in the urls.


Changing object's identifier attribute in urls
==============================================

Using the latest TastyPie you can define a ``detail_uri_name`` attribute
in the ``Meta`` class, to use a different attribute than the object's ``pk`` ::

    class UserResource(ExtendedModelResource):
        class Meta:
            queryset = User.objects.all()
            detail_uri_name = 'username'

With ``ExtendedModelResource`` you can change the regular expression used for your identifier attribute in the urls, you can override the method ``get_url_id_attribute_regex`` and return it, like the following example ::

    def get_detail_uri_name_regex(self):
        return r'[aA-zZ][\w-]*'

More information
================

:Date: 08-20-2012
:Version: 0.2
:Authors:
  - Alan Descoins - Tryolabs <alan@tryolabs.com>
  - Martín Santos - Tryolabs <santos@tryolabs.com>

:Website:
  https://github.com/tryolabs/django-tastypie-extendedmodelresource