176 try: |
210 try: |
177 eid = err.entity.eid |
211 eid = err.entity.eid |
178 except AttributeError: |
212 except AttributeError: |
179 eid = err.entity |
213 eid = err.entity |
180 return (False, (eid, err.errors)) |
214 return (False, (eid, err.errors)) |
181 |
215 |
182 def xmlize(source): |
|
183 head = u'<?xml version="1.0"?>\n' + STRICT_DOCTYPE % CW_XHTML_EXTENSIONS |
|
184 return head + u'<div xmlns="http://www.w3.org/1999/xhtml" xmlns:cubicweb="http://www.logilab.org/2008/cubicweb">%s</div>' % source.strip() |
|
185 |
|
186 def jsonize(func): |
|
187 """sets correct content_type and calls `simplejson.dumps` on results |
|
188 """ |
|
189 def wrapper(self, *args, **kwargs): |
|
190 self.req.set_content_type('application/json') |
|
191 result = func(self, *args, **kwargs) |
|
192 return simplejson.dumps(result) |
|
193 return wrapper |
|
194 |
|
195 |
|
196 def check_pageid(func): |
|
197 """decorator which checks the given pageid is found in the |
|
198 user's session data |
|
199 """ |
|
200 def wrapper(self, *args, **kwargs): |
|
201 data = self.req.get_session_data(self.req.pageid) |
|
202 if data is None: |
|
203 raise RemoteCallFailed(self.req._('pageid-not-found')) |
|
204 return func(self, *args, **kwargs) |
|
205 return wrapper |
|
206 |
|
207 |
216 |
208 class JSonController(Controller): |
217 class JSonController(Controller): |
209 id = 'json' |
218 id = 'json' |
210 template = 'main' |
219 |
211 |
220 def publish(self, rset=None): |
212 def publish(self, rset=None): |
221 """call js_* methods. Expected form keys: |
213 mode = self.req.form.get('mode', 'html') |
222 |
|
223 :fname: the method name without the js_ prefix |
|
224 :args: arguments list (json) |
|
225 |
|
226 note: it's the responsability of js_* methods to set the correct |
|
227 response content type |
|
228 """ |
214 self.req.pageid = self.req.form.get('pageid') |
229 self.req.pageid = self.req.form.get('pageid') |
215 try: |
230 fname = self.req.form['fname'] |
216 func = getattr(self, '%s_exec' % mode) |
231 try: |
217 except AttributeError, ex: |
232 func = getattr(self, 'js_%s' % fname) |
218 self.error('json controller got an unknown mode %r', mode) |
233 except AttributeError: |
219 self.error('\t%s', ex) |
234 raise RemoteCallFailed('no %s method' % fname) |
220 result = u'' |
235 # no <arg> attribute means the callback takes no argument |
221 else: |
236 args = self.req.form.get('arg', ()) |
222 try: |
237 if not isinstance(args, (list, tuple)): |
223 result = func(rset) |
238 args = (args,) |
224 except RemoteCallFailed: |
239 args = [simplejson.loads(arg) for arg in args] |
225 raise |
240 try: |
226 except Exception, ex: |
241 result = func(*args) |
227 self.exception('an exception occured on json request(rset=%s): %s', |
242 except RemoteCallFailed: |
228 rset, ex) |
243 raise |
229 raise RemoteCallFailed(repr(ex)) |
244 except Exception, ex: |
230 return result.encode(self.req.encoding) |
245 self.exception('an exception occured while calling js_%s(%s): %s', |
|
246 fname, args, ex) |
|
247 raise RemoteCallFailed(repr(ex)) |
|
248 if result is None: |
|
249 return '' |
|
250 # get unicode on @htmlize methods, encoded string on @jsonize methods |
|
251 elif isinstance(result, unicode): |
|
252 return result.encode(self.req.encoding) |
|
253 return result |
|
254 |
|
255 def _rebuild_posted_form(self, names, values, action=None): |
|
256 form = {} |
|
257 for name, value in zip(names, values): |
|
258 # remove possible __action_xxx inputs |
|
259 if name.startswith('__action'): |
|
260 continue |
|
261 # form.setdefault(name, []).append(value) |
|
262 if name in form: |
|
263 curvalue = form[name] |
|
264 if isinstance(curvalue, list): |
|
265 curvalue.append(value) |
|
266 else: |
|
267 form[name] = [curvalue, value] |
|
268 else: |
|
269 form[name] = value |
|
270 # simulate click on __action_%s button to help the controller |
|
271 if action: |
|
272 form['__action_%s' % action] = u'whatever' |
|
273 return form |
231 |
274 |
232 def _exec(self, rql, args=None, eidkey=None, rocheck=True): |
275 def _exec(self, rql, args=None, eidkey=None, rocheck=True): |
233 """json mode: execute RQL and return resultset as json""" |
276 """json mode: execute RQL and return resultset as json""" |
234 if rocheck: |
277 if rocheck: |
235 self.ensure_ro_rql(rql) |
278 self.ensure_ro_rql(rql) |
290 stream.write(u'<div class="ajaxHtmlHead">\n') # XXX use a widget ? |
317 stream.write(u'<div class="ajaxHtmlHead">\n') # XXX use a widget ? |
291 stream.write(extresources) |
318 stream.write(extresources) |
292 stream.write(u'</div>\n') |
319 stream.write(u'</div>\n') |
293 if req.form.get('paginate') and divid == 'pageContent': |
320 if req.form.get('paginate') and divid == 'pageContent': |
294 stream.write(u'</div></div>') |
321 stream.write(u'</div></div>') |
295 source = stream.getvalue() |
322 return stream.getvalue() |
296 return self._set_content_type(view, source) |
323 |
297 |
324 @xhtmlize |
298 def rawremote_exec(self, rset=None): |
325 def js_prop_widget(self, propkey, varname, tabindex=None): |
299 """like remote_exec but doesn't change content type""" |
326 """specific method for CWProperty handling""" |
300 # no <arg> attribute means the callback takes no argument |
327 entity = self.vreg.etype_class('CWProperty')(self.req, None, None) |
301 args = self.req.form.get('arg', ()) |
328 entity.eid = varname |
302 if not isinstance(args, (list, tuple)): |
329 entity['pkey'] = propkey |
303 args = (args,) |
330 form = self.vreg.select_object('forms', 'edition', self.req, None, |
304 fname = self.req.form['fname'] |
331 entity=entity) |
305 args = [simplejson.loads(arg) for arg in args] |
332 form.form_build_context() |
306 try: |
333 vfield = form.field_by_name('value') |
307 func = getattr(self, 'js_%s' % fname) |
334 renderer = FormRenderer() |
308 except AttributeError: |
335 return vfield.render(form, renderer, tabindex=tabindex) \ |
309 self.exception('rawremote_exec fname=%s', fname) |
336 + renderer.render_help(form, vfield) |
310 return u"" |
337 |
311 return func(*args) |
338 @xhtmlize |
312 |
339 def js_component(self, compid, rql, registry='components', extraargs=None): |
313 remote_exec = jsonize(rawremote_exec) |
340 if rql: |
314 |
341 rset = self._exec(rql) |
315 def _rebuild_posted_form(self, names, values, action=None): |
342 else: |
316 form = {} |
343 rset = None |
317 for name, value in zip(names, values): |
344 comp = self.vreg.select_object(registry, compid, self.req, rset) |
318 # remove possible __action_xxx inputs |
345 if extraargs is None: |
319 if name.startswith('__action'): |
346 extraargs = {} |
320 continue |
347 else: # we receive unicode keys which is not supported by the **syntax |
321 # form.setdefault(name, []).append(value) |
348 extraargs = dict((str(key), value) |
322 if name in form: |
349 for key, value in extraargs.items()) |
323 curvalue = form[name] |
350 extraargs = extraargs or {} |
324 if isinstance(curvalue, list): |
351 return comp.dispatch(**extraargs) |
325 curvalue.append(value) |
352 |
326 else: |
353 @check_pageid |
327 form[name] = [curvalue, value] |
354 @xhtmlize |
328 else: |
355 def js_inline_creation_form(self, peid, ttype, rtype, role): |
329 form[name] = value |
356 view = self.vreg.select_view('inline-creation', self.req, None, |
330 # simulate click on __action_%s button to help the controller |
357 etype=ttype, peid=peid, rtype=rtype, |
331 if action: |
358 role=role) |
332 form['__action_%s' % action] = u'whatever' |
359 return view.dispatch(etype=ttype, peid=peid, rtype=rtype, role=role) |
333 return form |
360 |
334 |
361 @jsonize |
335 def js_validate_form(self, action, names, values): |
362 def js_validate_form(self, action, names, values): |
336 # XXX this method (and correspoding js calls) should use the new |
363 # XXX this method (and correspoding js calls) should use the new |
337 # `RemoteCallFailed` mechansim |
364 # `RemoteCallFailed` mechansim |
338 self.req.form = self._rebuild_posted_form(names, values, action) |
365 self.req.form = self._rebuild_posted_form(names, values, action) |
339 vreg = self.vreg |
366 vreg = self.vreg |
357 self.req.cnx.rollback() |
384 self.req.cnx.rollback() |
358 self.exception('unexpected error in js_validateform') |
385 self.exception('unexpected error in js_validateform') |
359 return (False, self.req._(str(err))) |
386 return (False, self.req._(str(err))) |
360 return (False, '???') |
387 return (False, '???') |
361 |
388 |
|
389 @jsonize |
362 def js_edit_field(self, action, names, values, rtype, eid): |
390 def js_edit_field(self, action, names, values, rtype, eid): |
363 success, args = self.js_validate_form(action, names, values) |
391 success, args = self.js_validate_form(action, names, values) |
364 if success: |
392 if success: |
365 rset = self.req.execute('Any X,N WHERE X eid %%(x)s, X %s N' % rtype, |
393 rset = self.req.execute('Any X,N WHERE X eid %%(x)s, X %s N' % rtype, |
366 {'x': eid}, 'x') |
394 {'x': eid}, 'x') |
367 entity = rset.get_entity(0, 0) |
395 entity = rset.get_entity(0, 0) |
368 return (success, args, entity.printable_value(rtype)) |
396 return (success, args, entity.printable_value(rtype)) |
369 else: |
397 else: |
370 return (success, args, None) |
398 return (success, args, None) |
371 |
399 |
372 def js_rql(self, rql): |
400 # def js_rql(self, rql): |
373 rset = self._exec(rql) |
401 # rset = self._exec(rql) |
374 return rset and rset.rows or [] |
402 # return rset and rset.rows or [] |
375 |
403 |
|
404 @jsonize |
376 def js_i18n(self, msgids): |
405 def js_i18n(self, msgids): |
377 """returns the translation of `msgid`""" |
406 """returns the translation of `msgid`""" |
378 return [self.req._(msgid) for msgid in msgids] |
407 return [self.req._(msgid) for msgid in msgids] |
379 |
408 |
|
409 @jsonize |
380 def js_format_date(self, strdate): |
410 def js_format_date(self, strdate): |
381 """returns the formatted date for `msgid`""" |
411 """returns the formatted date for `msgid`""" |
382 date = strptime(strdate, '%Y-%m-%d %H:%M:%S') |
412 date = strptime(strdate, '%Y-%m-%d %H:%M:%S') |
383 return self.format_date(date) |
413 return self.format_date(date) |
384 |
414 |
|
415 @jsonize |
385 def js_external_resource(self, resource): |
416 def js_external_resource(self, resource): |
386 """returns the URL of the external resource named `resource`""" |
417 """returns the URL of the external resource named `resource`""" |
387 return self.req.external_resource(resource) |
418 return self.req.external_resource(resource) |
388 |
419 |
389 def js_prop_widget(self, propkey, varname, tabindex=None): |
|
390 """specific method for CWProperty handling""" |
|
391 entity = self.vreg.etype_class('CWProperty')(self.req, None, None) |
|
392 entity.eid = varname |
|
393 entity['pkey'] = propkey |
|
394 form = self.vreg.select_object('forms', 'edition', self.req, None, |
|
395 entity=entity) |
|
396 form.form_build_context() |
|
397 vfield = form.field_by_name('value') |
|
398 renderer = FormRenderer() |
|
399 return vfield.render(form, renderer, tabindex=tabindex) \ |
|
400 + renderer.render_help(form, vfield) |
|
401 |
|
402 def js_component(self, compid, rql, registry='components', extraargs=None): |
|
403 if rql: |
|
404 rset = self._exec(rql) |
|
405 else: |
|
406 rset = None |
|
407 comp = self.vreg.select_object(registry, compid, self.req, rset) |
|
408 if extraargs is None: |
|
409 extraargs = {} |
|
410 else: # we receive unicode keys which is not supported by the **syntax |
|
411 extraargs = dict((str(key), value) |
|
412 for key, value in extraargs.items()) |
|
413 extraargs = extraargs or {} |
|
414 return self._set_content_type(comp, comp.dispatch(**extraargs)) |
|
415 |
|
416 @check_pageid |
420 @check_pageid |
|
421 @jsonize |
417 def js_user_callback(self, cbname): |
422 def js_user_callback(self, cbname): |
418 page_data = self.req.get_session_data(self.req.pageid, {}) |
423 page_data = self.req.get_session_data(self.req.pageid, {}) |
419 try: |
424 try: |
420 cb = page_data[cbname] |
425 cb = page_data[cbname] |
421 except KeyError: |
426 except KeyError: |
422 return None |
427 return None |
423 return cb(self.req) |
428 return cb(self.req) |
424 |
|
425 def js_unregister_user_callback(self, cbname): |
|
426 self.req.unregister_callback(self.req.pageid, cbname) |
|
427 |
|
428 def js_unload_page_data(self): |
|
429 self.req.del_session_data(self.req.pageid) |
|
430 |
|
431 def js_cancel_edition(self, errorurl): |
|
432 """cancelling edition from javascript |
|
433 |
|
434 We need to clear associated req's data : |
|
435 - errorurl |
|
436 - pending insertions / deletions |
|
437 """ |
|
438 self.req.cancel_edition(errorurl) |
|
439 |
|
440 @check_pageid |
|
441 def js_inline_creation_form(self, peid, ttype, rtype, role): |
|
442 view = self.vreg.select_view('inline-creation', self.req, None, |
|
443 etype=ttype, peid=peid, rtype=rtype, |
|
444 role=role) |
|
445 source = view.dispatch(etype=ttype, peid=peid, rtype=rtype, role=role) |
|
446 return self._set_content_type(view, source) |
|
447 |
|
448 def js_remove_pending_insert(self, (eidfrom, rel, eidto)): |
|
449 self._remove_pending(eidfrom, rel, eidto, 'insert') |
|
450 |
|
451 def js_add_pending_insert(self, (eidfrom, rel, eidto)): |
|
452 self._add_pending(eidfrom, rel, eidto, 'insert') |
|
453 |
|
454 def js_add_pending_inserts(self, tripletlist): |
|
455 for eidfrom, rel, eidto in tripletlist: |
|
456 self._add_pending(eidfrom, rel, eidto, 'insert') |
|
457 |
|
458 def js_remove_pending_delete(self, (eidfrom, rel, eidto)): |
|
459 self._remove_pending(eidfrom, rel, eidto, 'delete') |
|
460 |
|
461 def js_add_pending_delete(self, (eidfrom, rel, eidto)): |
|
462 self._add_pending(eidfrom, rel, eidto, 'delete') |
|
463 |
429 |
464 if HAS_SEARCH_RESTRICTION: |
430 if HAS_SEARCH_RESTRICTION: |
|
431 @jsonize |
465 def js_filter_build_rql(self, names, values): |
432 def js_filter_build_rql(self, names, values): |
466 form = self._rebuild_posted_form(names, values) |
433 form = self._rebuild_posted_form(names, values) |
467 self.req.form = form |
434 self.req.form = form |
468 builder = FilterRQLBuilder(self.req) |
435 builder = FilterRQLBuilder(self.req) |
469 return builder.build_rql() |
436 return builder.build_rql() |
470 |
437 |
|
438 @jsonize |
471 def js_filter_select_content(self, facetids, rql): |
439 def js_filter_select_content(self, facetids, rql): |
472 rqlst = self.vreg.parse(self.req, rql) # XXX Union unsupported yet |
440 rqlst = self.vreg.parse(self.req, rql) # XXX Union unsupported yet |
473 mainvar = prepare_facets_rqlst(rqlst)[0] |
441 mainvar = prepare_facets_rqlst(rqlst)[0] |
474 update_map = {} |
442 update_map = {} |
475 for facetid in facetids: |
443 for facetid in facetids: |
476 facet = get_facet(self.req, facetid, rqlst.children[0], mainvar) |
444 facet = get_facet(self.req, facetid, rqlst.children[0], mainvar) |
477 update_map[facetid] = facet.possible_values() |
445 update_map[facetid] = facet.possible_values() |
478 return update_map |
446 return update_map |
479 |
447 |
|
448 def js_unregister_user_callback(self, cbname): |
|
449 self.req.unregister_callback(self.req.pageid, cbname) |
|
450 |
|
451 def js_unload_page_data(self): |
|
452 self.req.del_session_data(self.req.pageid) |
|
453 |
|
454 def js_cancel_edition(self, errorurl): |
|
455 """cancelling edition from javascript |
|
456 |
|
457 We need to clear associated req's data : |
|
458 - errorurl |
|
459 - pending insertions / deletions |
|
460 """ |
|
461 self.req.cancel_edition(errorurl) |
|
462 |
480 def js_delete_bookmark(self, beid): |
463 def js_delete_bookmark(self, beid): |
481 try: |
464 rql = 'DELETE B bookmarked_by U WHERE B eid %(b)s, U eid %(u)s' |
482 rql = 'DELETE B bookmarked_by U WHERE B eid %(b)s, U eid %(u)s' |
465 self.req.execute(rql, {'b': typed_eid(beid), 'u' : self.req.user.eid}) |
483 self.req.execute(rql, {'b': typed_eid(beid), 'u' : self.req.user.eid}) |
466 |
484 except Exception, ex: |
467 def js_set_cookie(self, cookiename, cookievalue): |
485 self.exception(unicode(ex)) |
468 # XXX we should consider jQuery.Cookie |
486 return self.req._('Problem occured') |
469 cookiename, cookievalue = str(cookiename), str(cookievalue) |
|
470 cookies = self.req.get_cookie() |
|
471 cookies[cookiename] = cookievalue |
|
472 self.req.set_cookie(cookies, cookiename) |
|
473 |
|
474 # relations edition stuff ################################################## |
487 |
475 |
488 def _add_pending(self, eidfrom, rel, eidto, kind): |
476 def _add_pending(self, eidfrom, rel, eidto, kind): |
489 key = 'pending_%s' % kind |
477 key = 'pending_%s' % kind |
490 pendings = self.req.get_session_data(key, set()) |
478 pendings = self.req.get_session_data(key, set()) |
491 pendings.add( (typed_eid(eidfrom), rel, typed_eid(eidto)) ) |
479 pendings.add( (typed_eid(eidfrom), rel, typed_eid(eidto)) ) |
492 self.req.set_session_data(key, pendings) |
480 self.req.set_session_data(key, pendings) |
493 |
481 |
494 def _remove_pending(self, eidfrom, rel, eidto, kind): |
482 def _remove_pending(self, eidfrom, rel, eidto, kind): |
495 key = 'pending_%s' % kind |
483 key = 'pending_%s' % kind |
496 try: |
484 try: |
497 pendings = self.req.get_session_data(key) |
485 pendings = self.req.get_session_data(key) |
498 pendings.remove( (typed_eid(eidfrom), rel, typed_eid(eidto)) ) |
486 pendings.remove( (typed_eid(eidfrom), rel, typed_eid(eidto)) ) |
499 except: |
487 except: |
500 self.exception('while removing pending eids') |
488 self.exception('while removing pending eids') |
501 else: |
489 else: |
502 self.req.set_session_data(key, pendings) |
490 self.req.set_session_data(key, pendings) |
503 |
491 |
|
492 def js_remove_pending_insert(self, (eidfrom, rel, eidto)): |
|
493 self._remove_pending(eidfrom, rel, eidto, 'insert') |
|
494 |
|
495 def js_add_pending_inserts(self, tripletlist): |
|
496 for eidfrom, rel, eidto in tripletlist: |
|
497 self._add_pending(eidfrom, rel, eidto, 'insert') |
|
498 |
|
499 def js_remove_pending_delete(self, (eidfrom, rel, eidto)): |
|
500 self._remove_pending(eidfrom, rel, eidto, 'delete') |
|
501 |
|
502 def js_add_pending_delete(self, (eidfrom, rel, eidto)): |
|
503 self._add_pending(eidfrom, rel, eidto, 'delete') |
|
504 |
|
505 # XXX specific code. Kill me and my AddComboBox friend |
|
506 @jsonize |
504 def js_add_and_link_new_entity(self, etype_to, rel, eid_to, etype_from, value_from): |
507 def js_add_and_link_new_entity(self, etype_to, rel, eid_to, etype_from, value_from): |
505 # create a new entity |
508 # create a new entity |
506 eid_from = self.req.execute('INSERT %s T : T name "%s"' % ( etype_from, value_from ))[0][0] |
509 eid_from = self.req.execute('INSERT %s T : T name "%s"' % ( etype_from, value_from ))[0][0] |
507 # link the new entity to the main entity |
510 # link the new entity to the main entity |
508 rql = 'SET F %(rel)s T WHERE F eid %(eid_to)s, T eid %(eid_from)s' % {'rel' : rel, 'eid_to' : eid_to, 'eid_from' : eid_from} |
511 rql = 'SET F %(rel)s T WHERE F eid %(eid_to)s, T eid %(eid_from)s' % {'rel' : rel, 'eid_to' : eid_to, 'eid_from' : eid_from} |
509 return eid_from |
512 return eid_from |
510 |
513 |
511 def js_set_cookie(self, cookiename, cookievalue): |
|
512 # XXX we should consider jQuery.Cookie |
|
513 cookiename, cookievalue = str(cookiename), str(cookievalue) |
|
514 cookies = self.req.get_cookie() |
|
515 cookies[cookiename] = cookievalue |
|
516 self.req.set_cookie(cookies, cookiename) |
|
517 |
514 |
518 class SendMailController(Controller): |
515 class SendMailController(Controller): |
519 id = 'sendmail' |
516 id = 'sendmail' |
520 __select__ = match_user_groups('managers', 'users') |
517 __select__ = match_user_groups('managers', 'users') |
521 |
518 |