pyjamaswithdjangojsonrpc

gitfoxi edited this page Jun 16, 2012 · 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