Showing posts with label piston. Show all posts
Showing posts with label piston. Show all posts

Oct 22, 2009

django-piston authentication against django auth -- part 4

The previous post ends with one known "Open Issue". The authentication for the ajax call was sitll hard coded [1]. In this post I am going to show you how you can extend django-piston to authenticate your users against django.contrib.auth. This might be important if your web app already take advantage of this module to manage and authenticate users.


A bit of reading of the source code and the documentation of django-piston [2] leads me to understand that django-piston has been designed from the ground to enable you to easily write your own authentication handler. Out of the box it comes with "HttpBasicAuthentication" and OAuthAuthentication. "HttpBasicAuthentication" is a very good example how to implement a DjangoAuthentication.

An authentication handler is a class, which must have 2 methods: is_authenticated, challenge

class DjangoAuthentication(object):
    """
    Django authentication. 
    """
    def __init__(self, login_url=None, redirect_field_name=REDIRECT_FIELD_NAME):
        if not login_url:
            login_url = settings.LOGIN_URL
        self.login_url = login_url
        self.redirect_field_name = redirect_field_name
        self.request = None
    
    def is_authenticated(self, request):
        """
        This method call the `is_authenticated` method of django
        User in django.contrib.auth.models.
        
        `is_authenticated`: Will be called when checking for
        authentication. It returns True if the user is authenticated
        False otherwise.
        """
        self.request = request
        return request.user.is_authenticated()
        
    def challenge(self):
        """
        `challenge`: In cases where `is_authenticated` returns
        False, the result of this method will be returned.
        This will usually be a `HttpResponse` object with
        some kind of challenge headers and 401 code on it.
        """
        path = urlquote(self.request.get_full_path())
        tup = self.login_url, self.redirect_field_name, path 
        return HttpResponseRedirect('%s?%s=%s' %tup)


The implementation is relatively simple, is_authenticated uses the method with the same name available from a django User and the challenge redirect the user to the login page.

This post will end the series on django-piston it was for me a pretext to get familliar with this great app that make creating a RESTFUL api for django easy. You can find all the modifications I did into my fork on bitbucket [3]


[1] http://bitbucket.org/yml/django-piston/src/a8bcb7f9756e/examples/blogserver/templates/edit_ajaxy_post.html#cl-6
[2] http://bitbucket.org/jespern/django-piston/wiki/Documentation#authentication
[3] http://bitbucket.org/yml/django-piston/

Oct 18, 2009

django-piston form validation -- part 3

In my previous post titled "Exploration of django-piston -- part 2" [1] I end the post by 2 open issues. This post will propose a solution to the first one:

The validation errors are returned as a pseudo xml string.


Bad Request <ul class="errorlist"><li>content<ul class="errorlist"><li>This field is required.</li></ul></li><li>title<ul class="errorlist"><li>This field is required.</li></ul></li></ul>


To put it in other words I would prefer if django-piston return the validation errors of the form in the format specified in the request.

JSON


$ curl -u testuser:foobar -X POST -d content="This post is created using the api" http://127.0.0.1:8000/api/posts/?format=json
Bad Request {"title": ["This field is required."]}


yaml

$ curl -u testuser:foobar -X POST -d content="This post is created using the api" http://127.0.0.1:8000/api/posts/?format=yaml
Bad Request title: [!!python/unicode 'This field is required.']

xml


$ curl -u testuser:foobar -X POST -d content="This post is created using the api" http://127.0.0.1:8000/api/posts/?format=xml
Bad Request <ul class="errorlist"><li>title<ul class="errorlist"><li>This field is required.</li></ul></li></ul>


After a while reading the source of django-piston and poking around I have implemented this feature in my branch.

The biggest hurdle was that for some reasons "form.errors" cannot be serialized directly.


simplejson.dumps(form.errors)
*** TypeError:  is not JSON serializable


In order to work around this issue you need to force python to evaluate this proxy object.


dict((key, [unicode(v) for v in values]) for key,values in form.errors.items())


The last bit is to change the JS in order to adapt it to the fact that now the validation errors are returned as a json string.


    response = response.substring(12, response.length);
    errors = eval("("+response+")");

    $.each($(':input'), function(index, value) {
        field =$(value).attr('id');
        field = field.substring(3, field.length);
        field_errors = errors[field];
        if (field_errors) {
            ul = "<ul class=\"errorlist\">\n";
            $.each(field_errors, function(index, value){
              ul += "<li>"+value+"</li>\n";
            });
            ul += "</ul>\n";
            $(value).parent().prepend(ul);
        };
    });

The complete code related to the implementation of this feature is available in my branch [3].

Open Issue

Authentication is still hardcoded into the html pages. What is the best way to keep HttpBasicAuthentication to the external api that could easily be used by curl and add single sign on with django admin interface ?

The example is still very incomplete. It is missing among other things the capability to update/delete a blog post

I would be glad to read from you on how to improve this example or to get pointer of reusable application that have been built on top of django-piston.


[1] http://yml-blog.blogspot.com/2009/10/exploration-of-django-piston-part-2.html
[2] http://bitbucket.org/yml/django-piston/changeset/a8bcb7f9756e/
[3] http://bitbucket.org/yml/django-piston/

Oct 14, 2009

exploration of django-piston -- part 2

Djangocon 09 was really food for thought and this post follow my previous post about django-piston. I found the talk from Avi Bryant about his experience building Trendly [1].

My take away of this talk was that they have a single HTML page with a lot of JS. The JS is the used to get the JSON data the JSON from the server. Unfortunately I am unable to find a link to a better description of its talk.

My last post hopefully convince you that it is easy relatively easy to create a web api to your django application [2]. This Post aims to show you how you can create a page to list and create a blogpost.

List and create a blogpost

The idea here is to reuse the web api that we have created in the previous article to build a web page. One of the interesting feature of django-piston is the decorator @validate(BlogpostForm, 'PUT'). It enables you to validate the data send to your server and it takes as input a django form. A longuer description of this feature is available here [4].

The view cannot be simpler [5] :

def create_ajaxy_post(request):
    form = BlogpostForm()
    return direct_to_template(request,
                              template='edit_ajaxy_post.html',
                              extra_context={'form':form} )
The template is using jQuery [6] which ease the ajax call and the DOM manipulation. The template [7] has a very simple structure :


{% block content %}

<h1>Ajaxy</h1>

<div id="content"></div>

<h2>Ajaxy form</h1>

<form method="POST" action="{% url posts %}" id="post_form">

    <div id="post_form_error"></div>

    {{ form.as_p }}

    <input type='submit' name='create' onclick="return send_form();" value='Create'></input>

</form>

{% endblock %}
In order to give life to this page you need some JS which will get from the JSON string of the blogposts and POST the DATA to create a new blogpost and add it to the list.

If you try to POST an empty blogpost the data will be validated on the server and the validation errors will be displayed to the user :


* content
  o This field is required.
* title
  o This field is required.


Open Issue

The validation error are returned as a pseudo xml string :


Bad Request <ul class="errorlist"><li>content<ul class="errorlist"><li>This field is required.</li></ul></li><li>title<ul class="errorlist"><li>This field is required.</li></ul></li></ul>


It will be much more convenient to get a json dict with the error. Any one has an idea on how to get this ?

Authentication is still hardcoded into the html pages. What is the best way to keep HttpBasicAuthentication to the external api that could easily be used by curl and add single sign on wiht the admin ?

The example is still very incomplete. It is missing among other things the capability to update/delete a blog post

I would be glad to read from you on how to improve this example or to get pointer of reusable application that have been built on top of django-piston.


[1] http://trendly.com/
[2] http://bitbucket.org/yml/django-piston/src/tip/examples/blogserver/README.txt
[3] http://bitbucket.org/yml/django-piston/src/72c72e0b4b7e/examples/blogserver/templates/edit_ajaxy_post.html
[4] http://bitbucket.org/jespern/django-piston/wiki/Documentation#form-validation
[5] http://bitbucket.org/yml/django-piston/src/tip/examples/blogserver/blog/views.py
[6] http://jquery.com/
[7] http://bitbucket.org/yml/django-piston/src/72c72e0b4b7e/examples/blogserver/templates/edit_ajaxy_post.html

Sep 21, 2009

Add a web api to your app with django-piston

More and more often I found myself in a situation where I would like to add a web api to my django applications. I have recently tried to used django-piston http://bitbucket.org/jespern/django-piston for this and I found the learning curve a bit steep. It is not particularly hard but it requires you to understand few things before being able to enjoy it.

This post should help you to understand how to create the handlers to read, create, update, delete an object and see how you can call this web api from the command line using curl. I have forked django-piston to extend the example "blogserver". I would recommend you to also read the README inside the example

You can grab the code like this :

hg clone http://bitbucket.org/yml/django-piston/


curl is a command line tool to transfer data from or to a server, using one of the supported protocols (HTTP, HTTPS, FTP, FTPS, SCP, SFTP, TFTP, DICT, TELNET, LDAP or FILE). The command is designed to work without user interaction.

Here it is the 4 things that I found a bit hard to understand.

1. @require_extended


This decorator is handy if you want to restrict the access to your handler to only the request that have have one of the header listed below :
• application/json
• application/x-yaml
• text/xml
• application/python-pickle

The direct effect on the curl command line is that you will need to add a -H 'Content-Type:', this will give you something like :

$ curl -u testuser:foobar -H 'Content-Type:application/json' http://127.0.0.1:8000/api/posts/?format=json


If the header is omitted django-piston will return a 'Bad Request'.

2. Request Method

The handler for your resource can be composed of the following method : read, create, update, delete that are respectively mapped to the following request method : GET, POST, PUT, DELETE. "-X " is used to specify the method you want to use. Here it is an example that execute the update method of the blogserver :

$ curl -u testuser:foobar -H 'Content-Type:application/json' -X PUT -d '{"content": "Update test", "title": "Update test"}' http://127.0.0.1:8000/api/post/1/



3. Passing data to your handler

In this example I am going to demonstrate how to pass JSON string with curl. In order to do this you should use the -d followed by the JSON string :

$ curl -u testuser:foobar -H 'Content-Type:application/json' -X PUT -d '{"content": "Update test", "title": "Update test"}' http://127.0.0.1:8000/api/post/1/


django-piston will automatically turn this string into a python dictionary that is ready to be used by your handler. You will find this data in 'request.data' the raw string is available in "request.raw_post_data"

4. How to pass parameter to your handler


It took me a while to understand that the same handler can be mounted to several urls this will allow you to pass additional parameters like the primary key ID or the slug :

....
blogposts = Resource(handler=BlogpostHandler, authentication=auth)
urlpatterns = patterns('',
url(r'^posts/$', blogposts),
url(r'^post/(?P.+)/$', blogposts),
.....


In the example above "blockposts" is mapped to 2 different urls, the second one will be particularly handy to read, update, delete a particular post. Additional parameters from the URL will be passed to the method : read, create, update, delete. I encourage you to check out the code from bitbucket to see how you could take advantage of this technique.

This post barely scratch the surface of how to use django-piston, to put it in a nutshell this reusable application makes creating a web api for your own project simple. It avoids you to write a lot of boilerplate code because it abstracts all the machinery and let you focus on important things.

I would be glad to hear from you how you use it and what is your favorite trick.