laurent@371: # jsonrpc.py laurent@371: # original code: http://trac.pyworks.org/pyjamas/wiki/DjangoWithPyJamas laurent@371: # also from: http://www.pimentech.fr/technologies/outils andrej@1783: import datetime andrej@1850: andrej@1783: from django.core.serializers import serialize andrej@1783: laurent@371: laurent@371: from pyjs.jsonrpc import JSONRPCServiceBase laurent@371: # JSONRPCService and jsonremote are used in combination to drastically laurent@371: # simplify the provision of JSONRPC services. use as follows: laurent@371: # laurent@371: # jsonservice = JSONRPCService() laurent@371: # laurent@371: # @jsonremote(jsonservice) laurent@371: # def test(request, echo_param): laurent@371: # return "echoing the param back: %s" % echo_param laurent@371: # laurent@371: # dump jsonservice into urlpatterns: laurent@371: # (r'^service1/$', 'djangoapp.views.jsonservice'), laurent@371: andrej@1736: laurent@371: class JSONRPCService(JSONRPCServiceBase): andrej@1730: laurent@371: def __call__(self, request, extra=None): laurent@371: return self.process(request.raw_post_data) laurent@371: andrej@1736: laurent@371: def jsonremote(service): laurent@371: """Make JSONRPCService a decorator so that you can write : andrej@1730: laurent@371: from jsonrpc import JSONRPCService laurent@371: chatservice = JSONRPCService() laurent@371: laurent@371: @jsonremote(chatservice) laurent@371: def login(request, user_name): laurent@371: (...) laurent@371: """ laurent@371: def remotify(func): laurent@371: if isinstance(service, JSONRPCService): laurent@371: service.add_method(func.__name__, func) laurent@371: else: laurent@371: emsg = 'Service "%s" not found' % str(service.__name__) andrej@1765: raise NotImplementedError(emsg) laurent@371: return func laurent@371: return remotify laurent@371: laurent@371: laurent@371: # FormProcessor provides a mechanism for turning Django Forms into JSONRPC laurent@371: # Services. If you have an existing Django app which makes prevalent laurent@371: # use of Django Forms it will save you rewriting the app. laurent@371: # use as follows. in djangoapp/views.py : laurent@371: # laurent@371: # class SimpleForm(forms.Form): laurent@371: # testfield = forms.CharField(max_length=100) laurent@371: # laurent@371: # class SimpleForm2(forms.Form): laurent@371: # testfield = forms.CharField(max_length=20) laurent@371: # laurent@371: # processor = FormProcessor({'processsimpleform': SimpleForm, laurent@371: # 'processsimpleform2': SimpleForm2}) laurent@371: # laurent@371: # this will result in a JSONRPC service being created with two laurent@371: # RPC functions. dump "processor" into urlpatterns to make it laurent@371: # part of the app: laurent@371: # (r'^formsservice/$', 'djangoapp.views.processor'), laurent@371: andrej@1736: laurent@371: def builderrors(form): laurent@371: d = {} laurent@371: for error in form.errors.keys(): laurent@371: if error not in d: laurent@371: d[error] = [] laurent@371: for errorval in form.errors[error]: laurent@371: d[error].append(unicode(errorval)) laurent@371: return d laurent@371: laurent@371: laurent@371: # contains the list of arguments in each field laurent@371: field_names = { laurent@371: 'CharField': ['max_length', 'min_length'], laurent@371: 'IntegerField': ['max_value', 'min_value'], laurent@371: 'FloatField': ['max_value', 'min_value'], laurent@371: 'DecimalField': ['max_value', 'min_value', 'max_digits', 'decimal_places'], laurent@371: 'DateField': ['input_formats'], laurent@371: 'DateTimeField': ['input_formats'], laurent@371: 'TimeField': ['input_formats'], andrej@1737: 'RegexField': ['max_length', 'min_length'], # sadly we can't get the expr laurent@371: 'EmailField': ['max_length', 'min_length'], laurent@371: 'URLField': ['max_length', 'min_length', 'verify_exists', 'user_agent'], laurent@371: 'ChoiceField': ['choices'], laurent@371: 'FilePathField': ['path', 'match', 'recursive', 'choices'], laurent@371: 'IPAddressField': ['max_length', 'min_length'], laurent@371: } laurent@371: andrej@1736: laurent@371: def describe_field_errors(field): laurent@371: res = {} laurent@371: field_type = field.__class__.__name__ laurent@371: msgs = {} laurent@371: for n, m in field.error_messages.items(): laurent@371: msgs[n] = unicode(m) laurent@371: res['error_messages'] = msgs laurent@371: if field_type in ['ComboField', 'MultiValueField', 'SplitDateTimeField']: laurent@371: res['fields'] = map(describe_field, field.fields) laurent@371: return res laurent@371: andrej@1736: laurent@371: def describe_fields_errors(fields, field_names): laurent@371: res = {} laurent@371: if not field_names: laurent@371: field_names = fields.keys() laurent@371: for name in field_names: laurent@371: field = fields[name] laurent@371: res[name] = describe_field_errors(field) laurent@371: return res laurent@371: andrej@1736: laurent@371: def describe_field(field): laurent@371: res = {} laurent@371: field_type = field.__class__.__name__ andrej@1769: for fname in (field_names.get(field_type, []) + andrej@1769: ['help_text', 'label', 'initial', 'required']): laurent@371: res[fname] = getattr(field, fname) laurent@371: if field_type in ['ComboField', 'MultiValueField', 'SplitDateTimeField']: laurent@371: res['fields'] = map(describe_field, field.fields) laurent@371: return res laurent@371: andrej@1736: laurent@371: def describe_fields(fields, field_names): laurent@371: res = {} laurent@371: if not field_names: laurent@371: field_names = fields.keys() laurent@371: for name in field_names: laurent@371: field = fields[name] laurent@371: res[name] = describe_field(field) laurent@371: return res laurent@371: andrej@1736: laurent@371: class FormProcessor(JSONRPCService): laurent@371: def __init__(self, forms, _formcls=None): laurent@371: laurent@371: if _formcls is None: laurent@371: JSONRPCService.__init__(self) laurent@371: for k in forms.keys(): andrej@1754: s = FormProcessor({}, forms[k]) laurent@371: self.add_method(k, s.__process) laurent@371: else: laurent@371: JSONRPCService.__init__(self, forms) laurent@371: self.formcls = _formcls laurent@371: laurent@371: def __process(self, request, params, command=None): laurent@371: laurent@371: f = self.formcls(params) laurent@371: andrej@1737: if command is None: # just validate laurent@371: if not f.is_valid(): andrej@1740: return {'success': False, 'errors': builderrors(f)} andrej@1740: return {'success': True} laurent@371: andrej@1763: elif 'describe_errors' in command: laurent@371: field_names = command['describe_errors'] laurent@371: return describe_fields_errors(f.fields, field_names) laurent@371: andrej@1763: elif 'describe' in command: laurent@371: field_names = command['describe'] laurent@371: return describe_fields(f.fields, field_names) laurent@371: andrej@1763: elif 'save' in command: laurent@371: if not f.is_valid(): andrej@1740: return {'success': False, 'errors': builderrors(f)} andrej@1737: instance = f.save() # XXX: if you want more, over-ride save. andrej@1746: return {'success': True, 'instance': json_convert(instance)} laurent@371: andrej@1763: elif 'html' in command: laurent@371: return {'success': True, 'html': f.as_table()} laurent@371: laurent@371: return "unrecognised command" laurent@371: laurent@371: # The following is incredibly convenient for saving vast amounts of laurent@371: # coding, avoiding doing silly things like this: laurent@371: # jsonresult = {'field1': djangoobject.field1, laurent@371: # 'field2': djangoobject.date.strftime('%Y.%M'), laurent@371: # ..... } laurent@371: # laurent@371: # The date/time flatten function is there because JSONRPC doesn't laurent@371: # support date/time objects or formats, so conversion to a string laurent@371: # is the most logical choice. pyjamas, being python, can easily laurent@371: # be used to parse the string result at the other end. laurent@371: # laurent@371: # use as follows: laurent@371: # laurent@371: # jsonservice = JSONRPCService() laurent@371: # laurent@371: # @jsonremote(jsonservice) laurent@371: # def list_some_model(request, start=0, count=10): laurent@371: # l = SomeDjangoModelClass.objects.filter() laurent@371: # res = json_convert(l[start:end]) laurent@371: # laurent@371: # @jsonremote(jsonservice) laurent@371: # def list_another_model(request, start=0, count=10): laurent@371: # l = AnotherDjangoModelClass.objects.filter() laurent@371: # res = json_convert(l[start:end]) laurent@371: # laurent@371: # dump jsonservice into urlpatterns to make the two RPC functions, laurent@371: # list_some_model and list_another_model part of the django app: laurent@371: # (r'^service1/$', 'djangoapp.views.jsonservice'), laurent@371: andrej@1749: laurent@371: def dict_datetimeflatten(item): laurent@371: d = {} laurent@371: for k, v in item.items(): laurent@371: k = str(k) laurent@371: if isinstance(v, datetime.date): laurent@371: d[k] = str(v) laurent@371: elif isinstance(v, dict): laurent@371: d[k] = dict_datetimeflatten(v) laurent@371: else: laurent@371: d[k] = v laurent@371: return d laurent@371: andrej@1736: laurent@371: def json_convert(l, fields=None): laurent@371: res = [] laurent@371: for item in serialize('python', l, fields=fields): laurent@371: res.append(dict_datetimeflatten(item)) laurent@371: return res