Freitag, 3. August 2012

Django REST TastyPie helper

Heute habe ich mir django und REST-API angeschaut, nach dem Google ein Dutzen django-rest-projekte ausgespuckt hat, bin ich auf tastypie (git) gestoßen, da das Projekt schon seit einigen Jahren existiert und munter weiter entwickelt wird habe ich mir das näher angeschaut.

Schnell pip install tastypie in die virtualenv eingegeben, ein bisschen konfigurieren und voila! es klappt.

Nur leider ist es doch ein bisschen mühsam für jede ORM-Klasse das Tastypie-Gegenstück (Resource-Klasse) zu schreiben, auch wenn die Klasse trivial aufgebaut ist.

Der Ansatz jede Resource-Klasse in der urls.py zu registrieren erscheint mir nicht sehr elegant, das fürht bei größeren Projekten mit mehreren "Apps" und Klassen zu einem riesen Import-Schlacht (korrigiert mich, falls ich da falsch lege).

Also habe ich mich kurzer Hand entschieden eine API-POOL zu schreiben, der sich drum kümmert triviale Resource-Klassen dynamisch zu erstellen und komplexe Klassen zu sammeln.

#Datei: TastyPiePool
#coding: utf-8
'''
Created on 03.08.2012

@author: trungphanan
@contact: info@level96.de

'''

from tastypie.api import Api
from tastypie.resources import ModelResource, ALL, ALL_WITH_RELATIONS
from tastypie.authentication import Authentication
from tastypie.authorization import DjangoAuthorization
import tastypie.fields



class TastyPiePool(object):
    '''
    '''

    def __init__(self, api_name="v1"):
        self.api = Api(api_name=api_name)
        self.allowed_methods = ("get", "post", "put", "delete")
        

    def register(self, orm_class, resource_name, foreign_keys={}, filtering={}, manager=None, excludes=None, fields=None, allowed_methods=None, authentication=None, authorization=None):
        '''
        Dynamicly creates Resource Class from Django-ORM-Model
        '''
        
        resource_attrs = {"Meta": type(
            "Meta", (), 
            {
                "queryset": orm_class.objects.all() if not manager else manager,
                "resource_name": resource_name,
                "excludes": excludes, 
                "fields": fields, 
                "allowed_methods": allowed_methods if allowed_methods else self.allowed_methods,
                "authentication": Authentication() if not authentication else authentication,
                "authorization": DjangoAuthorization() if not authorization else authorization,
                "filtering": dict([ (f, ALL_WITH_RELATIONS if filtering[f]=="all" else filtering[f]) for f in filtering ])
            }
        )}
        
        for fk in foreign_keys:
            resource_attrs.update({ 
                fk: tastypie.fields.ForeignKey(foreign_keys[fk], 
                getattr(foreign_keys[fk].Meta, "resource_name")) 
           })
            
        
        cls = type(
            "%sResource" % type(orm_class).__name__,
            (ModelResource, object), 
            resource_attrs
        )
        
        self.register_resource(cls())
        
        return cls
    
    
    def register_resource(self, resource, canonical=True):
        self.api.register(resource, canonical)
    
    
    @property
    def urls(self):
        return self.api.urls

        


api_pool = TastyPiePool()


in der models.py importieren wir die API-POOL und registrieren die Klasse und den resource-name, letzteres wird als Slug für die API verwendet.
Die Funktion register generiert dynamisch eine Resource-Klasse mit dem Namen: orm-klasse-name Resource (tastypie namens-konvention) mit der entsprechenden Meta-Klasse.
Dabei kann ein abweichenden Object-Manager, excludes, fields oder allowed_methods angegeben werden (siehe tastypie doku)

Für den Fall das ihr komplexere Resource-Klassen benötigt und geschrieben habt, könnt ihr die Funktion "register_resource(CLASS)" benutzen.

from TastyPiePool import api_pool

class Poll(models.Model):
    name = models.TextField()

api_pool.register(Poll, 'poll' ) # args: orm-class, resource_name


Nach dem die Klasse registiert wurde müssen wir die api_pool nur noch der urls.py registriert werden damit die urls erreichtbar sind.


from django.conf.urls.defaults import *

from TastyPiePool import api_pool


urlpatterns = patterns('',
    (r'^api/', include(api_pool.urls)),
)


danach server neustarren und ihr erreicht die API unter:
http://host:port/api/v1/RESCOURCE-NAME/?format=json

viel spaß beim Testen und Erweitern des Scripts


für jQuery-API klickt ihr hier.



Keine Kommentare:

Kommentar veröffentlichen