1 # -*- coding: utf-8 -*-
2 import base64, glob, os, re
3 from xml.etree import ElementTree
4 from cStringIO import StringIO
10 import openerpweb.nonliterals
14 # Should move to openerpweb.Xml2Json
17 # Simple and straightforward XML-to-JSON converter in Python
20 # URL: http://code.google.com/p/xml2json-direct/
22 def convert_to_json(s):
23 return simplejson.dumps(
24 Xml2Json.convert_to_structure(s), sort_keys=True, indent=4)
27 def convert_to_structure(s):
28 root = ElementTree.fromstring(s)
29 return Xml2Json.convert_element(root)
32 def convert_element(el, skip_whitespaces=True):
35 ns, name = el.tag.rsplit("}", 1)
37 res["namespace"] = ns[1:]
41 for k, v in el.items():
44 if el.text and (not skip_whitespaces or el.text.strip() != ''):
47 kids.append(Xml2Json.convert_element(kid))
48 if kid.tail and (not skip_whitespaces or kid.tail.strip() != ''):
50 res["children"] = kids
53 #----------------------------------------------------------
54 # OpenERP Web base Controllers
55 #----------------------------------------------------------
57 def manifest_glob(addons, key):
60 globlist = openerpweb.addons_manifest.get(addon, {}).get(key, [])
62 for pattern in globlist:
63 for path in glob.glob(os.path.join(openerpweb.path_addons, addon, pattern)):
64 files.append(path[len(openerpweb.path_addons):])
67 def concat_files(file_list):
68 """ Concatenate file content
69 return (concat,timestamp)
70 concat: concatenation of file content
71 timestamp: max(os.path.getmtime of file_list)
73 root = openerpweb.path_root
77 fname = os.path.join(root, i)
78 ftime = os.path.getmtime(fname)
79 if ftime > files_timestamp:
80 files_timestamp = ftime
81 files_content = open(fname).read()
82 files_concat = "".join(files_content)
85 class WebClient(openerpweb.Controller):
86 _cp_path = "/base/webclient"
88 @openerpweb.jsonrequest
89 def csslist(self, req, mods='base'):
90 return manifest_glob(mods.split(','), 'css')
92 @openerpweb.jsonrequest
93 def jslist(self, req, mods='base'):
94 return manifest_glob(mods.split(','), 'js')
96 @openerpweb.httprequest
97 def css(self, req, mods='base'):
98 cherrypy.response.headers['Content-Type'] = 'text/css'
99 files = manifest_glob(mods.split(','), 'css')
100 concat = concat_files(files)[0]
101 # TODO request set the Date of last modif and Etag
104 @openerpweb.httprequest
105 def js(self, req, mods='base'):
106 cherrypy.response.headers['Content-Type'] = 'application/javascript'
107 files = manifest_glob(mods.split(','), 'js')
108 concat = concat_files(files)[0]
109 # TODO request set the Date of last modif and Etag
112 @openerpweb.httprequest
114 template ="""<!DOCTYPE html>
115 <html style="height: 100%%">
117 <meta http-equiv="content-type" content="text/html; charset=utf-8" />
118 <title>OpenERP</title>
120 <script type="text/javascript">
122 QWeb = new QWeb2.Engine();
123 openerp.init().base.webclient("oe");
126 <link rel="shortcut icon" href="/base/static/src/img/favicon.ico" type="image/x-icon"/>
129 <link rel="stylesheet" href="/base/static/src/css/base-ie7.css" type="text/css"/>
132 <body id="oe" class="openerp"></body>
134 """.replace('\n'+' '*8,'\n')
137 jslist = ['/base/webclient/js']
139 jslist = manifest_glob(['base'], 'js')
140 js = "\n ".join(['<script type="text/javascript" src="%s"></script>'%i for i in jslist])
143 csslist = ['/base/webclient/css']
145 csslist = manifest_glob(['base'], 'css')
146 css = "\n ".join(['<link rel="stylesheet" href="%s">'%i for i in csslist])
147 r = template % (js, css)
150 class Database(openerpweb.Controller):
151 _cp_path = "/base/database"
153 @openerpweb.jsonrequest
154 def get_databases_list(self, req):
155 proxy = req.session.proxy("db")
157 h = req.httprequest.headers['Host'].split(':')[0]
159 r = cherrypy.config['openerp.dbfilter'].replace('%h',h).replace('%d',d)
161 dbs = [i for i in dbs if re.match(r,i)]
162 return {"db_list": dbs}
164 class Session(openerpweb.Controller):
165 _cp_path = "/base/session"
167 @openerpweb.jsonrequest
168 def login(self, req, db, login, password):
169 req.session.login(db, login, password)
172 "session_id": req.session_id,
173 "uid": req.session._uid,
176 @openerpweb.jsonrequest
177 def sc_list(self, req):
178 return req.session.model('ir.ui.view_sc').get_sc(req.session._uid, "ir.ui.menu",
179 req.session.eval_context(req.context))
181 @openerpweb.jsonrequest
182 def modules(self, req):
183 return {"modules": [name
184 for name, manifest in openerpweb.addons_manifest.iteritems()
185 if manifest.get('active', True)]}
187 @openerpweb.jsonrequest
188 def eval_domain_and_context(self, req, contexts, domains,
190 """ Evaluates sequences of domains and contexts, composing them into
191 a single context, domain or group_by sequence.
193 :param list contexts: list of contexts to merge together. Contexts are
194 evaluated in sequence, all previous contexts
195 are part of their own evaluation context
196 (starting at the session context).
197 :param list domains: list of domains to merge together. Domains are
198 evaluated in sequence and appended to one another
199 (implicit AND), their evaluation domain is the
200 result of merging all contexts.
201 :param list group_by_seq: list of domains (which may be in a different
202 order than the ``contexts`` parameter),
203 evaluated in sequence, their ``'group_by'``
204 key is extracted if they have one.
209 the global context created by merging all of
213 the concatenation of all domains
216 a list of fields to group by, potentially empty (in which case
217 no group by should be performed)
219 context, domain = eval_context_and_domain(req.session,
220 openerpweb.nonliterals.CompoundContext(*(contexts or [])),
221 openerpweb.nonliterals.CompoundDomain(*(domains or [])))
223 group_by_sequence = []
224 for candidate in (group_by_seq or []):
225 ctx = req.session.eval_context(candidate, context)
226 group_by = ctx.get('group_by')
229 elif isinstance(group_by, basestring):
230 group_by_sequence.append(group_by)
232 group_by_sequence.extend(group_by)
237 'group_by': group_by_sequence
240 @openerpweb.jsonrequest
241 def save_session_action(self, req, the_action):
243 This method store an action object in the session object and returns an integer
244 identifying that action. The method get_session_action() can be used to get
247 :param the_action: The action to save in the session.
248 :type the_action: anything
249 :return: A key identifying the saved action.
252 saved_actions = cherrypy.session.get('saved_actions')
253 if not saved_actions:
254 saved_actions = {"next":0, "actions":{}}
255 cherrypy.session['saved_actions'] = saved_actions
256 # we don't allow more than 10 stored actions
257 if len(saved_actions["actions"]) >= 10:
258 del saved_actions["actions"][min(saved_actions["actions"].keys())]
259 key = saved_actions["next"]
260 saved_actions["actions"][key] = the_action
261 saved_actions["next"] = key + 1
264 @openerpweb.jsonrequest
265 def get_session_action(self, req, key):
267 Gets back a previously saved action. This method can return None if the action
268 was saved since too much time (this case should be handled in a smart way).
270 :param key: The key given by save_session_action()
272 :return: The saved action or None.
275 saved_actions = cherrypy.session.get('saved_actions')
276 if not saved_actions:
278 return saved_actions["actions"].get(key)
280 def eval_context_and_domain(session, context, domain=None):
281 e_context = session.eval_context(context)
282 # should we give the evaluated context as an evaluation context to the domain?
283 e_domain = session.eval_domain(domain or [])
285 return e_context, e_domain
287 def load_actions_from_ir_values(req, key, key2, models, meta, context):
288 Values = req.session.model('ir.values')
289 actions = Values.get(key, key2, models, meta, context)
291 return [(id, name, clean_action(action, req.session))
292 for id, name, action in actions]
294 def clean_action(action, session):
295 if action['type'] != 'ir.actions.act_window':
297 # values come from the server, we can just eval them
298 if isinstance(action.get('context', None), basestring):
299 action['context'] = eval(
301 session.evaluation_context()) or {}
303 if isinstance(action.get('domain', None), basestring):
304 action['domain'] = eval(
306 session.evaluation_context(
307 action.get('context', {}))) or []
308 if 'flags' not in action:
309 # Set empty flags dictionary for web client.
310 action['flags'] = dict()
311 return fix_view_modes(action)
313 def generate_views(action):
315 While the server generates a sequence called "views" computing dependencies
316 between a bunch of stuff for views coming directly from the database
317 (the ``ir.actions.act_window model``), it's also possible for e.g. buttons
318 to return custom view dictionaries generated on the fly.
320 In that case, there is no ``views`` key available on the action.
322 Since the web client relies on ``action['views']``, generate it here from
323 ``view_mode`` and ``view_id``.
325 Currently handles two different cases:
327 * no view_id, multiple view_mode
328 * single view_id, single view_mode
330 :param dict action: action descriptor dictionary to generate a views key for
332 view_id = action.get('view_id', False)
333 if isinstance(view_id, (list, tuple)):
336 # providing at least one view mode is a requirement, not an option
337 view_modes = action['view_mode'].split(',')
339 if len(view_modes) > 1:
341 raise ValueError('Non-db action dictionaries should provide '
342 'either multiple view modes or a single view '
343 'mode and an optional view id.\n\n Got view '
344 'modes %r and view id %r for action %r' % (
345 view_modes, view_id, action))
346 action['views'] = [(False, mode) for mode in view_modes]
348 action['views'] = [(view_id, view_modes[0])]
350 def fix_view_modes(action):
351 """ For historical reasons, OpenERP has weird dealings in relation to
352 view_mode and the view_type attribute (on window actions):
354 * one of the view modes is ``tree``, which stands for both list views
356 * the choice is made by checking ``view_type``, which is either
357 ``form`` for a list view or ``tree`` for an actual tree view
359 This methods simply folds the view_type into view_mode by adding a
360 new view mode ``list`` which is the result of the ``tree`` view_mode
361 in conjunction with the ``form`` view_type.
363 TODO: this should go into the doc, some kind of "peculiarities" section
365 :param dict action: an action descriptor
366 :returns: nothing, the action is modified in place
368 if 'views' not in action:
369 generate_views(action)
371 if action.pop('view_type') != 'form':
375 [id, mode if mode != 'tree' else 'list']
376 for id, mode in action['views']
381 class Menu(openerpweb.Controller):
382 _cp_path = "/base/menu"
384 @openerpweb.jsonrequest
386 return {'data': self.do_load(req)}
388 def do_load(self, req):
389 """ Loads all menu items (all applications and their sub-menus).
391 :param req: A request object, with an OpenERP session attribute
392 :type req: < session -> OpenERPSession >
393 :return: the menu root
394 :rtype: dict('children': menu_nodes)
396 Menus = req.session.model('ir.ui.menu')
397 # menus are loaded fully unlike a regular tree view, cause there are
398 # less than 512 items
399 context = req.session.eval_context(req.context)
400 menu_ids = Menus.search([], 0, False, False, context)
401 menu_items = Menus.read(menu_ids, ['name', 'sequence', 'parent_id'], context)
402 menu_root = {'id': False, 'name': 'root', 'parent_id': [-1, '']}
403 menu_items.append(menu_root)
405 # make a tree using parent_id
406 menu_items_map = dict((menu_item["id"], menu_item) for menu_item in menu_items)
407 for menu_item in menu_items:
408 if menu_item['parent_id']:
409 parent = menu_item['parent_id'][0]
412 if parent in menu_items_map:
413 menu_items_map[parent].setdefault(
414 'children', []).append(menu_item)
416 # sort by sequence a tree using parent_id
417 for menu_item in menu_items:
418 menu_item.setdefault('children', []).sort(
419 key=lambda x:x["sequence"])
423 @openerpweb.jsonrequest
424 def action(self, req, menu_id):
425 actions = load_actions_from_ir_values(req,'action', 'tree_but_open',
426 [('ir.ui.menu', menu_id)], False,
427 req.session.eval_context(req.context))
428 return {"action": actions}
430 class DataSet(openerpweb.Controller):
431 _cp_path = "/base/dataset"
433 @openerpweb.jsonrequest
434 def fields(self, req, model):
435 return {'fields': req.session.model(model).fields_get(False,
436 req.session.eval_context(req.context))}
438 @openerpweb.jsonrequest
439 def search_read(self, request, model, fields=False, offset=0, limit=False, domain=None, sort=None):
440 return self.do_search_read(request, model, fields, offset, limit, domain, sort)
441 def do_search_read(self, request, model, fields=False, offset=0, limit=False, domain=None
443 """ Performs a search() followed by a read() (if needed) using the
444 provided search criteria
446 :param request: a JSON-RPC request object
447 :type request: openerpweb.JsonRequest
448 :param str model: the name of the model to search on
449 :param fields: a list of the fields to return in the result records
451 :param int offset: from which index should the results start being returned
452 :param int limit: the maximum number of records to return
453 :param list domain: the search domain for the query
454 :param list sort: sorting directives
455 :returns: A structure (dict) with two keys: ids (all the ids matching
456 the (domain, context) pair) and records (paginated records
457 matching fields selection set)
460 Model = request.session.model(model)
461 context, domain = eval_context_and_domain(
462 request.session, request.context, domain)
464 ids = Model.search(domain, 0, False, sort or False, context)
465 # need to fill the dataset with all ids for the (domain, context) pair,
466 # so search un-paginated and paginate manually before reading
467 paginated_ids = ids[offset:(offset + limit if limit else None)]
468 if fields and fields == ['id']:
469 # shortcut read if we only want the ids
472 'records': map(lambda id: {'id': id}, paginated_ids)
475 records = Model.read(paginated_ids, fields or False, context)
476 records.sort(key=lambda obj: ids.index(obj['id']))
483 @openerpweb.jsonrequest
484 def get(self, request, model, ids, fields=False):
485 return self.do_get(request, model, ids, fields)
486 def do_get(self, request, model, ids, fields=False):
487 """ Fetches and returns the records of the model ``model`` whose ids
490 The results are in the same order as the inputs, but elements may be
491 missing (if there is no record left for the id)
493 :param request: the JSON-RPC2 request object
494 :type request: openerpweb.JsonRequest
495 :param model: the model to read from
497 :param ids: a list of identifiers
499 :param fields: a list of fields to fetch, ``False`` or empty to fetch
500 all fields in the model
501 :type fields: list | False
502 :returns: a list of records, in the same order as the list of ids
505 Model = request.session.model(model)
506 records = Model.read(ids, fields, request.session.eval_context(request.context))
508 record_map = dict((record['id'], record) for record in records)
510 return [record_map[id] for id in ids if record_map.get(id)]
512 @openerpweb.jsonrequest
513 def load(self, req, model, id, fields):
514 m = req.session.model(model)
516 r = m.read([id], False, req.session.eval_context(req.context))
519 return {'value': value}
521 @openerpweb.jsonrequest
522 def create(self, req, model, data):
523 m = req.session.model(model)
524 r = m.create(data, req.session.eval_context(req.context))
527 @openerpweb.jsonrequest
528 def save(self, req, model, id, data):
529 m = req.session.model(model)
530 r = m.write([id], data, req.session.eval_context(req.context))
533 @openerpweb.jsonrequest
534 def unlink(self, request, model, ids=()):
535 Model = request.session.model(model)
536 return Model.unlink(ids, request.session.eval_context(request.context))
538 def call_common(self, req, model, method, args, domain_id=None, context_id=None):
539 domain = args[domain_id] if domain_id and len(args) - 1 >= domain_id else []
540 context = args[context_id] if context_id and len(args) - 1 >= context_id else {}
541 c, d = eval_context_and_domain(req.session, context, domain)
542 if domain_id and len(args) - 1 >= domain_id:
544 if context_id and len(args) - 1 >= context_id:
547 return getattr(req.session.model(model), method)(*args)
549 @openerpweb.jsonrequest
550 def call(self, req, model, method, args, domain_id=None, context_id=None):
551 return self.call_common(req, model, method, args, domain_id, context_id)
553 @openerpweb.jsonrequest
554 def call_button(self, req, model, method, args, domain_id=None, context_id=None):
555 action = self.call_common(req, model, method, args, domain_id, context_id)
556 if isinstance(action, dict) and action.get('type') != '':
557 return {'result': clean_action(action, req.session)}
558 return {'result': False}
560 @openerpweb.jsonrequest
561 def exec_workflow(self, req, model, id, signal):
562 r = req.session.exec_workflow(model, id, signal)
565 @openerpweb.jsonrequest
566 def default_get(self, req, model, fields):
567 Model = req.session.model(model)
568 return Model.default_get(fields, req.session.eval_context(req.context))
570 class DataGroup(openerpweb.Controller):
571 _cp_path = "/base/group"
572 @openerpweb.jsonrequest
573 def read(self, request, model, fields, group_by_fields, domain=None, sort=None):
574 Model = request.session.model(model)
575 context, domain = eval_context_and_domain(request.session, request.context, domain)
577 return Model.read_group(
578 domain or [], fields, group_by_fields, 0, False,
579 dict(context, group_by=group_by_fields), sort or False)
581 class View(openerpweb.Controller):
582 _cp_path = "/base/view"
584 def fields_view_get(self, request, model, view_id, view_type,
585 transform=True, toolbar=False, submenu=False):
586 Model = request.session.model(model)
587 context = request.session.eval_context(request.context)
588 fvg = Model.fields_view_get(view_id, view_type, context, toolbar, submenu)
589 # todo fme?: check that we should pass the evaluated context here
590 self.process_view(request.session, fvg, context, transform)
593 def process_view(self, session, fvg, context, transform):
594 # depending on how it feels, xmlrpclib.ServerProxy can translate
595 # XML-RPC strings to ``str`` or ``unicode``. ElementTree does not
596 # enjoy unicode strings which can not be trivially converted to
597 # strings, and it blows up during parsing.
599 # So ensure we fix this retardation by converting view xml back to
601 if isinstance(fvg['arch'], unicode):
602 arch = fvg['arch'].encode('utf-8')
607 evaluation_context = session.evaluation_context(context or {})
608 xml = self.transform_view(arch, session, evaluation_context)
610 xml = ElementTree.fromstring(arch)
611 fvg['arch'] = Xml2Json.convert_element(xml)
613 for field in fvg['fields'].itervalues():
614 if field.get('views'):
615 for view in field["views"].itervalues():
616 self.process_view(session, view, None, transform)
617 if field.get('domain'):
618 field["domain"] = self.parse_domain(field["domain"], session)
619 if field.get('context'):
620 field["context"] = self.parse_context(field["context"], session)
622 @openerpweb.jsonrequest
623 def add_custom(self, request, view_id, arch):
624 CustomView = request.session.model('ir.ui.view.custom')
626 'user_id': request.session._uid,
629 }, request.session.eval_context(request.context))
630 return {'result': True}
632 @openerpweb.jsonrequest
633 def undo_custom(self, request, view_id, reset=False):
634 CustomView = request.session.model('ir.ui.view.custom')
635 context = request.session.eval_context(request.context)
636 vcustom = CustomView.search([('user_id', '=', request.session._uid), ('ref_id' ,'=', view_id)],
637 0, False, False, context)
640 CustomView.unlink(vcustom, context)
642 CustomView.unlink([vcustom[0]], context)
643 return {'result': True}
644 return {'result': False}
646 def transform_view(self, view_string, session, context=None):
647 # transform nodes on the fly via iterparse, instead of
648 # doing it statically on the parsing result
649 parser = ElementTree.iterparse(StringIO(view_string), events=("start",))
651 for event, elem in parser:
655 self.parse_domains_and_contexts(elem, session)
658 def parse_domain(self, domain, session):
659 """ Parses an arbitrary string containing a domain, transforms it
660 to either a literal domain or a :class:`openerpweb.nonliterals.Domain`
662 :param domain: the domain to parse, if the domain is not a string it is assumed to
663 be a literal domain and is returned as-is
664 :param session: Current OpenERP session
665 :type session: openerpweb.openerpweb.OpenERPSession
667 if not isinstance(domain, (str, unicode)):
670 return openerpweb.ast.literal_eval(domain)
673 return openerpweb.nonliterals.Domain(session, domain)
675 def parse_context(self, context, session):
676 """ Parses an arbitrary string containing a context, transforms it
677 to either a literal context or a :class:`openerpweb.nonliterals.Context`
679 :param context: the context to parse, if the context is not a string it is assumed to
680 be a literal domain and is returned as-is
681 :param session: Current OpenERP session
682 :type session: openerpweb.openerpweb.OpenERPSession
684 if not isinstance(context, (str, unicode)):
687 return openerpweb.ast.literal_eval(context)
689 return openerpweb.nonliterals.Context(session, context)
691 def parse_domains_and_contexts(self, elem, session):
692 """ Converts domains and contexts from the view into Python objects,
693 either literals if they can be parsed by literal_eval or a special
694 placeholder object if the domain or context refers to free variables.
696 :param elem: the current node being parsed
697 :type param: xml.etree.ElementTree.Element
698 :param session: OpenERP session object, used to store and retrieve
700 :type session: openerpweb.openerpweb.OpenERPSession
702 for el in ['domain', 'filter_domain']:
703 domain = elem.get(el, '').strip()
705 elem.set(el, self.parse_domain(domain, session))
706 for el in ['context', 'default_get']:
707 context_string = elem.get(el, '').strip()
709 elem.set(el, self.parse_context(context_string, session))
711 class FormView(View):
712 _cp_path = "/base/formview"
714 @openerpweb.jsonrequest
715 def load(self, req, model, view_id, toolbar=False):
716 fields_view = self.fields_view_get(req, model, view_id, 'form', toolbar=toolbar)
717 return {'fields_view': fields_view}
719 class ListView(View):
720 _cp_path = "/base/listview"
722 @openerpweb.jsonrequest
723 def load(self, req, model, view_id, toolbar=False):
724 fields_view = self.fields_view_get(req, model, view_id, 'tree', toolbar=toolbar)
725 return {'fields_view': fields_view}
727 def process_colors(self, view, row, context):
728 colors = view['arch']['attrs'].get('colors')
735 for pair in colors.split(';')
736 if eval(pair.split(':')[1], dict(context, **row))
741 elif len(color) == 1:
745 class SearchView(View):
746 _cp_path = "/base/searchview"
748 @openerpweb.jsonrequest
749 def load(self, req, model, view_id):
750 fields_view = self.fields_view_get(req, model, view_id, 'search')
751 return {'fields_view': fields_view}
753 @openerpweb.jsonrequest
754 def fields_get(self, req, model):
755 Model = req.session.model(model)
756 fields = Model.fields_get(False, req.session.eval_context(req.context))
757 for field in fields.values():
758 # shouldn't convert the views too?
759 if field.get('domain'):
760 field["domain"] = self.parse_domain(field["domain"], req.session)
761 if field.get('context'):
762 field["context"] = self.parse_domain(field["context"], req.session)
763 return {'fields': fields}
765 class Binary(openerpweb.Controller):
766 _cp_path = "/base/binary"
768 @openerpweb.httprequest
769 def image(self, request, session_id, model, id, field, **kw):
770 cherrypy.response.headers['Content-Type'] = 'image/png'
771 Model = request.session.model(model)
772 context = request.session.eval_context(request.context)
775 res = Model.default_get([field], context).get(field, '')
777 res = Model.read([int(id)], [field], context)[0].get(field, '')
778 return base64.decodestring(res)
779 except: # TODO: what's the exception here?
780 return self.placeholder()
781 def placeholder(self):
782 return open(os.path.join(openerpweb.path_addons, 'base', 'static', 'src', 'img', 'placeholder.png'), 'rb').read()
784 @openerpweb.httprequest
785 def saveas(self, request, session_id, model, id, field, fieldname, **kw):
786 Model = request.session.model(model)
787 context = request.session.eval_context(request.context)
788 res = Model.read([int(id)], [field, fieldname], context)[0]
789 filecontent = res.get(field, '')
791 raise cherrypy.NotFound
793 cherrypy.response.headers['Content-Type'] = 'application/octet-stream'
794 filename = '%s_%s' % (model.replace('.', '_'), id)
796 filename = res.get(fieldname, '') or filename
797 cherrypy.response.headers['Content-Disposition'] = 'attachment; filename=' + filename
798 return base64.decodestring(filecontent)
800 @openerpweb.httprequest
801 def upload(self, request, session_id, callback, ufile=None):
802 cherrypy.response.timeout = 500
804 for key, val in cherrypy.request.headers.iteritems():
805 headers[key.lower()] = val
806 size = int(headers.get('content-length', 0))
807 # TODO: might be useful to have a configuration flag for max-length file uploads
809 out = """<script language="javascript" type="text/javascript">
810 var win = window.top.window,
812 if (typeof(callback) === 'function') {
813 callback.apply(this, %s);
815 win.jQuery('#oe_notification', win.document).notify('create', {
816 title: "Ajax File Upload",
817 text: "Could not find callback"
821 data = ufile.file.read()
822 args = [size, ufile.filename, ufile.headers.getheader('Content-Type'), base64.encodestring(data)]
824 args = [False, e.message]
825 return out % (simplejson.dumps(callback), simplejson.dumps(args))
827 @openerpweb.httprequest
828 def upload_attachment(self, request, session_id, callback, model, id, ufile=None):
829 cherrypy.response.timeout = 500
830 context = request.session.eval_context(request.context)
831 Model = request.session.model('ir.attachment')
833 out = """<script language="javascript" type="text/javascript">
834 var win = window.top.window,
836 if (typeof(callback) === 'function') {
837 callback.call(this, %s);
840 attachment_id = Model.create({
841 'name': ufile.filename,
842 'datas': base64.encodestring(ufile.file.read()),
847 'filename': ufile.filename,
851 args = { 'error': e.message }
852 return out % (simplejson.dumps(callback), simplejson.dumps(args))
854 class Action(openerpweb.Controller):
855 _cp_path = "/base/action"
857 @openerpweb.jsonrequest
858 def load(self, req, action_id):
859 Actions = req.session.model('ir.actions.actions')
861 context = req.session.eval_context(req.context)
862 action_type = Actions.read([action_id], ['type'], context)
864 action = req.session.model(action_type[0]['type']).read([action_id], False,
867 value = clean_action(action[0], req.session)
868 return {'result': value}
870 @openerpweb.jsonrequest
871 def run(self, req, action_id):
872 return clean_action(req.session.model('ir.actions.server').run(
873 [action_id], req.session.eval_context(req.context)), req.session)