1 # -*- coding: utf-8 -*-
4 from xml.etree import ElementTree
5 from cStringIO import StringIO
11 import openerpweb.nonliterals
16 # Should move to openerpweb.Xml2Json
19 # Simple and straightforward XML-to-JSON converter in Python
22 # URL: http://code.google.com/p/xml2json-direct/
24 def convert_to_json(s):
25 return simplejson.dumps(
26 Xml2Json.convert_to_structure(s), sort_keys=True, indent=4)
29 def convert_to_structure(s):
30 root = ElementTree.fromstring(s)
31 return Xml2Json.convert_element(root)
34 def convert_element(el, skip_whitespaces=True):
37 ns, name = el.tag.rsplit("}", 1)
39 res["namespace"] = ns[1:]
43 for k, v in el.items():
46 if el.text and (not skip_whitespaces or el.text.strip() != ''):
49 kids.append(Xml2Json.convert_element(kid))
50 if kid.tail and (not skip_whitespaces or kid.tail.strip() != ''):
52 res["children"] = kids
55 #----------------------------------------------------------
56 # OpenERP Web base Controllers
57 #----------------------------------------------------------
59 class Session(openerpweb.Controller):
60 _cp_path = "/base/session"
62 def manifest_glob(self, addons, key):
65 globlist = openerpweb.addons_manifest.get(addon, {}).get(key, [])
68 resource_path[len(openerpweb.path_addons):]
69 for pattern in globlist
70 for resource_path in glob.glob(os.path.join(
71 openerpweb.path_addons, addon, pattern))
75 def concat_files(self, file_list):
76 """ Concatenate file content
77 return (concat,timestamp)
78 concat: concatenation of file content
79 timestamp: max(os.path.getmtime of file_list)
81 root = openerpweb.path_root
85 fname = os.path.join(root, i)
86 ftime = os.path.getmtime(fname)
87 if ftime > files_timestamp:
88 files_timestamp = ftime
89 files_content = open(fname).read()
90 files_concat = "".join(files_content)
93 @openerpweb.jsonrequest
94 def login(self, req, db, login, password):
95 req.session.login(db, login, password)
98 "session_id": req.session_id,
99 "uid": req.session._uid,
102 @openerpweb.jsonrequest
103 def sc_list(self, req):
104 return req.session.model('ir.ui.view_sc').get_sc(req.session._uid, "ir.ui.menu",
105 req.session.eval_context(req.context))
107 @openerpweb.jsonrequest
108 def get_databases_list(self, req):
109 proxy = req.session.proxy("db")
112 return {"db_list": dbs}
114 @openerpweb.jsonrequest
115 def get_lang_list(self, req):
116 lang_list = [('en_US', 'English (US)')]
118 lang_list = lang_list + (req.session.proxy("db").list_lang() or [])
121 return {"lang_list": lang_list}
123 @openerpweb.jsonrequest
124 def db_operation(self, req, flag, **kw):
128 super_admin_pwd = kw.get('super_admin_pwd')
129 dbname = kw.get('db')
130 demo_data = kw.get('demo_data')
131 db_lang = kw.get('db_lang')
132 admin_pwd = kw.get('admin_pwd')
133 confirm_pwd = kw.get('confirm_pwd')
135 if not re.match('^[a-zA-Z][a-zA-Z0-9_]+$', dbname):
136 return {'error': "You must avoid all accents, space or special characters.", 'title': 'Bad database name'}
140 password = kw.get('password')
143 return req.session.proxy("db").drop(password, db)
145 if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
146 return {'error': 'Bad super admin password !', 'title': 'Drop Database'}
148 return {'error': 'Could not drop database !', 'title': 'Drop Database'}
150 elif flag == 'backup':
152 password = kw.get('password')
154 res = req.session.proxy("db").dump(password, db)
156 cherrypy.response.headers['Content-Type'] = "application/data"
157 cherrypy.response.headers['Content-Disposition'] = 'filename="' + db + '.dump"'
158 return base64.decodestring(res)
160 if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
161 return {'error': 'Bad super admin password !', 'title': 'Backup Database'}
163 return {'error': 'Could not drop database !', 'title': 'Backup Database'}
165 elif flag == 'restore':
166 filename = kw.get('filename')
168 password = kw.get('password')
171 data = base64.encodestring(filename.file.read())
172 return req.session.proxy("db").restore(password, db, data)
174 if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
175 return {'error': 'Bad super admin password !', 'title': 'Restore Database'}
177 return {'error': 'Could not restore database !', 'title': 'Restore Database'}
179 elif flag == 'change_password':
180 old_password = kw.get('old_password')
181 new_password = kw.get('new_password')
182 confirm_password = kw.get('confirm_password')
185 return req.session.proxy("db").change_admin_password(old_password, new_password)
187 if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
188 return {'error': 'Bad super admin password !', 'title': 'Change Password'}
190 return {'error': 'Error, password not changed !', 'title': 'Change Password'}
192 @openerpweb.jsonrequest
193 def modules(self, req):
194 return {"modules": [name
195 for name, manifest in openerpweb.addons_manifest.iteritems()
196 if manifest.get('active', True)]}
198 @openerpweb.jsonrequest
199 def csslist(self, req, mods='base'):
200 return {'files': self.manifest_glob(mods.split(','), 'css')}
202 @openerpweb.jsonrequest
203 def jslist(self, req, mods='base'):
204 return {'files': self.manifest_glob(mods.split(','), 'js')}
206 def css(self, req, mods='base'):
207 files = self.manifest_glob(mods.split(','), 'css')
208 concat = self.concat_files(files)[0]
209 # TODO request set the Date of last modif and Etag
213 def js(self, req, mods='base'):
214 files = self.manifest_glob(mods.split(','), 'js')
215 concat = self.concat_files(files)[0]
216 # TODO request set the Date of last modif and Etag
220 @openerpweb.jsonrequest
221 def eval_domain_and_context(self, req, contexts, domains,
223 """ Evaluates sequences of domains and contexts, composing them into
224 a single context, domain or group_by sequence.
226 :param list contexts: list of contexts to merge together. Contexts are
227 evaluated in sequence, all previous contexts
228 are part of their own evaluation context
229 (starting at the session context).
230 :param list domains: list of domains to merge together. Domains are
231 evaluated in sequence and appended to one another
232 (implicit AND), their evaluation domain is the
233 result of merging all contexts.
234 :param list group_by_seq: list of domains (which may be in a different
235 order than the ``contexts`` parameter),
236 evaluated in sequence, their ``'group_by'``
237 key is extracted if they have one.
242 the global context created by merging all of
246 the concatenation of all domains
249 a list of fields to group by, potentially empty (in which case
250 no group by should be performed)
252 context, domain = eval_context_and_domain(req.session,
253 openerpweb.nonliterals.CompoundContext(*(contexts or [])),
254 openerpweb.nonliterals.CompoundDomain(*(domains or [])))
256 group_by_sequence = []
257 for candidate in (group_by_seq or []):
258 ctx = req.session.eval_context(candidate, context)
259 group_by = ctx.get('group_by')
262 elif isinstance(group_by, basestring):
263 group_by_sequence.append(group_by)
265 group_by_sequence.extend(group_by)
270 'group_by': group_by_sequence
273 @openerpweb.jsonrequest
274 def save_session_action(self, req, the_action):
276 This method store an action object in the session object and returns an integer
277 identifying that action. The method get_session_action() can be used to get
280 :param the_action: The action to save in the session.
281 :type the_action: anything
282 :return: A key identifying the saved action.
285 saved_actions = cherrypy.session.get('saved_actions')
286 if not saved_actions:
287 saved_actions = {"next":0, "actions":{}}
288 cherrypy.session['saved_actions'] = saved_actions
289 # we don't allow more than 10 stored actions
290 if len(saved_actions["actions"]) >= 10:
291 del saved_actions["actions"][min(saved_actions["actions"].keys())]
292 key = saved_actions["next"]
293 saved_actions["actions"][key] = the_action
294 saved_actions["next"] = key + 1
297 @openerpweb.jsonrequest
298 def get_session_action(self, req, key):
300 Gets back a previously saved action. This method can return None if the action
301 was saved since too much time (this case should be handled in a smart way).
303 :param key: The key given by save_session_action()
305 :return: The saved action or None.
308 saved_actions = cherrypy.session.get('saved_actions')
309 if not saved_actions:
311 return saved_actions["actions"].get(key)
313 def eval_context_and_domain(session, context, domain=None):
314 e_context = session.eval_context(context)
315 # should we give the evaluated context as an evaluation context to the domain?
316 e_domain = session.eval_domain(domain or [])
318 return e_context, e_domain
320 def load_actions_from_ir_values(req, key, key2, models, meta, context):
321 Values = req.session.model('ir.values')
322 actions = Values.get(key, key2, models, meta, context)
324 return [(id, name, clean_action(action, req.session))
325 for id, name, action in actions]
327 def clean_action(action, session):
328 if action['type'] != 'ir.actions.act_window':
330 # values come from the server, we can just eval them
331 if isinstance(action.get('context', None), basestring):
332 action['context'] = eval(
334 session.evaluation_context()) or {}
336 if isinstance(action.get('domain', None), basestring):
337 action['domain'] = eval(
339 session.evaluation_context(
340 action['context'])) or []
341 if 'flags' not in action:
342 # Set empty flags dictionary for web client.
343 action['flags'] = dict()
344 return fix_view_modes(action)
346 def generate_views(action):
348 While the server generates a sequence called "views" computing dependencies
349 between a bunch of stuff for views coming directly from the database
350 (the ``ir.actions.act_window model``), it's also possible for e.g. buttons
351 to return custom view dictionaries generated on the fly.
353 In that case, there is no ``views`` key available on the action.
355 Since the web client relies on ``action['views']``, generate it here from
356 ``view_mode`` and ``view_id``.
358 Currently handles two different cases:
360 * no view_id, multiple view_mode
361 * single view_id, single view_mode
363 :param dict action: action descriptor dictionary to generate a views key for
365 view_id = action.get('view_id', False)
366 if isinstance(view_id, (list, tuple)):
369 # providing at least one view mode is a requirement, not an option
370 view_modes = action['view_mode'].split(',')
372 if len(view_modes) > 1:
374 raise ValueError('Non-db action dictionaries should provide '
375 'either multiple view modes or a single view '
376 'mode and an optional view id.\n\n Got view '
377 'modes %r and view id %r for action %r' % (
378 view_modes, view_id, action))
379 action['views'] = [(False, mode) for mode in view_modes]
381 action['views'] = [(view_id, view_modes[0])]
384 def fix_view_modes(action):
385 """ For historical reasons, OpenERP has weird dealings in relation to
386 view_mode and the view_type attribute (on window actions):
388 * one of the view modes is ``tree``, which stands for both list views
390 * the choice is made by checking ``view_type``, which is either
391 ``form`` for a list view or ``tree`` for an actual tree view
393 This methods simply folds the view_type into view_mode by adding a
394 new view mode ``list`` which is the result of the ``tree`` view_mode
395 in conjunction with the ``form`` view_type.
397 TODO: this should go into the doc, some kind of "peculiarities" section
399 :param dict action: an action descriptor
400 :returns: nothing, the action is modified in place
402 if 'views' not in action:
403 generate_views(action)
405 if action.pop('view_type') != 'form':
409 [id, mode if mode != 'tree' else 'list']
410 for id, mode in action['views']
415 class Menu(openerpweb.Controller):
416 _cp_path = "/base/menu"
418 @openerpweb.jsonrequest
420 return {'data': self.do_load(req)}
422 def do_load(self, req):
423 """ Loads all menu items (all applications and their sub-menus).
425 :param req: A request object, with an OpenERP session attribute
426 :type req: < session -> OpenERPSession >
427 :return: the menu root
428 :rtype: dict('children': menu_nodes)
430 Menus = req.session.model('ir.ui.menu')
431 # menus are loaded fully unlike a regular tree view, cause there are
432 # less than 512 items
433 context = req.session.eval_context(req.context)
434 menu_ids = Menus.search([], 0, False, False, context)
435 menu_items = Menus.read(menu_ids, ['name', 'sequence', 'parent_id'], context)
436 menu_root = {'id': False, 'name': 'root', 'parent_id': [-1, '']}
437 menu_items.append(menu_root)
439 # make a tree using parent_id
440 menu_items_map = dict((menu_item["id"], menu_item) for menu_item in menu_items)
441 for menu_item in menu_items:
442 if menu_item['parent_id']:
443 parent = menu_item['parent_id'][0]
446 if parent in menu_items_map:
447 menu_items_map[parent].setdefault(
448 'children', []).append(menu_item)
450 # sort by sequence a tree using parent_id
451 for menu_item in menu_items:
452 menu_item.setdefault('children', []).sort(
453 key=lambda x:x["sequence"])
457 @openerpweb.jsonrequest
458 def action(self, req, menu_id):
459 actions = load_actions_from_ir_values(req,'action', 'tree_but_open',
460 [('ir.ui.menu', menu_id)], False,
461 req.session.eval_context(req.context))
462 return {"action": actions}
464 class DataSet(openerpweb.Controller):
465 _cp_path = "/base/dataset"
467 @openerpweb.jsonrequest
468 def fields(self, req, model):
469 return {'fields': req.session.model(model).fields_get(False,
470 req.session.eval_context(req.context))}
472 @openerpweb.jsonrequest
473 def search_read(self, request, model, fields=False, offset=0, limit=False, domain=None, sort=None):
474 return self.do_search_read(request, model, fields, offset, limit, domain, sort)
475 def do_search_read(self, request, model, fields=False, offset=0, limit=False, domain=None
477 """ Performs a search() followed by a read() (if needed) using the
478 provided search criteria
480 :param request: a JSON-RPC request object
481 :type request: openerpweb.JsonRequest
482 :param str model: the name of the model to search on
483 :param fields: a list of the fields to return in the result records
485 :param int offset: from which index should the results start being returned
486 :param int limit: the maximum number of records to return
487 :param list domain: the search domain for the query
488 :param list sort: sorting directives
489 :returns: A structure (dict) with two keys: ids (all the ids matching
490 the (domain, context) pair) and records (paginated records
491 matching fields selection set)
494 Model = request.session.model(model)
495 context, domain = eval_context_and_domain(
496 request.session, request.context, domain)
498 ids = Model.search(domain, 0, False, sort or False, context)
499 # need to fill the dataset with all ids for the (domain, context) pair,
500 # so search un-paginated and paginate manually before reading
501 paginated_ids = ids[offset:(offset + limit if limit else None)]
502 if fields and fields == ['id']:
503 # shortcut read if we only want the ids
506 'records': map(lambda id: {'id': id}, paginated_ids)
509 records = Model.read(paginated_ids, fields or False, context)
510 records.sort(key=lambda obj: ids.index(obj['id']))
517 @openerpweb.jsonrequest
518 def get(self, request, model, ids, fields=False):
519 return self.do_get(request, model, ids, fields)
520 def do_get(self, request, model, ids, fields=False):
521 """ Fetches and returns the records of the model ``model`` whose ids
524 The results are in the same order as the inputs, but elements may be
525 missing (if there is no record left for the id)
527 :param request: the JSON-RPC2 request object
528 :type request: openerpweb.JsonRequest
529 :param model: the model to read from
531 :param ids: a list of identifiers
533 :param fields: a list of fields to fetch, ``False`` or empty to fetch
534 all fields in the model
535 :type fields: list | False
536 :returns: a list of records, in the same order as the list of ids
539 Model = request.session.model(model)
540 records = Model.read(ids, fields, request.session.eval_context(request.context))
542 record_map = dict((record['id'], record) for record in records)
544 return [record_map[id] for id in ids if record_map.get(id)]
546 @openerpweb.jsonrequest
547 def load(self, req, model, id, fields):
548 m = req.session.model(model)
550 r = m.read([id], False, req.session.eval_context(req.context))
553 return {'value': value}
555 @openerpweb.jsonrequest
556 def create(self, req, model, data):
557 m = req.session.model(model)
558 r = m.create(data, req.session.eval_context(req.context))
561 @openerpweb.jsonrequest
562 def save(self, req, model, id, data):
563 m = req.session.model(model)
564 r = m.write([id], data, req.session.eval_context(req.context))
567 @openerpweb.jsonrequest
568 def unlink(self, request, model, ids=()):
569 Model = request.session.model(model)
570 return Model.unlink(ids, request.session.eval_context(request.context))
572 def call_common(self, req, model, method, args, domain_id=None, context_id=None):
573 domain = args[domain_id] if domain_id and len(args) - 1 >= domain_id else []
574 context = args[context_id] if context_id and len(args) - 1 >= context_id else {}
575 c, d = eval_context_and_domain(req.session, context, domain)
576 if domain_id and len(args) - 1 >= domain_id:
578 if context_id and len(args) - 1 >= context_id:
581 return getattr(req.session.model(model), method)(*args)
583 @openerpweb.jsonrequest
584 def call(self, req, model, method, args, domain_id=None, context_id=None):
585 return {'result': self.call_common(req, model, method, args, domain_id, context_id)}
587 @openerpweb.jsonrequest
588 def call_button(self, req, model, method, args, domain_id=None, context_id=None):
589 action = self.call_common(req, model, method, args, domain_id, context_id)
590 if isinstance(action, dict) and action.get('type') != '':
591 return {'result': clean_action(action, req.session)}
592 return {'result': False}
594 @openerpweb.jsonrequest
595 def exec_workflow(self, req, model, id, signal):
596 r = req.session.exec_workflow(model, id, signal)
599 @openerpweb.jsonrequest
600 def default_get(self, req, model, fields):
601 m = req.session.model(model)
602 r = m.default_get(fields, req.session.eval_context(req.context))
605 class DataGroup(openerpweb.Controller):
606 _cp_path = "/base/group"
607 @openerpweb.jsonrequest
608 def read(self, request, model, fields, group_by_fields, domain=None):
609 Model = request.session.model(model)
610 context, domain = eval_context_and_domain(request.session, request.context, domain)
612 return Model.read_group(
613 domain or [], fields, group_by_fields, 0, False,
614 dict(context, group_by=group_by_fields))
616 class View(openerpweb.Controller):
617 _cp_path = "/base/view"
619 def fields_view_get(self, request, model, view_id, view_type,
620 transform=True, toolbar=False, submenu=False):
621 Model = request.session.model(model)
622 context = request.session.eval_context(request.context)
623 fvg = Model.fields_view_get(view_id, view_type, context, toolbar, submenu)
624 # todo fme?: check that we should pass the evaluated context here
625 self.process_view(request.session, fvg, context, transform)
628 def process_view(self, session, fvg, context, transform):
629 # depending on how it feels, xmlrpclib.ServerProxy can translate
630 # XML-RPC strings to ``str`` or ``unicode``. ElementTree does not
631 # enjoy unicode strings which can not be trivially converted to
632 # strings, and it blows up during parsing.
634 # So ensure we fix this retardation by converting view xml back to
636 if isinstance(fvg['arch'], unicode):
637 arch = fvg['arch'].encode('utf-8')
642 evaluation_context = session.evaluation_context(context or {})
643 xml = self.transform_view(arch, session, evaluation_context)
645 xml = ElementTree.fromstring(arch)
646 fvg['arch'] = Xml2Json.convert_element(xml)
647 for field in fvg['fields'].values():
648 if field.has_key('views') and field['views']:
649 for view in field["views"].values():
650 self.process_view(session, view, None, transform)
652 @openerpweb.jsonrequest
653 def add_custom(self, request, view_id, arch):
654 CustomView = request.session.model('ir.ui.view.custom')
656 'user_id': request.session._uid,
659 }, request.session.eval_context(request.context))
660 return {'result': True}
662 @openerpweb.jsonrequest
663 def undo_custom(self, request, view_id, reset=False):
664 CustomView = request.session.model('ir.ui.view.custom')
665 context = request.session.eval_context(request.context)
666 vcustom = CustomView.search([('user_id', '=', request.session._uid), ('ref_id' ,'=', view_id)],
667 0, False, False, context)
670 CustomView.unlink(vcustom, context)
672 CustomView.unlink([vcustom[0]], context)
673 return {'result': True}
674 return {'result': False}
676 def transform_view(self, view_string, session, context=None):
677 # transform nodes on the fly via iterparse, instead of
678 # doing it statically on the parsing result
679 parser = ElementTree.iterparse(StringIO(view_string), events=("start",))
681 for event, elem in parser:
685 self.parse_domains_and_contexts(elem, session)
688 def parse_domain(self, elem, attr_name, session):
689 """ Parses an attribute of the provided name as a domain, transforms it
690 to either a literal domain or a :class:`openerpweb.nonliterals.Domain`
692 :param elem: the node being parsed
693 :type param: xml.etree.ElementTree.Element
694 :param str attr_name: the name of the attribute which should be parsed
695 :param session: Current OpenERP session
696 :type session: openerpweb.openerpweb.OpenERPSession
698 domain = elem.get(attr_name, '').strip()
703 openerpweb.ast.literal_eval(
708 openerpweb.nonliterals.Domain(session, domain))
710 def parse_domains_and_contexts(self, elem, session):
711 """ Converts domains and contexts from the view into Python objects,
712 either literals if they can be parsed by literal_eval or a special
713 placeholder object if the domain or context refers to free variables.
715 :param elem: the current node being parsed
716 :type param: xml.etree.ElementTree.Element
717 :param session: OpenERP session object, used to store and retrieve
719 :type session: openerpweb.openerpweb.OpenERPSession
721 self.parse_domain(elem, 'domain', session)
722 self.parse_domain(elem, 'filter_domain', session)
723 for el in ['context', 'default_get']:
724 context_string = elem.get(el, '').strip()
728 openerpweb.ast.literal_eval(context_string))
731 openerpweb.nonliterals.Context(
732 session, context_string))
734 class FormView(View):
735 _cp_path = "/base/formview"
737 @openerpweb.jsonrequest
738 def load(self, req, model, view_id, toolbar=False):
739 fields_view = self.fields_view_get(req, model, view_id, 'form', toolbar=toolbar)
740 return {'fields_view': fields_view}
742 class ListView(View):
743 _cp_path = "/base/listview"
745 @openerpweb.jsonrequest
746 def load(self, req, model, view_id, toolbar=False):
747 fields_view = self.fields_view_get(req, model, view_id, 'tree', toolbar=toolbar)
748 return {'fields_view': fields_view}
750 def process_colors(self, view, row, context):
751 colors = view['arch']['attrs'].get('colors')
758 for pair in colors.split(';')
759 if eval(pair.split(':')[1], dict(context, **row))
764 elif len(color) == 1:
768 class SearchView(View):
769 _cp_path = "/base/searchview"
771 @openerpweb.jsonrequest
772 def load(self, req, model, view_id):
773 fields_view = self.fields_view_get(req, model, view_id, 'search')
774 return {'fields_view': fields_view}
776 @openerpweb.jsonrequest
777 def fields_get(self, req, model):
778 Model = req.session.model(model)
779 fields = Model.fields_get(False, req.session.eval_context(req.context))
780 return {'fields': fields}
782 class Binary(openerpweb.Controller):
783 _cp_path = "/base/binary"
785 @openerpweb.httprequest
786 def image(self, request, session_id, model, id, field, **kw):
787 cherrypy.response.headers['Content-Type'] = 'image/png'
788 Model = request.session.model(model)
789 context = request.session.eval_context(request.context)
792 res = Model.default_get([field], context).get(field, '')
794 res = Model.read([int(id)], [field], context)[0].get(field, '')
795 return base64.decodestring(res)
796 except: # TODO: what's the exception here?
797 return self.placeholder()
798 def placeholder(self):
799 return open(os.path.join(openerpweb.path_addons, 'base', 'static', 'src', 'img', 'placeholder.png'), 'rb').read()
801 @openerpweb.httprequest
802 def saveas(self, request, session_id, model, id, field, fieldname, **kw):
803 Model = request.session.model(model)
804 context = request.session.eval_context(request.context)
805 res = Model.read([int(id)], [field, fieldname], context)[0]
806 filecontent = res.get(field, '')
808 raise cherrypy.NotFound
810 cherrypy.response.headers['Content-Type'] = 'application/octet-stream'
811 filename = '%s_%s' % (model.replace('.', '_'), id)
813 filename = res.get(fieldname, '') or filename
814 cherrypy.response.headers['Content-Disposition'] = 'attachment; filename=' + filename
815 return base64.decodestring(filecontent)
817 @openerpweb.httprequest
818 def upload(self, request, session_id, callback, ufile=None):
819 cherrypy.response.timeout = 500
821 for key, val in cherrypy.request.headers.iteritems():
822 headers[key.lower()] = val
823 size = int(headers.get('content-length', 0))
824 # TODO: might be useful to have a configuration flag for max-length file uploads
826 out = """<script language="javascript" type="text/javascript">
827 var win = window.top.window,
829 if (typeof(callback) === 'function') {
830 callback.apply(this, %s);
832 win.jQuery('#oe_notification', win.document).notify('create', {
833 title: "Ajax File Upload",
834 text: "Could not find callback"
838 data = ufile.file.read()
839 args = [size, ufile.filename, ufile.headers.getheader('Content-Type'), base64.encodestring(data)]
841 args = [False, e.message]
842 return out % (simplejson.dumps(callback), simplejson.dumps(args))
844 @openerpweb.httprequest
845 def upload_attachment(self, request, session_id, callback, model, id, ufile=None):
846 cherrypy.response.timeout = 500
847 context = request.session.eval_context(request.context)
848 Model = request.session.model('ir.attachment')
850 out = """<script language="javascript" type="text/javascript">
851 var win = window.top.window,
853 if (typeof(callback) === 'function') {
854 callback.call(this, %s);
857 attachment_id = Model.create({
858 'name': ufile.filename,
859 'datas': base64.encodestring(ufile.file.read()),
864 'filename': ufile.filename,
868 args = { 'error': e.message }
869 return out % (simplejson.dumps(callback), simplejson.dumps(args))
871 class Action(openerpweb.Controller):
872 _cp_path = "/base/action"
874 @openerpweb.jsonrequest
875 def load(self, req, action_id):
876 Actions = req.session.model('ir.actions.actions')
878 context = req.session.eval_context(req.context)
879 action_type = Actions.read([action_id], ['type'], context)
881 action = req.session.model(action_type[0]['type']).read([action_id], False,
884 value = clean_action(action[0], req.session)
885 return {'result': value}
887 @openerpweb.jsonrequest
888 def run(self, req, action_id):
889 return clean_action(req.session.model('ir.actions.server').run(
890 [action_id], req.session.eval_context(req.context)), req.session)