[IMP]Onclick of category display product details in listview
[odoo/odoo.git] / addons / base / controllers / main.py
1 # -*- coding: utf-8 -*-
2 import base64
3 import glob, os
4 import pprint
5 from xml.etree import ElementTree
6 from cStringIO import StringIO
7
8 import simplejson
9
10 import openerpweb
11 import openerpweb.ast
12 import openerpweb.nonliterals
13
14 import cherrypy
15
16 # Should move to openerpweb.Xml2Json
17 class Xml2Json:
18     # xml2json-direct
19     # Simple and straightforward XML-to-JSON converter in Python
20     # New BSD Licensed
21     #
22     # URL: http://code.google.com/p/xml2json-direct/
23     @staticmethod
24     def convert_to_json(s):
25         return simplejson.dumps(
26             Xml2Json.convert_to_structure(s), sort_keys=True, indent=4)
27
28     @staticmethod
29     def convert_to_structure(s):
30         root = ElementTree.fromstring(s)
31         return Xml2Json.convert_element(root)
32
33     @staticmethod
34     def convert_element(el, skip_whitespaces=True):
35         res = {}
36         if el.tag[0] == "{":
37             ns, name = el.tag.rsplit("}", 1)
38             res["tag"] = name
39             res["namespace"] = ns[1:]
40         else:
41             res["tag"] = el.tag
42         res["attrs"] = {}
43         for k, v in el.items():
44             res["attrs"][k] = v
45         kids = []
46         if el.text and (not skip_whitespaces or el.text.strip() != ''):
47             kids.append(el.text)
48         for kid in el:
49             kids.append(Xml2Json.convert_element(kid))
50             if kid.tail and (not skip_whitespaces or kid.tail.strip() != ''):
51                 kids.append(kid.tail)
52         res["children"] = kids
53         return res
54
55 #----------------------------------------------------------
56 # OpenERP Web base Controllers
57 #----------------------------------------------------------
58
59 class Session(openerpweb.Controller):
60     _cp_path = "/base/session"
61
62     def manifest_glob(self, addons, key):
63         files = []
64         for addon in addons:
65             globlist = openerpweb.addons_manifest.get(addon, {}).get(key, [])
66
67             files.extend([
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))
72             ])
73         return files
74
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)
80         """
81         root = openerpweb.path_root
82         files_content = []
83         files_timestamp = 0
84         for i in file_list:
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)
91         return files_concat
92
93     @openerpweb.jsonrequest
94     def login(self, req, db, login, password):
95         req.session.login(db, login, password)
96
97         return {
98             "session_id": req.session_id,
99             "uid": req.session._uid,
100         }
101
102     @openerpweb.jsonrequest
103     def get_databases_list(self, req):
104         proxy = req.session.proxy("db")
105         dbs = proxy.list()
106
107         return {"db_list": dbs}
108
109     @openerpweb.jsonrequest
110     def modules(self, req):
111         return {"modules": [name
112             for name, manifest in openerpweb.addons_manifest.iteritems()
113             if manifest.get('active', True)]}
114
115     @openerpweb.jsonrequest
116     def csslist(self, req, mods='base'):
117         return {'files': self.manifest_glob(mods.split(','), 'css')}
118
119     @openerpweb.jsonrequest
120     def jslist(self, req, mods='base'):
121         return {'files': self.manifest_glob(mods.split(','), 'js')}
122
123     def css(self, req, mods='base,base_hello'):
124         files = self.manifest_glob(mods.split(','), 'css')
125         concat = self.concat_files(files)[0]
126         # TODO request set the Date of last modif and Etag
127         return concat
128     css.exposed = True
129
130     def js(self, req, mods='base,base_hello'):
131         files = self.manifest_glob(mods.split(','), 'js')
132         concat = self.concat_files(files)[0]
133         # TODO request set the Date of last modif and Etag
134         return concat
135     js.exposed = True
136
137     @openerpweb.jsonrequest
138     def eval_domain_and_context(self, req, contexts, domains,
139                                 group_by_seq=None):
140         """ Evaluates sequences of domains and contexts, composing them into
141         a single context, domain or group_by sequence.
142
143         :param list contexts: list of contexts to merge together. Contexts are
144                               evaluated in sequence, all previous contexts
145                               are part of their own evaluation context
146                               (starting at the session context).
147         :param list domains: list of domains to merge together. Domains are
148                              evaluated in sequence and appended to one another
149                              (implicit AND), their evaluation domain is the
150                              result of merging all contexts.
151         :param list group_by_seq: list of domains (which may be in a different
152                                   order than the ``contexts`` parameter),
153                                   evaluated in sequence, their ``'group_by'``
154                                   key is extracted if they have one.
155         :returns:
156             a 3-dict of:
157
158             context (``dict``)
159                 the global context created by merging all of
160                 ``contexts``
161
162             domain (``list``)
163                 the concatenation of all domains
164
165             group_by (``list``)
166                 a list of fields to group by, potentially empty (in which case
167                 no group by should be performed)
168         """
169         context = req.session.eval_contexts(contexts)
170         domain = req.session.eval_domains(domains, context)
171
172         group_by_sequence = []
173         for candidate in (group_by_seq or []):
174             ctx = req.session.eval_context(candidate, context)
175             group_by = ctx.get('group_by')
176             if not group_by:
177                 continue
178             elif isinstance(group_by, basestring):
179                 group_by_sequence.append(group_by)
180             else:
181                 group_by_sequence.extend(group_by)
182
183         return {
184             'context': context,
185             'domain': domain,
186             'group_by': group_by_sequence
187         }
188
189     @openerpweb.jsonrequest
190     def save_session_action(self, req, the_action):
191         """
192         This method store an action object in the session object and returns an integer
193         identifying that action. The method get_session_action() can be used to get
194         back the action.
195
196         :param the_action: The action to save in the session.
197         :type the_action: anything
198         :return: A key identifying the saved action.
199         :rtype: integer
200         """
201         saved_actions = cherrypy.session.get('saved_actions')
202         if not saved_actions:
203             saved_actions = {"next":0, "actions":{}}
204             cherrypy.session['saved_actions'] = saved_actions
205         # we don't allow more than 10 stored actions
206         if len(saved_actions["actions"]) >= 10:
207             del saved_actions["actions"][min(saved_actions["actions"].keys())]
208         key = saved_actions["next"]
209         saved_actions["actions"][key] = the_action
210         saved_actions["next"] = key + 1
211         return key
212
213     @openerpweb.jsonrequest
214     def get_session_action(self, req, key):
215         """
216         Gets back a previously saved action. This method can return None if the action
217         was saved since too much time (this case should be handled in a smart way).
218
219         :param key: The key given by save_session_action()
220         :type key: integer
221         :return: The saved action or None.
222         :rtype: anything
223         """
224         saved_actions = cherrypy.session.get('saved_actions')
225         if not saved_actions:
226             return None
227         return saved_actions["actions"].get(key)
228
229
230 def load_actions_from_ir_values(req, key, key2, models, meta, context):
231     Values = req.session.model('ir.values')
232     actions = Values.get(key, key2, models, meta, context)
233
234     for _, _, action in actions:
235         clean_action(action, req.session)
236
237     return actions
238
239 def clean_action(action, session):
240     # values come from the server, we can just eval them
241     if isinstance(action['context'], basestring):
242         action['context'] = eval(
243             action['context'],
244             session.evaluation_context()) or {}
245
246     if isinstance(action['domain'], basestring):
247         action['domain'] = eval(
248             action['domain'],
249             session.evaluation_context(
250                 action['context'])) or []
251     if not action.has_key('flags'):
252         # Set empty flags dictionary for web client.
253         action['flags'] = dict()
254     return fix_view_modes(action)
255
256 def fix_view_modes(action):
257     """ For historical reasons, OpenERP has weird dealings in relation to
258     view_mode and the view_type attribute (on window actions):
259
260     * one of the view modes is ``tree``, which stands for both list views
261       and tree views
262     * the choice is made by checking ``view_type``, which is either
263       ``form`` for a list view or ``tree`` for an actual tree view
264
265     This methods simply folds the view_type into view_mode by adding a
266     new view mode ``list`` which is the result of the ``tree`` view_mode
267     in conjunction with the ``form`` view_type.
268
269     TODO: this should go into the doc, some kind of "peculiarities" section
270
271     :param dict action: an action descriptor
272     :returns: nothing, the action is modified in place
273     """
274     if action.pop('view_type') != 'form':
275         return
276
277     action['view_mode'] = ','.join(
278         mode if mode != 'tree' else 'list'
279         for mode in action['view_mode'].split(','))
280     action['views'] = [
281         [id, mode if mode != 'tree' else 'list']
282         for id, mode in action['views']
283     ]
284     return action
285
286 class Menu(openerpweb.Controller):
287     _cp_path = "/base/menu"
288
289     @openerpweb.jsonrequest
290     def load(self, req):
291         return {'data': self.do_load(req)}
292
293     def do_load(self, req):
294         """ Loads all menu items (all applications and their sub-menus).
295
296         :param req: A request object, with an OpenERP session attribute
297         :type req: < session -> OpenERPSession >
298         :return: the menu root
299         :rtype: dict('children': menu_nodes)
300         """
301         Menus = req.session.model('ir.ui.menu')
302         # menus are loaded fully unlike a regular tree view, cause there are
303         # less than 512 items
304         menu_ids = Menus.search([])
305         menu_items = Menus.read(menu_ids, ['name', 'sequence', 'parent_id'])
306         menu_root = {'id': False, 'name': 'root', 'parent_id': [-1, '']}
307         menu_items.append(menu_root)
308
309         # make a tree using parent_id
310         menu_items_map = dict((menu_item["id"], menu_item) for menu_item in menu_items)
311         for menu_item in menu_items:
312             if menu_item['parent_id']:
313                 parent = menu_item['parent_id'][0]
314             else:
315                 parent = False
316             if parent in menu_items_map:
317                 menu_items_map[parent].setdefault(
318                     'children', []).append(menu_item)
319
320         # sort by sequence a tree using parent_id
321         for menu_item in menu_items:
322             menu_item.setdefault('children', []).sort(
323                 key=lambda x:x["sequence"])
324
325         return menu_root
326
327     @openerpweb.jsonrequest
328     def action(self, req, menu_id):
329         actions = load_actions_from_ir_values(req,'action', 'tree_but_open',
330                                              [('ir.ui.menu', menu_id)], False, {})
331
332         return {"action": actions}
333
334 class DataSet(openerpweb.Controller):
335     _cp_path = "/base/dataset"
336
337     @openerpweb.jsonrequest
338     def fields(self, req, model):
339         return {'fields': req.session.model(model).fields_get()}
340
341     @openerpweb.jsonrequest
342     def search_read(self, request, model, fields=False, offset=0, limit=False, domain=None, context=None, sort=None):
343         return self.do_search_read(request, model, fields, offset, limit, domain, context, sort)
344     def do_search_read(self, request, model, fields=False, offset=0, limit=False, domain=None, context=None, sort=None):
345         """ Performs a search() followed by a read() (if needed) using the
346         provided search criteria
347
348         :param request: a JSON-RPC request object
349         :type request: openerpweb.JsonRequest
350         :param str model: the name of the model to search on
351         :param fields: a list of the fields to return in the result records
352         :type fields: [str]
353         :param int offset: from which index should the results start being returned
354         :param int limit: the maximum number of records to return
355         :param list domain: the search domain for the query
356         :param list sort: sorting directives
357         :returns: a list of result records
358         :rtype: list
359         """
360         Model = request.session.model(model)
361
362         ids = Model.search(domain or [], offset or 0, limit or False,
363                            sort or False, request.context)
364
365         if fields and fields == ['id']:
366             # shortcut read if we only want the ids
367             return map(lambda id: {'id': id}, ids)
368
369         reads = Model.read(ids, fields or False, request.context)
370         reads.sort(key=lambda obj: ids.index(obj['id']))
371         return reads
372
373     @openerpweb.jsonrequest
374     def read(self, request, model, ids, fields=False):
375         return self.do_search_read(request, model, ids, fields)
376
377     @openerpweb.jsonrequest
378     def get(self, request, model, ids, fields=False):
379         return self.do_get(request, model, ids, fields)
380
381     def do_get(self, request, model, ids, fields=False):
382         """ Fetches and returns the records of the model ``model`` whose ids
383         are in ``ids``.
384
385         The results are in the same order as the inputs, but elements may be
386         missing (if there is no record left for the id)
387
388         :param request: the JSON-RPC2 request object
389         :type request: openerpweb.JsonRequest
390         :param model: the model to read from
391         :type model: str
392         :param ids: a list of identifiers
393         :type ids: list
394         :param fields: a list of fields to fetch, ``False`` or empty to fetch
395                        all fields in the model
396         :type fields: list | False
397         :returns: a list of records, in the same order as the list of ids
398         :rtype: list
399         """
400         Model = request.session.model(model)
401         records = Model.read(ids, fields, request.context)
402
403         record_map = dict((record['id'], record) for record in records)
404
405         return [record_map[id] for id in ids if record_map.get(id)]
406
407     @openerpweb.jsonrequest
408     def load(self, req, model, id, fields):
409         m = req.session.model(model)
410         value = {}
411         r = m.read([id])
412         if r:
413             value = r[0]
414         return {'value': value}
415
416     @openerpweb.jsonrequest
417     def create(self, req, model, data, context={}):
418         m = req.session.model(model)
419         r = m.create(data, context)
420         return {'result': r}
421
422     @openerpweb.jsonrequest
423     def save(self, req, model, id, data, context={}):
424         m = req.session.model(model)
425         r = m.write([id], data, context)
426         return {'result': r}
427
428     @openerpweb.jsonrequest
429     def unlink(self, request, model, ids=[]):
430         Model = request.session.model(model)
431         return Model.unlink(ids)
432
433     @openerpweb.jsonrequest
434     def call(self, req, model, method, args):
435         m = req.session.model(model)
436         r = getattr(m, method)(*args)
437         return {'result': r}
438
439     @openerpweb.jsonrequest
440     def exec_workflow(self, req, model, id, signal):
441         r = req.session.exec_workflow(model, id, signal)
442         return {'result': r}
443
444     @openerpweb.jsonrequest
445     def default_get(self, req, model, fields, context={}):
446         m = req.session.model(model)
447         r = m.default_get(fields, context)
448         return {'result': r}
449
450     @openerpweb.jsonrequest
451     def name_search(self, req, model, search_str, domain=[], context={}):
452         m = req.session.model(model)
453         r = m.name_search(search_str+'%', domain, '=ilike', context)
454         return {'result': r}
455
456 class DataGroup(openerpweb.Controller):
457     _cp_path = "/base/group"
458     @openerpweb.jsonrequest
459     def read(self, request, model, group_by_fields, domain=None):
460         Model = request.session.model(model)
461
462         return Model.read_group(
463             domain or [], False, group_by_fields, 0, False,
464             dict(request.context, group_by=group_by_fields))
465
466 class View(openerpweb.Controller):
467     _cp_path = "/base/view"
468
469     def fields_view_get(self, request, model, view_id, view_type,
470                         transform=True, toolbar=False, submenu=False):
471         Model = request.session.model(model)
472         fvg = Model.fields_view_get(view_id, view_type, request.context,
473                                     toolbar, submenu)
474         self.process_view(request.session, fvg, request.context, transform)
475         return fvg
476
477     def process_view(self, session, fvg, context, transform):
478         if transform:
479             evaluation_context = session.evaluation_context(context or {})
480             xml = self.transform_view(fvg['arch'], session, evaluation_context)
481         else:
482             xml = ElementTree.fromstring(fvg['arch'])
483         fvg['arch'] = Xml2Json.convert_element(xml)
484         for field in fvg['fields'].values():
485             if field.has_key('views') and field['views']:
486                 for view in field["views"].values():
487                     self.process_view(session, view, None, transform)
488
489     @openerpweb.jsonrequest
490     def add_custom(self, request, view_id, arch):
491         CustomView = request.session.model('ir.ui.view.custom')
492         CustomView.create({
493             'user_id': request.session._uid,
494             'ref_id': view_id,
495             'arch': arch
496         })
497         return {'result': True}
498
499     @openerpweb.jsonrequest
500     def undo_custom(self, request, view_id, reset=False):
501         CustomView = request.session.model('ir.ui.view.custom')
502         vcustom = CustomView.search([('user_id', '=', request.session._uid), ('ref_id' ,'=', view_id)])
503         if vcustom:
504             if reset:
505                 CustomView.unlink(vcustom)
506             else:
507                 CustomView.unlink([vcustom[0]])
508             return {'result': True}
509         return {'result': False}
510
511     def normalize_attrs(self, elem, context):
512         """ Normalize @attrs, @invisible, @required, @readonly and @states, so
513         the client only has to deal with @attrs.
514
515         See `the discoveries pad <http://pad.openerp.com/discoveries>`_ for
516         the rationale.
517
518         :param elem: the current view node (Python object)
519         :type elem: xml.etree.ElementTree.Element
520         :param dict context: evaluation context
521         """
522         # If @attrs is normalized in json by server, the eval should be replaced by simplejson.loads
523         attrs = openerpweb.ast.literal_eval(elem.get('attrs', '{}'))
524         if 'states' in elem.attrib:
525             attrs.setdefault('invisible', [])\
526                 .append(('state', 'not in', elem.attrib.pop('states').split(',')))
527         if attrs:
528             elem.set('attrs', simplejson.dumps(attrs))
529         for a in ['invisible', 'readonly', 'required']:
530             if a in elem.attrib:
531                 # In the XML we trust
532                 avalue = bool(eval(elem.get(a, 'False'),
533                                    {'context': context or {}}))
534                 if not avalue:
535                     del elem.attrib[a]
536                 else:
537                     elem.attrib[a] = '1'
538                     if a == 'invisible' and 'attrs' in elem.attrib:
539                         del elem.attrib['attrs']
540
541     def transform_view(self, view_string, session, context=None):
542         # transform nodes on the fly via iterparse, instead of
543         # doing it statically on the parsing result
544         parser = ElementTree.iterparse(StringIO(view_string), events=("start",))
545         root = None
546         for event, elem in parser:
547             if event == "start":
548                 if root is None:
549                     root = elem
550                 self.normalize_attrs(elem, context)
551                 self.parse_domains_and_contexts(elem, session)
552         return root
553
554     def parse_domain(self, elem, attr_name, session):
555         """ Parses an attribute of the provided name as a domain, transforms it
556         to either a literal domain or a :class:`openerpweb.nonliterals.Domain`
557
558         :param elem: the node being parsed
559         :type param: xml.etree.ElementTree.Element
560         :param str attr_name: the name of the attribute which should be parsed
561         :param session: Current OpenERP session
562         :type session: openerpweb.openerpweb.OpenERPSession
563         """
564         domain = elem.get(attr_name, '').strip()
565         if domain:
566             try:
567                 elem.set(
568                     attr_name,
569                     openerpweb.ast.literal_eval(
570                         domain))
571             except ValueError:
572                 # not a literal
573                 elem.set(attr_name,
574                          openerpweb.nonliterals.Domain(session, domain))
575
576     def parse_domains_and_contexts(self, elem, session):
577         """ Converts domains and contexts from the view into Python objects,
578         either literals if they can be parsed by literal_eval or a special
579         placeholder object if the domain or context refers to free variables.
580
581         :param elem: the current node being parsed
582         :type param: xml.etree.ElementTree.Element
583         :param session: OpenERP session object, used to store and retrieve
584                         non-literal objects
585         :type session: openerpweb.openerpweb.OpenERPSession
586         """
587         self.parse_domain(elem, 'domain', session)
588         self.parse_domain(elem, 'filter_domain', session)
589         context_string = elem.get('context', '').strip()
590         if context_string:
591             try:
592                 elem.set('context',
593                          openerpweb.ast.literal_eval(context_string))
594             except ValueError:
595                 elem.set('context',
596                          openerpweb.nonliterals.Context(
597                              session, context_string))
598
599 class FormView(View):
600     _cp_path = "/base/formview"
601
602     @openerpweb.jsonrequest
603     def load(self, req, model, view_id, toolbar=False):
604         fields_view = self.fields_view_get(req, model, view_id, 'form', toolbar=toolbar)
605         return {'fields_view': fields_view}
606
607 class ListView(View):
608     _cp_path = "/base/listview"
609
610     @openerpweb.jsonrequest
611     def load(self, req, model, view_id, toolbar=False):
612         fields_view = self.fields_view_get(req, model, view_id, 'tree', toolbar=toolbar)
613         return {'fields_view': fields_view}
614
615     def process_colors(self, view, row, context):
616         colors = view['arch']['attrs'].get('colors')
617
618         if not colors:
619             return None
620
621         color = [
622             pair.split(':')[0]
623             for pair in colors.split(';')
624             if eval(pair.split(':')[1], dict(context, **row))
625         ]
626
627         if not color:
628             return None
629         elif len(color) == 1:
630             return color[0]
631         return 'maroon'
632
633 class SearchView(View):
634     _cp_path = "/base/searchview"
635
636     @openerpweb.jsonrequest
637     def load(self, req, model, view_id):
638         fields_view = self.fields_view_get(req, model, view_id, 'search')
639         return {'fields_view': fields_view}
640
641     @openerpweb.jsonrequest
642     def fields_get(self, req, model):
643         Model = req.session.model(model)
644         fields = Model.fields_get()
645         return {'fields': fields}
646
647 class Binary(openerpweb.Controller):
648     _cp_path = "/base/binary"
649
650     @openerpweb.httprequest
651     def image(self, request, session_id, model, id, field, **kw):
652         cherrypy.response.headers['Content-Type'] = 'image/png'
653         Model = request.session.model(model)
654         try:
655             if not id:
656                 res = Model.default_get([field], request.context).get(field, '')
657             else:
658                 res = Model.read([int(id)], [field], request.context)[0].get(field, '')
659             return base64.decodestring(res)
660         except: # TODO: what's the exception here?
661             return self.placeholder()
662     def placeholder(self):
663         return open(os.path.join(openerpweb.path_addons, 'base', 'static', 'src', 'img', 'placeholder.png'), 'rb').read()
664
665     @openerpweb.httprequest
666     def saveas(self, request, session_id, model, id, field, fieldname, **kw):
667         Model = request.session.model(model)
668         res = Model.read([int(id)], [field, fieldname])[0]
669         filecontent = res.get(field, '')
670         if not filecontent:
671             raise cherrypy.NotFound
672         else:
673             cherrypy.response.headers['Content-Type'] = 'application/octet-stream'
674             filename = '%s_%s' % (model.replace('.', '_'), id)
675             if fieldname:
676                 filename = res.get(fieldname, '') or filename
677             cherrypy.response.headers['Content-Disposition'] = 'attachment; filename=' +  filename
678             return base64.decodestring(filecontent)
679
680     @openerpweb.httprequest
681     def upload(self, request, session_id, callback, ufile=None):
682         cherrypy.response.timeout = 500
683         headers = {}
684         for key, val in cherrypy.request.headers.iteritems():
685             headers[key.lower()] = val
686         size = int(headers.get('content-length', 0))
687         # TODO: might be usefull to have a configuration flag for max-lenght file uploads
688         try:
689             out = """<script language="javascript" type="text/javascript">
690                         var win = window.top.window,
691                             callback = win[%s];
692                         if (typeof(callback) === 'function') {
693                             callback.apply(this, %s);
694                         } else {
695                             win.jQuery('#oe_notification', win.document).notify('create', {
696                                 title: "Ajax File Upload",
697                                 text: "Could not find callback"
698                             });
699                         }
700                     </script>"""
701             data = ufile.file.read()
702             args = [size, ufile.filename, ufile.headers.getheader('Content-Type'), base64.encodestring(data)]
703         except Exception, e:
704             args = [False, e.message]
705         return out % (simplejson.dumps(callback), simplejson.dumps(args))
706
707     @openerpweb.httprequest
708     def upload_attachment(self, request, session_id, callback, model, id, ufile=None):
709         cherrypy.response.timeout = 500
710         Model = request.session.model('ir.attachment')
711         try:
712             out = """<script language="javascript" type="text/javascript">
713                         var win = window.top.window,
714                             callback = win[%s];
715                         if (typeof(callback) === 'function') {
716                             callback.call(this, %s);
717                         }
718                     </script>"""
719             attachment_id = Model.create({
720                 'name': ufile.filename,
721                 'datas': base64.encodestring(ufile.file.read()),
722                 'res_model': model,
723                 'res_id': int(id)
724             })
725             args = {
726                 'filename': ufile.filename,
727                 'id':  attachment_id
728             }
729         except Exception, e:
730             args = { 'error': e.message }
731         return out % (simplejson.dumps(callback), simplejson.dumps(args))
732
733 class Action(openerpweb.Controller):
734     _cp_path = "/base/action"
735
736     @openerpweb.jsonrequest
737     def load(self, req, action_id):
738         Actions = req.session.model('ir.actions.actions')
739         value = False
740         action_type = Actions.read([action_id], ['type'], req.session.context)
741         if action_type:
742             action = req.session.model(action_type[0]['type']).read([action_id], False, req.session.context)
743             if action:
744                 value = clean_action(action[0], req.session)
745         return {'result': value}
746 class TreeView(View):
747     _cp_path = "/base/treeview"
748
749     @openerpweb.jsonrequest
750     def load(self, req, model, view_id, toolbar=False):
751         Model = req.session.model(model)
752         fields = Model.fields_get()
753         fields_view = self.fields_view_get(req, model, view_id, 'tree', toolbar=toolbar)
754         return {'field_parent': fields_view, 'fields' : fields}
755