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
113 def home(self, req, s_action=None):
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 # TODO query server for installed web modules
185 for name, manifest in openerpweb.addons_manifest.items():
186 if name != 'base' and manifest.get('active', True):
190 @openerpweb.jsonrequest
191 def eval_domain_and_context(self, req, contexts, domains,
193 """ Evaluates sequences of domains and contexts, composing them into
194 a single context, domain or group_by sequence.
196 :param list contexts: list of contexts to merge together. Contexts are
197 evaluated in sequence, all previous contexts
198 are part of their own evaluation context
199 (starting at the session context).
200 :param list domains: list of domains to merge together. Domains are
201 evaluated in sequence and appended to one another
202 (implicit AND), their evaluation domain is the
203 result of merging all contexts.
204 :param list group_by_seq: list of domains (which may be in a different
205 order than the ``contexts`` parameter),
206 evaluated in sequence, their ``'group_by'``
207 key is extracted if they have one.
212 the global context created by merging all of
216 the concatenation of all domains
219 a list of fields to group by, potentially empty (in which case
220 no group by should be performed)
222 context, domain = eval_context_and_domain(req.session,
223 openerpweb.nonliterals.CompoundContext(*(contexts or [])),
224 openerpweb.nonliterals.CompoundDomain(*(domains or [])))
226 group_by_sequence = []
227 for candidate in (group_by_seq or []):
228 ctx = req.session.eval_context(candidate, context)
229 group_by = ctx.get('group_by')
232 elif isinstance(group_by, basestring):
233 group_by_sequence.append(group_by)
235 group_by_sequence.extend(group_by)
240 'group_by': group_by_sequence
243 @openerpweb.jsonrequest
244 def save_session_action(self, req, the_action):
246 This method store an action object in the session object and returns an integer
247 identifying that action. The method get_session_action() can be used to get
250 :param the_action: The action to save in the session.
251 :type the_action: anything
252 :return: A key identifying the saved action.
255 saved_actions = cherrypy.session.get('saved_actions')
256 if not saved_actions:
257 saved_actions = {"next":0, "actions":{}}
258 cherrypy.session['saved_actions'] = saved_actions
259 # we don't allow more than 10 stored actions
260 if len(saved_actions["actions"]) >= 10:
261 del saved_actions["actions"][min(saved_actions["actions"].keys())]
262 key = saved_actions["next"]
263 saved_actions["actions"][key] = the_action
264 saved_actions["next"] = key + 1
267 @openerpweb.jsonrequest
268 def get_session_action(self, req, key):
270 Gets back a previously saved action. This method can return None if the action
271 was saved since too much time (this case should be handled in a smart way).
273 :param key: The key given by save_session_action()
275 :return: The saved action or None.
278 saved_actions = cherrypy.session.get('saved_actions')
279 if not saved_actions:
281 return saved_actions["actions"].get(key)
283 @openerpweb.jsonrequest
284 def check(self, req):
285 req.session.assert_valid()
288 def eval_context_and_domain(session, context, domain=None):
289 e_context = session.eval_context(context)
290 # should we give the evaluated context as an evaluation context to the domain?
291 e_domain = session.eval_domain(domain or [])
293 return e_context, e_domain
295 def load_actions_from_ir_values(req, key, key2, models, meta, context):
296 Values = req.session.model('ir.values')
297 actions = Values.get(key, key2, models, meta, context)
299 return [(id, name, clean_action(action, req.session))
300 for id, name, action in actions]
302 def clean_action(action, session):
303 action.setdefault('flags', {})
304 if action['type'] != 'ir.actions.act_window':
306 # values come from the server, we can just eval them
307 if isinstance(action.get('context'), basestring):
308 action['context'] = eval(
310 session.evaluation_context()) or {}
312 if isinstance(action.get('domain'), basestring):
313 action['domain'] = eval(
315 session.evaluation_context(
316 action.get('context', {}))) or []
318 return fix_view_modes(action)
320 def generate_views(action):
322 While the server generates a sequence called "views" computing dependencies
323 between a bunch of stuff for views coming directly from the database
324 (the ``ir.actions.act_window model``), it's also possible for e.g. buttons
325 to return custom view dictionaries generated on the fly.
327 In that case, there is no ``views`` key available on the action.
329 Since the web client relies on ``action['views']``, generate it here from
330 ``view_mode`` and ``view_id``.
332 Currently handles two different cases:
334 * no view_id, multiple view_mode
335 * single view_id, single view_mode
337 :param dict action: action descriptor dictionary to generate a views key for
339 view_id = action.get('view_id', False)
340 if isinstance(view_id, (list, tuple)):
343 # providing at least one view mode is a requirement, not an option
344 view_modes = action['view_mode'].split(',')
346 if len(view_modes) > 1:
348 raise ValueError('Non-db action dictionaries should provide '
349 'either multiple view modes or a single view '
350 'mode and an optional view id.\n\n Got view '
351 'modes %r and view id %r for action %r' % (
352 view_modes, view_id, action))
353 action['views'] = [(False, mode) for mode in view_modes]
355 action['views'] = [(view_id, view_modes[0])]
357 def fix_view_modes(action):
358 """ For historical reasons, OpenERP has weird dealings in relation to
359 view_mode and the view_type attribute (on window actions):
361 * one of the view modes is ``tree``, which stands for both list views
363 * the choice is made by checking ``view_type``, which is either
364 ``form`` for a list view or ``tree`` for an actual tree view
366 This methods simply folds the view_type into view_mode by adding a
367 new view mode ``list`` which is the result of the ``tree`` view_mode
368 in conjunction with the ``form`` view_type.
370 TODO: this should go into the doc, some kind of "peculiarities" section
372 :param dict action: an action descriptor
373 :returns: nothing, the action is modified in place
375 if 'views' not in action:
376 generate_views(action)
378 if action.pop('view_type') != 'form':
382 [id, mode if mode != 'tree' else 'list']
383 for id, mode in action['views']
388 class Menu(openerpweb.Controller):
389 _cp_path = "/base/menu"
391 @openerpweb.jsonrequest
393 return {'data': self.do_load(req)}
395 def do_load(self, req):
396 """ Loads all menu items (all applications and their sub-menus).
398 :param req: A request object, with an OpenERP session attribute
399 :type req: < session -> OpenERPSession >
400 :return: the menu root
401 :rtype: dict('children': menu_nodes)
403 Menus = req.session.model('ir.ui.menu')
404 # menus are loaded fully unlike a regular tree view, cause there are
405 # less than 512 items
406 context = req.session.eval_context(req.context)
407 menu_ids = Menus.search([], 0, False, False, context)
408 menu_items = Menus.read(menu_ids, ['name', 'sequence', 'parent_id'], context)
409 menu_root = {'id': False, 'name': 'root', 'parent_id': [-1, '']}
410 menu_items.append(menu_root)
412 # make a tree using parent_id
413 menu_items_map = dict((menu_item["id"], menu_item) for menu_item in menu_items)
414 for menu_item in menu_items:
415 if menu_item['parent_id']:
416 parent = menu_item['parent_id'][0]
419 if parent in menu_items_map:
420 menu_items_map[parent].setdefault(
421 'children', []).append(menu_item)
423 # sort by sequence a tree using parent_id
424 for menu_item in menu_items:
425 menu_item.setdefault('children', []).sort(
426 key=lambda x:x["sequence"])
430 @openerpweb.jsonrequest
431 def action(self, req, menu_id):
432 actions = load_actions_from_ir_values(req,'action', 'tree_but_open',
433 [('ir.ui.menu', menu_id)], False,
434 req.session.eval_context(req.context))
435 return {"action": actions}
437 class DataSet(openerpweb.Controller):
438 _cp_path = "/base/dataset"
440 @openerpweb.jsonrequest
441 def fields(self, req, model):
442 return {'fields': req.session.model(model).fields_get(False,
443 req.session.eval_context(req.context))}
445 @openerpweb.jsonrequest
446 def search_read(self, request, model, fields=False, offset=0, limit=False, domain=None, sort=None):
447 return self.do_search_read(request, model, fields, offset, limit, domain, sort)
448 def do_search_read(self, request, model, fields=False, offset=0, limit=False, domain=None
450 """ Performs a search() followed by a read() (if needed) using the
451 provided search criteria
453 :param request: a JSON-RPC request object
454 :type request: openerpweb.JsonRequest
455 :param str model: the name of the model to search on
456 :param fields: a list of the fields to return in the result records
458 :param int offset: from which index should the results start being returned
459 :param int limit: the maximum number of records to return
460 :param list domain: the search domain for the query
461 :param list sort: sorting directives
462 :returns: A structure (dict) with two keys: ids (all the ids matching
463 the (domain, context) pair) and records (paginated records
464 matching fields selection set)
467 Model = request.session.model(model)
468 context, domain = eval_context_and_domain(
469 request.session, request.context, domain)
471 ids = Model.search(domain, 0, False, sort or False, context)
472 # need to fill the dataset with all ids for the (domain, context) pair,
473 # so search un-paginated and paginate manually before reading
474 paginated_ids = ids[offset:(offset + limit if limit else None)]
475 if fields and fields == ['id']:
476 # shortcut read if we only want the ids
479 'records': map(lambda id: {'id': id}, paginated_ids)
482 records = Model.read(paginated_ids, fields or False, context)
483 records.sort(key=lambda obj: ids.index(obj['id']))
490 @openerpweb.jsonrequest
491 def get(self, request, model, ids, fields=False):
492 return self.do_get(request, model, ids, fields)
493 def do_get(self, request, model, ids, fields=False):
494 """ Fetches and returns the records of the model ``model`` whose ids
497 The results are in the same order as the inputs, but elements may be
498 missing (if there is no record left for the id)
500 :param request: the JSON-RPC2 request object
501 :type request: openerpweb.JsonRequest
502 :param model: the model to read from
504 :param ids: a list of identifiers
506 :param fields: a list of fields to fetch, ``False`` or empty to fetch
507 all fields in the model
508 :type fields: list | False
509 :returns: a list of records, in the same order as the list of ids
512 Model = request.session.model(model)
513 records = Model.read(ids, fields, request.session.eval_context(request.context))
515 record_map = dict((record['id'], record) for record in records)
517 return [record_map[id] for id in ids if record_map.get(id)]
519 @openerpweb.jsonrequest
520 def load(self, req, model, id, fields):
521 m = req.session.model(model)
523 r = m.read([id], False, req.session.eval_context(req.context))
526 return {'value': value}
528 @openerpweb.jsonrequest
529 def create(self, req, model, data):
530 m = req.session.model(model)
531 r = m.create(data, req.session.eval_context(req.context))
534 @openerpweb.jsonrequest
535 def save(self, req, model, id, data):
536 m = req.session.model(model)
537 r = m.write([id], data, req.session.eval_context(req.context))
540 @openerpweb.jsonrequest
541 def unlink(self, request, model, ids=()):
542 Model = request.session.model(model)
543 return Model.unlink(ids, request.session.eval_context(request.context))
545 def call_common(self, req, model, method, args, domain_id=None, context_id=None):
546 domain = args[domain_id] if domain_id and len(args) - 1 >= domain_id else []
547 context = args[context_id] if context_id and len(args) - 1 >= context_id else {}
548 c, d = eval_context_and_domain(req.session, context, domain)
549 if domain_id and len(args) - 1 >= domain_id:
551 if context_id and len(args) - 1 >= context_id:
554 return getattr(req.session.model(model), method)(*args)
556 @openerpweb.jsonrequest
557 def call(self, req, model, method, args, domain_id=None, context_id=None):
558 return self.call_common(req, model, method, args, domain_id, context_id)
560 @openerpweb.jsonrequest
561 def call_button(self, req, model, method, args, domain_id=None, context_id=None):
562 action = self.call_common(req, model, method, args, domain_id, context_id)
563 if isinstance(action, dict) and action.get('type') != '':
564 return {'result': clean_action(action, req.session)}
565 return {'result': False}
567 @openerpweb.jsonrequest
568 def exec_workflow(self, req, model, id, signal):
569 r = req.session.exec_workflow(model, id, signal)
572 @openerpweb.jsonrequest
573 def default_get(self, req, model, fields):
574 Model = req.session.model(model)
575 return Model.default_get(fields, req.session.eval_context(req.context))
577 class DataGroup(openerpweb.Controller):
578 _cp_path = "/base/group"
579 @openerpweb.jsonrequest
580 def read(self, request, model, fields, group_by_fields, domain=None, sort=None):
581 Model = request.session.model(model)
582 context, domain = eval_context_and_domain(request.session, request.context, domain)
584 return Model.read_group(
585 domain or [], fields, group_by_fields, 0, False,
586 dict(context, group_by=group_by_fields), sort or False)
588 class View(openerpweb.Controller):
589 _cp_path = "/base/view"
591 def fields_view_get(self, request, model, view_id, view_type,
592 transform=True, toolbar=False, submenu=False):
593 Model = request.session.model(model)
594 context = request.session.eval_context(request.context)
595 fvg = Model.fields_view_get(view_id, view_type, context, toolbar, submenu)
596 # todo fme?: check that we should pass the evaluated context here
597 self.process_view(request.session, fvg, context, transform)
600 def process_view(self, session, fvg, context, transform):
601 # depending on how it feels, xmlrpclib.ServerProxy can translate
602 # XML-RPC strings to ``str`` or ``unicode``. ElementTree does not
603 # enjoy unicode strings which can not be trivially converted to
604 # strings, and it blows up during parsing.
606 # So ensure we fix this retardation by converting view xml back to
608 if isinstance(fvg['arch'], unicode):
609 arch = fvg['arch'].encode('utf-8')
614 evaluation_context = session.evaluation_context(context or {})
615 xml = self.transform_view(arch, session, evaluation_context)
617 xml = ElementTree.fromstring(arch)
618 fvg['arch'] = Xml2Json.convert_element(xml)
620 for field in fvg['fields'].itervalues():
621 if field.get('views'):
622 for view in field["views"].itervalues():
623 self.process_view(session, view, None, transform)
624 if field.get('domain'):
625 field["domain"] = self.parse_domain(field["domain"], session)
626 if field.get('context'):
627 field["context"] = self.parse_context(field["context"], session)
629 @openerpweb.jsonrequest
630 def add_custom(self, request, view_id, arch):
631 CustomView = request.session.model('ir.ui.view.custom')
633 'user_id': request.session._uid,
636 }, request.session.eval_context(request.context))
637 return {'result': True}
639 @openerpweb.jsonrequest
640 def undo_custom(self, request, view_id, reset=False):
641 CustomView = request.session.model('ir.ui.view.custom')
642 context = request.session.eval_context(request.context)
643 vcustom = CustomView.search([('user_id', '=', request.session._uid), ('ref_id' ,'=', view_id)],
644 0, False, False, context)
647 CustomView.unlink(vcustom, context)
649 CustomView.unlink([vcustom[0]], context)
650 return {'result': True}
651 return {'result': False}
653 def transform_view(self, view_string, session, context=None):
654 # transform nodes on the fly via iterparse, instead of
655 # doing it statically on the parsing result
656 parser = ElementTree.iterparse(StringIO(view_string), events=("start",))
658 for event, elem in parser:
662 self.parse_domains_and_contexts(elem, session)
665 def parse_domain(self, domain, session):
666 """ Parses an arbitrary string containing a domain, transforms it
667 to either a literal domain or a :class:`openerpweb.nonliterals.Domain`
669 :param domain: the domain to parse, if the domain is not a string it is assumed to
670 be a literal domain and is returned as-is
671 :param session: Current OpenERP session
672 :type session: openerpweb.openerpweb.OpenERPSession
674 if not isinstance(domain, (str, unicode)):
677 return openerpweb.ast.literal_eval(domain)
680 return openerpweb.nonliterals.Domain(session, domain)
682 def parse_context(self, context, session):
683 """ Parses an arbitrary string containing a context, transforms it
684 to either a literal context or a :class:`openerpweb.nonliterals.Context`
686 :param context: the context to parse, if the context is not a string it is assumed to
687 be a literal domain and is returned as-is
688 :param session: Current OpenERP session
689 :type session: openerpweb.openerpweb.OpenERPSession
691 if not isinstance(context, (str, unicode)):
694 return openerpweb.ast.literal_eval(context)
696 return openerpweb.nonliterals.Context(session, context)
698 def parse_domains_and_contexts(self, elem, session):
699 """ Converts domains and contexts from the view into Python objects,
700 either literals if they can be parsed by literal_eval or a special
701 placeholder object if the domain or context refers to free variables.
703 :param elem: the current node being parsed
704 :type param: xml.etree.ElementTree.Element
705 :param session: OpenERP session object, used to store and retrieve
707 :type session: openerpweb.openerpweb.OpenERPSession
709 for el in ['domain', 'filter_domain']:
710 domain = elem.get(el, '').strip()
712 elem.set(el, self.parse_domain(domain, session))
713 for el in ['context', 'default_get']:
714 context_string = elem.get(el, '').strip()
716 elem.set(el, self.parse_context(context_string, session))
718 class FormView(View):
719 _cp_path = "/base/formview"
721 @openerpweb.jsonrequest
722 def load(self, req, model, view_id, toolbar=False):
723 fields_view = self.fields_view_get(req, model, view_id, 'form', toolbar=toolbar)
724 return {'fields_view': fields_view}
726 class ListView(View):
727 _cp_path = "/base/listview"
729 @openerpweb.jsonrequest
730 def load(self, req, model, view_id, toolbar=False):
731 fields_view = self.fields_view_get(req, model, view_id, 'tree', toolbar=toolbar)
732 return {'fields_view': fields_view}
734 def process_colors(self, view, row, context):
735 colors = view['arch']['attrs'].get('colors')
742 for pair in colors.split(';')
743 if eval(pair.split(':')[1], dict(context, **row))
748 elif len(color) == 1:
752 class SearchView(View):
753 _cp_path = "/base/searchview"
755 @openerpweb.jsonrequest
756 def load(self, req, model, view_id):
757 fields_view = self.fields_view_get(req, model, view_id, 'search')
758 return {'fields_view': fields_view}
760 @openerpweb.jsonrequest
761 def fields_get(self, req, model):
762 Model = req.session.model(model)
763 fields = Model.fields_get(False, req.session.eval_context(req.context))
764 for field in fields.values():
765 # shouldn't convert the views too?
766 if field.get('domain'):
767 field["domain"] = self.parse_domain(field["domain"], req.session)
768 if field.get('context'):
769 field["context"] = self.parse_domain(field["context"], req.session)
770 return {'fields': fields}
772 @openerpweb.jsonrequest
773 def get_filters(self, req, model):
774 Model = req.session.model("ir.filters")
775 filters = Model.get_filters(model)
776 for filter in filters:
777 filter["context"] = req.session.eval_context(self.parse_context(filter["context"], req.session))
778 filter["domain"] = req.session.eval_domain(self.parse_domain(filter["domain"], req.session))
781 @openerpweb.jsonrequest
782 def save_filter(self, req, model, name, context_to_save, domain):
783 Model = req.session.model("ir.filters")
784 ctx = openerpweb.nonliterals.CompoundContext(context_to_save)
785 ctx.session = req.session
787 domain = openerpweb.nonliterals.CompoundDomain(domain)
788 domain.session = req.session
789 domain = domain.evaluate()
790 uid = req.session._uid
791 context = req.session.eval_context(req.context)
792 to_return = Model.create_or_replace({"context": ctx,
800 class Binary(openerpweb.Controller):
801 _cp_path = "/base/binary"
803 @openerpweb.httprequest
804 def image(self, request, session_id, model, id, field, **kw):
805 cherrypy.response.headers['Content-Type'] = 'image/png'
806 Model = request.session.model(model)
807 context = request.session.eval_context(request.context)
810 res = Model.default_get([field], context).get(field, '')
812 res = Model.read([int(id)], [field], context)[0].get(field, '')
813 return base64.decodestring(res)
814 except: # TODO: what's the exception here?
815 return self.placeholder()
816 def placeholder(self):
817 return open(os.path.join(openerpweb.path_addons, 'base', 'static', 'src', 'img', 'placeholder.png'), 'rb').read()
819 @openerpweb.httprequest
820 def saveas(self, request, session_id, model, id, field, fieldname, **kw):
821 Model = request.session.model(model)
822 context = request.session.eval_context(request.context)
823 res = Model.read([int(id)], [field, fieldname], context)[0]
824 filecontent = res.get(field, '')
826 raise cherrypy.NotFound
828 cherrypy.response.headers['Content-Type'] = 'application/octet-stream'
829 filename = '%s_%s' % (model.replace('.', '_'), id)
831 filename = res.get(fieldname, '') or filename
832 cherrypy.response.headers['Content-Disposition'] = 'attachment; filename=' + filename
833 return base64.decodestring(filecontent)
835 @openerpweb.httprequest
836 def upload(self, request, session_id, callback, ufile=None):
837 cherrypy.response.timeout = 500
839 for key, val in cherrypy.request.headers.iteritems():
840 headers[key.lower()] = val
841 size = int(headers.get('content-length', 0))
842 # TODO: might be useful to have a configuration flag for max-length file uploads
844 out = """<script language="javascript" type="text/javascript">
845 var win = window.top.window,
847 if (typeof(callback) === 'function') {
848 callback.apply(this, %s);
850 win.jQuery('#oe_notification', win.document).notify('create', {
851 title: "Ajax File Upload",
852 text: "Could not find callback"
856 data = ufile.file.read()
857 args = [size, ufile.filename, ufile.headers.getheader('Content-Type'), base64.encodestring(data)]
859 args = [False, e.message]
860 return out % (simplejson.dumps(callback), simplejson.dumps(args))
862 @openerpweb.httprequest
863 def upload_attachment(self, request, session_id, callback, model, id, ufile=None):
864 cherrypy.response.timeout = 500
865 context = request.session.eval_context(request.context)
866 Model = request.session.model('ir.attachment')
868 out = """<script language="javascript" type="text/javascript">
869 var win = window.top.window,
871 if (typeof(callback) === 'function') {
872 callback.call(this, %s);
875 attachment_id = Model.create({
876 'name': ufile.filename,
877 'datas': base64.encodestring(ufile.file.read()),
882 'filename': ufile.filename,
886 args = { 'error': e.message }
887 return out % (simplejson.dumps(callback), simplejson.dumps(args))
889 class Action(openerpweb.Controller):
890 _cp_path = "/base/action"
892 @openerpweb.jsonrequest
893 def load(self, req, action_id):
894 Actions = req.session.model('ir.actions.actions')
896 context = req.session.eval_context(req.context)
897 action_type = Actions.read([action_id], ['type'], context)
899 action = req.session.model(action_type[0]['type']).read([action_id], False,
902 value = clean_action(action[0], req.session)
903 return {'result': value}
905 @openerpweb.jsonrequest
906 def run(self, req, action_id):
907 return clean_action(req.session.model('ir.actions.server').run(
908 [action_id], req.session.eval_context(req.context)), req.session)