|
1 # jsonrpc.py |
|
2 # original code: http://trac.pyworks.org/pyjamas/wiki/DjangoWithPyJamas |
|
3 # also from: http://www.pimentech.fr/technologies/outils |
|
4 from django.utils import simplejson |
|
5 from django.http import HttpResponse |
|
6 import sys |
|
7 |
|
8 from pyjs.jsonrpc import JSONRPCServiceBase |
|
9 # JSONRPCService and jsonremote are used in combination to drastically |
|
10 # simplify the provision of JSONRPC services. use as follows: |
|
11 # |
|
12 # jsonservice = JSONRPCService() |
|
13 # |
|
14 # @jsonremote(jsonservice) |
|
15 # def test(request, echo_param): |
|
16 # return "echoing the param back: %s" % echo_param |
|
17 # |
|
18 # dump jsonservice into urlpatterns: |
|
19 # (r'^service1/$', 'djangoapp.views.jsonservice'), |
|
20 |
|
21 class JSONRPCService(JSONRPCServiceBase): |
|
22 |
|
23 def __call__(self, request, extra=None): |
|
24 return self.process(request.raw_post_data) |
|
25 |
|
26 def jsonremote(service): |
|
27 """Make JSONRPCService a decorator so that you can write : |
|
28 |
|
29 from jsonrpc import JSONRPCService |
|
30 chatservice = JSONRPCService() |
|
31 |
|
32 @jsonremote(chatservice) |
|
33 def login(request, user_name): |
|
34 (...) |
|
35 """ |
|
36 def remotify(func): |
|
37 if isinstance(service, JSONRPCService): |
|
38 service.add_method(func.__name__, func) |
|
39 else: |
|
40 emsg = 'Service "%s" not found' % str(service.__name__) |
|
41 raise NotImplementedError, emsg |
|
42 return func |
|
43 return remotify |
|
44 |
|
45 |
|
46 # FormProcessor provides a mechanism for turning Django Forms into JSONRPC |
|
47 # Services. If you have an existing Django app which makes prevalent |
|
48 # use of Django Forms it will save you rewriting the app. |
|
49 # use as follows. in djangoapp/views.py : |
|
50 # |
|
51 # class SimpleForm(forms.Form): |
|
52 # testfield = forms.CharField(max_length=100) |
|
53 # |
|
54 # class SimpleForm2(forms.Form): |
|
55 # testfield = forms.CharField(max_length=20) |
|
56 # |
|
57 # processor = FormProcessor({'processsimpleform': SimpleForm, |
|
58 # 'processsimpleform2': SimpleForm2}) |
|
59 # |
|
60 # this will result in a JSONRPC service being created with two |
|
61 # RPC functions. dump "processor" into urlpatterns to make it |
|
62 # part of the app: |
|
63 # (r'^formsservice/$', 'djangoapp.views.processor'), |
|
64 |
|
65 from django import forms |
|
66 |
|
67 def builderrors(form): |
|
68 d = {} |
|
69 for error in form.errors.keys(): |
|
70 if error not in d: |
|
71 d[error] = [] |
|
72 for errorval in form.errors[error]: |
|
73 d[error].append(unicode(errorval)) |
|
74 return d |
|
75 |
|
76 |
|
77 # contains the list of arguments in each field |
|
78 field_names = { |
|
79 'CharField': ['max_length', 'min_length'], |
|
80 'IntegerField': ['max_value', 'min_value'], |
|
81 'FloatField': ['max_value', 'min_value'], |
|
82 'DecimalField': ['max_value', 'min_value', 'max_digits', 'decimal_places'], |
|
83 'DateField': ['input_formats'], |
|
84 'DateTimeField': ['input_formats'], |
|
85 'TimeField': ['input_formats'], |
|
86 'RegexField': ['max_length', 'min_length'], # sadly we can't get the expr |
|
87 'EmailField': ['max_length', 'min_length'], |
|
88 'URLField': ['max_length', 'min_length', 'verify_exists', 'user_agent'], |
|
89 'ChoiceField': ['choices'], |
|
90 'FilePathField': ['path', 'match', 'recursive', 'choices'], |
|
91 'IPAddressField': ['max_length', 'min_length'], |
|
92 } |
|
93 |
|
94 def describe_field_errors(field): |
|
95 res = {} |
|
96 field_type = field.__class__.__name__ |
|
97 msgs = {} |
|
98 for n, m in field.error_messages.items(): |
|
99 msgs[n] = unicode(m) |
|
100 res['error_messages'] = msgs |
|
101 if field_type in ['ComboField', 'MultiValueField', 'SplitDateTimeField']: |
|
102 res['fields'] = map(describe_field, field.fields) |
|
103 return res |
|
104 |
|
105 def describe_fields_errors(fields, field_names): |
|
106 res = {} |
|
107 if not field_names: |
|
108 field_names = fields.keys() |
|
109 for name in field_names: |
|
110 field = fields[name] |
|
111 res[name] = describe_field_errors(field) |
|
112 return res |
|
113 |
|
114 def describe_field(field): |
|
115 res = {} |
|
116 field_type = field.__class__.__name__ |
|
117 for fname in field_names.get(field_type, []) + \ |
|
118 ['help_text', 'label', 'initial', 'required']: |
|
119 res[fname] = getattr(field, fname) |
|
120 if field_type in ['ComboField', 'MultiValueField', 'SplitDateTimeField']: |
|
121 res['fields'] = map(describe_field, field.fields) |
|
122 return res |
|
123 |
|
124 def describe_fields(fields, field_names): |
|
125 res = {} |
|
126 if not field_names: |
|
127 field_names = fields.keys() |
|
128 for name in field_names: |
|
129 field = fields[name] |
|
130 res[name] = describe_field(field) |
|
131 return res |
|
132 |
|
133 class FormProcessor(JSONRPCService): |
|
134 def __init__(self, forms, _formcls=None): |
|
135 |
|
136 if _formcls is None: |
|
137 JSONRPCService.__init__(self) |
|
138 for k in forms.keys(): |
|
139 s = FormProcessor({}, forms[k]) |
|
140 self.add_method(k, s.__process) |
|
141 else: |
|
142 JSONRPCService.__init__(self, forms) |
|
143 self.formcls = _formcls |
|
144 |
|
145 def __process(self, request, params, command=None): |
|
146 |
|
147 f = self.formcls(params) |
|
148 |
|
149 if command is None: # just validate |
|
150 if not f.is_valid(): |
|
151 return {'success':False, 'errors': builderrors(f)} |
|
152 return {'success':True} |
|
153 |
|
154 elif command.has_key('describe_errors'): |
|
155 field_names = command['describe_errors'] |
|
156 return describe_fields_errors(f.fields, field_names) |
|
157 |
|
158 elif command.has_key('describe'): |
|
159 field_names = command['describe'] |
|
160 return describe_fields(f.fields, field_names) |
|
161 |
|
162 elif command.has_key('save'): |
|
163 if not f.is_valid(): |
|
164 return {'success':False, 'errors': builderrors(f)} |
|
165 instance = f.save() # XXX: if you want more, over-ride save. |
|
166 return {'success': True, 'instance': json_convert(instance) } |
|
167 |
|
168 elif command.has_key('html'): |
|
169 return {'success': True, 'html': f.as_table()} |
|
170 |
|
171 return "unrecognised command" |
|
172 |
|
173 |
|
174 |
|
175 |
|
176 # The following is incredibly convenient for saving vast amounts of |
|
177 # coding, avoiding doing silly things like this: |
|
178 # jsonresult = {'field1': djangoobject.field1, |
|
179 # 'field2': djangoobject.date.strftime('%Y.%M'), |
|
180 # ..... } |
|
181 # |
|
182 # The date/time flatten function is there because JSONRPC doesn't |
|
183 # support date/time objects or formats, so conversion to a string |
|
184 # is the most logical choice. pyjamas, being python, can easily |
|
185 # be used to parse the string result at the other end. |
|
186 # |
|
187 # use as follows: |
|
188 # |
|
189 # jsonservice = JSONRPCService() |
|
190 # |
|
191 # @jsonremote(jsonservice) |
|
192 # def list_some_model(request, start=0, count=10): |
|
193 # l = SomeDjangoModelClass.objects.filter() |
|
194 # res = json_convert(l[start:end]) |
|
195 # |
|
196 # @jsonremote(jsonservice) |
|
197 # def list_another_model(request, start=0, count=10): |
|
198 # l = AnotherDjangoModelClass.objects.filter() |
|
199 # res = json_convert(l[start:end]) |
|
200 # |
|
201 # dump jsonservice into urlpatterns to make the two RPC functions, |
|
202 # list_some_model and list_another_model part of the django app: |
|
203 # (r'^service1/$', 'djangoapp.views.jsonservice'), |
|
204 |
|
205 from django.core.serializers import serialize |
|
206 import datetime |
|
207 from datetime import date |
|
208 |
|
209 def dict_datetimeflatten(item): |
|
210 d = {} |
|
211 for k, v in item.items(): |
|
212 k = str(k) |
|
213 if isinstance(v, datetime.date): |
|
214 d[k] = str(v) |
|
215 elif isinstance(v, dict): |
|
216 d[k] = dict_datetimeflatten(v) |
|
217 else: |
|
218 d[k] = v |
|
219 return d |
|
220 |
|
221 def json_convert(l, fields=None): |
|
222 res = [] |
|
223 for item in serialize('python', l, fields=fields): |
|
224 res.append(dict_datetimeflatten(item)) |
|
225 return res |
|
226 |