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