Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

pyjamaswithdjangojsonrpc

gitfoxi edited this page · 1 revision

Pyjamas with Django JSON-RPC

Some suggestions on using Pyjamas and Django together.

Please note that the use of the jsonrpc.py file in the standard pyjamas distribution is, for django, much much simpler. (lkcl)

Way 1

Using Pyjamas with Django is very straightforward, this is how I did it.

Pyjamas code for the UI:

    from ui import RootPanel, TextArea, Label, Button, HTML, VerticalPanel
    from JSONService import JSONProxy

    class DjangoTest:
        def onModuleLoad(self):
        self.TEXT_WAITING = "Waiting for response..."
        self.TEXT_ERROR = "Server Error"

        self.remote = EchoService()

        self.status=Label()
        self.text_area = TextArea()
        self.button = Button("Send text to Echo Service", self)
        self.button2 = Button("Send text to Echo Service 2", self)

        panel = VerticalPanel()
        panel.add(HTML("<b>JSON-RPC Example</b>"))
        panel.add(self.text_area)
        panel.add(self.button)
        panel.add(self.button2)        
        panel.add(self.status)

        RootPanel().add(panel)

        def onClick(self, sender):
        self.status.setText(self.TEXT_WAITING)

        if sender == self.button:         
            id = self.remote.echo(self.text_area.getText(), self)
        else:
            id = self.remote.echo2(self.text_area.getText(), self)            
        if id<0:
            self.status.setText(self.TEXT_ERROR)

        def onRemoteResponse(self, response, request_info):
        self.status.setText(response)

        def onRemoteError(self, code, message, request_info):
        self.status.setText("Server Error or Invalid Response: ERROR " + code + " - " + message)

    class EchoService(JSONProxy):
        def __init__(self):
        JSONProxy.__init__(self, "/gtd/test-jsonrpc/", ["echo", "echo2"])

This code is highly derived from JSONRPCExample.py.

In appropriate urls.py I added:

        ('test-jsonrpc/', 'test_jsonrpc'),

The view maps to an instance of my JSONRPCService class, lets first see how the view is created and initialized [in the appropriate view.py, I have the following]:

    test_jsonrpc = JSONRPCService()

    def echo(d): return d.upper()
    def echo2(d): return d.lower()

    test_jsonrpc.add_method("echo", echo)
    test_jsonrpc.add_method("echo2", echo2)

Finally the JSONRPCService class I wrote:

    from django.utils import simplejson
    from django.http import HttpResponse

    class JSONRPCService:
        def __init__(self, method_map={}):
        self.method_map = method_map

        def add_method(self, name, method):
        self.method_map[name] = method

        def __call__(self, request, extra=None):
        #assert extra == None # we do not yet support GET requests, something pyjams do not use anyways.
        data = simplejson.loads(request.raw_post_data)
        id, method, params = data["id"], data["method"], data["params"]
        if method in self.method_map:
            result = self.method_map[method](*params)
            return HttpResponse(simplejson.dumps({'id': id, 'result': result}))
        else:
            return HttpResponse(simplejson.dumps({'id': id, 'error': "No such method", 'code': -1}))

The code here is licensed under BSD if you care to use. Happy pyjaming django!

-- Amit Upadhyay

Another Way

Dobes Vandermeer:

Here's how I did it. I created json service wrapper class like so:

    from django.db import models
    from django.core.serializers import base
    from django.db import models
    from django.core.serializers.json import DateTimeAwareJSONEncoder
    import sys
    from traceback import print_exc
    from django.utils.simplejson.decoder import JSONDecoder
    from django.http import HttpResponse

    def jsonmethod(f):
       """
          A simple proxy used to indicate that the given method is in fact
          available to json clients.
       """
       f.json_public = True
       return f

    class MySerializer(base.Serializer):
        """
        Serializes a QuerySet to basic Python objects.
        """

        def start_serialization(self):
        self._current = None
        self.objects = []

        def end_serialization(self):
        pass

        def start_object(self, obj):
        self._current = {
            "model"  : str(obj._meta),
            "pk"     : str(obj._get_pk_val()),
        }

        def end_object(self, obj):
        self.objects.append(self._current)
        self._current = None

        def handle_field(self, obj, field):
        if isinstance(field, models.FileField) or isinstance(field, models.DateTimeField):
           self._current[field.name] = self.get_string_value(obj, field)
        else:
           self._current[field.name] = getattr(obj, field.name)

        def handle_fk_field(self, obj, field):
        related = getattr(obj, field.name)
        if related is not None:
            related = related._get_pk_val()
        self._current[field.name] = related

        def handle_m2m_field(self, obj, field):
        self._current[field.name] = [related._get_pk_val() for related in getattr(obj, field.name).iterator()]

        def getvalue(self):
        return self.objects[0]

    class DjangoAwareJSONEncoder(DateTimeAwareJSONEncoder):
       def default(self, o):
          if isinstance(o, models.Model):
         return MySerializer().serialize([o])
          else:
         return super(DjangoAwareJSONEncoder, self).default(o)


    class JSONService(object):   
       """
          Wrapper for a view func to indicate that it should de-serialize any
          POST json inputs and serialize the return value into a json result.

          Be sure to define a getMethodImplementation() or a put @jsonmethod
          in front of each public method.
       """

       class Error(Exception):
          """
         Raise this exception type to return an error to the client from
         an rpc handling method
          """
          def __init__(self, code=0, message='error'):
         self.code = code
         self.message = message

          def asDict(self):
         return {"code":self.code, "message":self.message}

       def getMethodImplementation(self, name):
          """
         Get the implementation of a method by method name._
          """

          try:
         f = getattr(self, name)
          except AttributeError:
         return None
          try:
         if f.im_func.json_public:
            return f
          except AttributeError:
         pass
          try:
         if f.json_public:
            return f
          except AttributeError:
         pass
          return None

       def getAvailableMethods(self):
          return self.__class__.__json_rpc_methods__

       def makeResponse(self, id, result, code=0, error=None):
          if error or code: error = {"code":code, "message":error}
          return {"result":result, "id":id, "error":error}

       def __call__(self, request, *args, **kwargs):

          if len(request.POST):
         rpcRequest = JSONDecoder().decode(request.raw_post_data)
          else:
         rpcRequest = dict(request.GET)
         try: rpcRequest['params'] = JSONDecoder().decode(request['params'])
         except KeyError: pass


          #print 'attempting call ... ', rpcRequest

          id = None
          params = None
          errorCode = 0
          try:
         method = rpcRequest["method"]
          except KeyError:  
         result = None       
         error = "Invalid JSON-RPC request; method must be specified: %r"%(rpcRequest) # Not a JSON request
          else:
         try: params = rpcRequest["params"]
         except KeyError: params = None
         try: id = rpcRequest["id"]
         except KeyError: id = None
         try: mode = request["mode"]
         except KeyError: mode = "json"
         try:
            f = self.getMethodImplementation(method)
            if f:
               if params is not None: args = list(args).extend(params)
               try: func = f.im_func
               except AttributeError: func = f
               argnames = func.func_code.co_varnames[len(params):func.func_code.co_argcount]
               for var in ("request", "rpcRequest", "id", "method"):
              if var in argnames:
                 kwargs[var] = locals()[var]
               result = f(*params, **kwargs)
               error = None
            else:
               result = None
               error = "No such method %r; available methods are %r"%(method, self.getAvailableMethods())

         except JSONService.Error, x:
            errorCode = x.code
            error = x.message
            result = None
         except:
         #   stackTrace = StringIO()
            print 'exception raised'
            sys.stdout.flush()
            print_exc()
            sys.stderr.flush()
            raise

          #   error = stackTrace.getvalue()
          #   result = None

          response = self.makeResponse(id, result, errorCode, error)
          #print 'response', response
          json = DjangoAwareJSONEncoder(ensure_ascii=False).iterencode(response)
          if mode == "json":
         return HttpResponse(json, mimetype='application/json')
          elif mode == "text":
         return HttpResponse(json, mimetype='text/plain')

Instead of using "views.py" in the app, I create a file service.py:

BUG AM 2010-08-05 It has been pointed out that the login() method is recursive and unlikely to work. Patches welcome to pyjamasdev@pyjs.org

    from habitsoft.login.models import Invitation
    from habitsoft.login.models import EmailChange
    from datetime import datetime
    import socket
    from django.conf import settings
    from django.core.mail import send_mail
    from django.template.context import Context
    from django.template.loader import get_template
    from django.core.exceptions import ObjectDoesNotExist
    from habitsoft.login.models import Account
    from random import getrandbits
    from django.contrib.auth import logout
    from django.contrib.auth import login
    from django.contrib.auth import authenticate
    from habitsoft.webframework.json import JSONService, jsonmethod

    class LoginService(JSONService):
        def __init__(self):
        pass

        @jsonmethod
        def login(self, username, password, request):
        user = authenticate(username=username, password=password)
        if user is not None:
            login(request, user)
            return True
        else:
            return False

        @jsonmethod
        def logout(self, request):
        logout(request)

        @jsonmethod
        def getProfile(self, request):    
        account = request.user
        return {"first_name":account.first_name,
            "last_name":account.last_name,
            "country_code":account.country_code}

        @jsonmethod
        def updateProfile(self, first, last, country_code, request):
        account = request.user
        account.first_name = first
        account.last_name = last
        account.country_code = country_code
        account.save()

        @jsonmethod
        def changePassword(self, oldPassword, newPassword, request):
        account = request.user
        if account.check_password(oldPassword):
            account.set_password(newPassword)
            account.save()
            return True
        return False

I created another file in the project, next to settings.py, called services.py like this:

    from mysite.login.service import LoginService
    from django.conf.urls.defaults import *

    login = LoginService()

    services = ('login',)


    urlpatterns = patterns('habitsoft.services',
                   *[('^%s/$'%name, name) for name in services]
                   )

In urls.py:

    from django.conf.urls.defaults import patterns
    from django.conf.urls.defaults import include

    urlpatterns = patterns('',
        # ... 
        (r'^services/', include('habitsoft.services')),
        # ...
    )

Sample usage in a pyjamas file:

    class RemoteLoginService(JSONProxy):
         def __init__(self):
          JSONProxy.__init__(self, "services/login/", 
                     ["login", 
                      "logout", 
                      "signUp", 
                      "activate",
                      "getProfile",
                      "updateProfile",
                      "changePassword",
                      "changeEmail",
                      "confirmEmail"])

    login = RemoteLoginService()

    class JsonHelloButtonHandler:
       def __init__(self, textArea):
          self.textArea = textArea

       def onClick(self, sender):
          self.textArea.setHTML("sending request...")
          login.login("user", "pass")

       def onRemoteResponse(self, response, request_info):
          self.textArea.setHTML("response: "+response)

       def onRemoteError(self, code, message, request_info):
          self.textArea.setHTML("error: "+message)


    class FrontPage:
       def __init__(self):
          self.testText = "test"

       def greet(self, sender):
          Window.alert("Hello, AJAX!  self = "+self+" prototype = "+self.prototype)

       def onModuleLoad(self):
          root = RootPanel()
          root.add(Button("Alert", self.greet))
          self.textArea = HTML()
          root.add(Button("JSON", JsonHelloButtonHandler(self.textArea)))
          root.add(self.textArea)
          self.textArea.setHTML("This is a test.")

       def toString(self):
          return "FrontPage instance"

Feel free to use this for any purpose.

See also

External Links

Related discussions

Something went wrong with that request. Please try again.