Showing posts with label json. Show all posts
Showing posts with label json. Show all posts

Friday, July 16, 2010

Getting piston forms to play nicely with JSON

Critical Update 2012/05/10!!!
Critical Update 2012/05/10!!!
Critical Update 2012/05/10!!!

Except for a critical security patch, django-piston has been unsupported for about 3 years. That is an eternity, and the number of forks to address multiple issues is cause for alarm. Also, the original author has left the project. Because of that, in it's place at this time I recommend django-tastypie. It is up-to-date, has very good documentation, supports OAUTH, and scored second place in the Django Packages thunderdome (it got nearly 3x as many points!). Another tool to consider is Django Rest Framework, which is as good as django-tastypie but lacks the OAUTH support.

Back to the existing blog post...

A commonly used tool by Djangonauts is django-piston, which is designed to make building a REST API easier. It even works with Django forms to provide easily written PUT/POST validation, which should be pretty darn nice. Unfortunately, if you go with django-piston forms validation it doesn't accomodate the JSON (or XML or YAML) requests and if validation fails it responds in HTML. Even more unfortunate, making validation accept and return JSON with PUT/POST requests is not documented.

While one could argue that it is documented in the django-piston docstrings, in my opinion that is not sufficient.

Fortunately while working on a project for Revolution Systems we worked out a solution:

"""
myapi/resource.py

    author: Daniel Greenfeld
    license: BSD

This assumes your API accepts JSON only.
"""

import json

from piston.decorator import decorator
from piston.resource import Resource
from piston.utils import rc, FormValidationError

def validate(v_form, operation='POST'):
    """ This fetches the submitted data for the form 
        from request.data because we always expect JSON data
        It is otherwise a copy of piston.util.validate.
    """
        
    @decorator
    def wrap(f, self, request, *a, **kwa):
        
        # Assume that the JSON response is in request.data
        # Probably want to do a getattr(request, data, None)
        #   and raise an exception if data is not found
        form = v_form(request.data)

        if form.is_valid():
            setattr(request, 'form', form)
            return f(self, request, *a, **kwa)
        else:
            raise FormValidationError(form)
    return wrap

class Resource(Resource):
    
    def form_validation_response(self, e):
        """
        Turns the error object into a serializable construct.
        All credit for this method goes to Jacob Kaplan-Moss
        """
        
        # Create a 400 status_code response
        resp = rc.BAD_REQUEST
        
        # Serialize the error.form.errors object
        json_errors = json.dumps(
            dict(
                (k, map(unicode, v))
                for (k,v) in e.form.errors.iteritems()
            )
        )
        resp.write(json_errors)
        return resp

Usage in handlers.py:

from django import forms

from piston.handler import BaseHandler

from myapp.models import Article

# We use our custom validate rather than piston's default
from myapi.resource import validate

class ArticleForm(forms.Form):
    """ This is best stored in forms.py but we put 
        here for sake of clarity"""

    author      = forms.CharField(required=True)
    title       = forms.CharField(required=True)
    content     = forms.CharField(required=True)

class ArticleHandler(BaseHandler):

    allowed_methods = ('GET', 'POST', 'PUT', 'DELETE', )
    model = Article
                    
    @validate(ArticleForm)
    def create(self, request):
        # Create/POST code goes here. 

    @validate(ArticleForm)
    def update(self, request, id):
        # Update/PUT code goes here. 


Usage in urls.py:

from django.conf.urls.defaults import *

from piston.authentication import HttpBasicAuthentication as auth

# Import our ArticleHandler
from myapi.handlers import ArticleHandler
# Use our custom Resource class instead of piston's default
from myapi.resource import Resource 

article_handler = Resource(ArticleHandler, authentication=auth)

urlpatterns = patterns('',
   url(
        r'^articles/(?P(\d+))$', 
        article_handler,
        { 'emitter_format': 'json' },
        name='api_article'
       ),   
)

Of course, this assumes you are mapping Create/Read/Update/Delete (CRUD) actions to your API.

I'm interested to see other solutions people have used to handle this in django-piston, and what suggestions people have that could improve on the examples I'm supplying here.

Tuesday, May 8, 2007

JSON and Python

I hate XML.
I love JSON.

Now that I've made that clear, lets go into how I want Python to handle my JSON.
  1. I want a built-in function to JSONify.
  2. I want a built-in function to deJSONify.
Now in the Python world there are multiple JSON libraries. cJSON, simplejson, demjson and more. I've used a few of these and they all seem good. However, most have extraneous methods that I don't care about, or name their json handling methods funny. For example, simplejson has you do loads() and dumps() for loading and dumping of objects/strings, and load() and dump() for loading and dumping of files. Kind of nice, but all I really want is a JSONify and a deJSONify function from my JSON handler. I can do the rest!

So what I've thought about doing is writing a Python JSON package that would do the following:
  1. Load a JSON handling package. It would have a list to select from so that if simplejson was not already on your machine, it might check for cjson, demjson, etc and grab the alternate instead.
  2. Have a JSONify function that would convert Python objects to JSON.
  3. Have a deJSONify function to convert JSON into Python objects.
This looks like a fun and handy little project.