[IMP]Implement selection box of list of fields under the header.
[odoo/odoo.git] / addons / base / controllers / main.py
1 # -*- coding: utf-8 -*-
2
3 import base64
4 import csv
5 import glob
6 import operator
7 import os
8 import re
9 import simplejson
10 import textwrap
11 import xmlrpclib
12 from xml.etree import ElementTree
13 from cStringIO import StringIO
14
15 import cherrypy
16
17 import openerpweb
18 import openerpweb.ast
19 import openerpweb.nonliterals
20
21 from babel.messages.pofile import read_po
22
23 # Should move to openerpweb.Xml2Json
24 class Xml2Json:
25     # xml2json-direct
26     # Simple and straightforward XML-to-JSON converter in Python
27     # New BSD Licensed
28     #
29     # URL: http://code.google.com/p/xml2json-direct/
30     @staticmethod
31     def convert_to_json(s):
32         return simplejson.dumps(
33             Xml2Json.convert_to_structure(s), sort_keys=True, indent=4)
34
35     @staticmethod
36     def convert_to_structure(s):
37         root = ElementTree.fromstring(s)
38         return Xml2Json.convert_element(root)
39
40     @staticmethod
41     def convert_element(el, skip_whitespaces=True):
42         res = {}
43         if el.tag[0] == "{":
44             ns, name = el.tag.rsplit("}", 1)
45             res["tag"] = name
46             res["namespace"] = ns[1:]
47         else:
48             res["tag"] = el.tag
49         res["attrs"] = {}
50         for k, v in el.items():
51             res["attrs"][k] = v
52         kids = []
53         if el.text and (not skip_whitespaces or el.text.strip() != ''):
54             kids.append(el.text)
55         for kid in el:
56             kids.append(Xml2Json.convert_element(kid))
57             if kid.tail and (not skip_whitespaces or kid.tail.strip() != ''):
58                 kids.append(kid.tail)
59         res["children"] = kids
60         return res
61
62 #----------------------------------------------------------
63 # OpenERP Web base Controllers
64 #----------------------------------------------------------
65
66 def manifest_glob(addons, key):
67     files = []
68     for addon in addons:
69         globlist = openerpweb.addons_manifest.get(addon, {}).get(key, [])
70         for pattern in globlist:
71             for path in glob.glob(os.path.join(openerpweb.path_addons, addon, pattern)):
72                 files.append(path[len(openerpweb.path_addons):])
73     return files
74
75 def concat_files(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     files_content = []
82     files_timestamp = 0
83     for i in file_list:
84         fname = os.path.join(openerpweb.path_addons, i[1:])
85         ftime = os.path.getmtime(fname)
86         if ftime > files_timestamp:
87             files_timestamp = ftime
88         files_content.append(open(fname).read())
89     files_concat = "".join(files_content)
90     return (files_concat,files_timestamp)
91
92 home_template = textwrap.dedent("""<!DOCTYPE html>
93 <html style="height: 100%%">
94     <head>
95         <meta http-equiv="content-type" content="text/html; charset=utf-8" />
96         <title>OpenERP</title>
97         <link rel="shortcut icon" href="/base/static/src/img/favicon.ico" type="image/x-icon"/>
98         %(css)s
99         <!--[if lte IE 7]>
100         <link rel="stylesheet" href="/base/static/src/css/base-ie7.css" type="text/css"/>
101         <![endif]-->
102         %(javascript)s
103         <script type="text/javascript">
104             $(function() {
105                 QWeb = new QWeb2.Engine();
106                 var c = new openerp.init();
107                 var wc = new c.base.WebClient("oe");
108                 wc.start();
109             });
110         </script>
111     </head>
112     <body id="oe" class="openerp"></body>
113 </html>
114 """)
115 class WebClient(openerpweb.Controller):
116     _cp_path = "/base/webclient"
117
118     @openerpweb.jsonrequest
119     def csslist(self, req, mods='base'):
120         return manifest_glob(mods.split(','), 'css')
121
122     @openerpweb.jsonrequest
123     def jslist(self, req, mods='base'):
124         return manifest_glob(mods.split(','), 'js')
125
126     @openerpweb.httprequest
127     def css(self, req, mods='base'):
128         req.httpresponse.headers['Content-Type'] = 'text/css'
129         files = manifest_glob(mods.split(','), 'css')
130         content,timestamp = concat_files(files)
131         # TODO request set the Date of last modif and Etag
132         return content
133
134     @openerpweb.httprequest
135     def js(self, req, mods='base'):
136         req.httpresponse.headers['Content-Type'] = 'application/javascript'
137         files = manifest_glob(mods.split(','), 'js')
138         content,timestamp = concat_files(files)
139         # TODO request set the Date of last modif and Etag
140         return content
141
142     @openerpweb.httprequest
143     def home(self, req, s_action=None):
144
145         # script tags
146         jslist = ['/base/webclient/js']
147         if req.debug:
148             jslist = manifest_glob(['base'], 'js')
149         js = "\n        ".join(['<script type="text/javascript" src="%s"></script>'%i for i in jslist])
150
151         # css tags
152         csslist = ['/base/webclient/css']
153         if req.debug:
154             csslist = manifest_glob(['base'], 'css')
155         css = "\n        ".join(['<link rel="stylesheet" href="%s">'%i for i in csslist])
156         r = home_template % {
157             'javascript': js,
158             'css': css
159         }
160         return r
161
162     @openerpweb.jsonrequest
163     def translations(self, req, mods, lang):
164         lang_model = req.session.model('res.lang')
165         ids = lang_model.search([("code", "=", lang)])
166         if ids:
167             lang_obj = lang_model.read(ids[0], ["direction", "date_format", "time_format",
168                                                 "grouping", "decimal_point", "thousands_sep"])
169         else:
170             lang_obj = None
171
172         if lang.count("_") > 0:
173             separator = "_"
174         else:
175             separator = "@"
176         langs = lang.split(separator)
177         langs = [separator.join(langs[:x]) for x in range(1, len(langs) + 1)]
178
179         transs = {}
180         for addon_name in mods:
181             transl = {"messages":[]}
182             transs[addon_name] = transl
183             for l in langs:
184                 f_name = os.path.join(openerpweb.path_addons, addon_name, "po", l + ".po")
185                 if not os.path.exists(f_name):
186                     continue
187                 try:
188                     with open(f_name) as t_file:
189                         po = read_po(t_file)
190                 except:
191                     continue
192                 for x in po:
193                     if x.id and x.string:
194                         transl["messages"].append({'id': x.id, 'string': x.string})
195         return {"modules": transs,
196                 "lang_parameters": lang_obj}
197
198 class Database(openerpweb.Controller):
199     _cp_path = "/base/database"
200
201     @openerpweb.jsonrequest
202     def get_list(self, req):
203         proxy = req.session.proxy("db")
204         dbs = proxy.list()
205         h = req.httprequest.headers['Host'].split(':')[0]
206         d = h.split('.')[0]
207         r = cherrypy.config['openerp.dbfilter'].replace('%h', h).replace('%d', d)
208         dbs = [i for i in dbs if re.match(r, i)]
209         return {"db_list": dbs}
210
211     @openerpweb.jsonrequest
212     def progress(self, req, password, id):
213         return req.session.proxy('db').get_progress(password, id)
214
215     @openerpweb.jsonrequest
216     def create(self, req, fields):
217
218         params = dict(map(operator.itemgetter('name', 'value'), fields))
219         create_attrs = (
220             params['super_admin_pwd'],
221             params['db_name'],
222             bool(params.get('demo_data')),
223             params['db_lang'],
224             params['create_admin_pwd']
225         )
226
227         try:
228             return req.session.proxy("db").create(*create_attrs)
229         except xmlrpclib.Fault, e:
230             if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
231                 return {'error': e.faultCode, 'title': 'Create Database'}
232         return {'error': 'Could not create database !', 'title': 'Create Database'}
233
234     @openerpweb.jsonrequest
235     def drop(self, req, fields):
236         password, db = operator.itemgetter(
237             'drop_pwd', 'drop_db')(
238                 dict(map(operator.itemgetter('name', 'value'), fields)))
239
240         try:
241             return req.session.proxy("db").drop(password, db)
242         except xmlrpclib.Fault, e:
243             if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
244                 return {'error': e.faultCode, 'title': 'Drop Database'}
245         return {'error': 'Could not drop database !', 'title': 'Drop Database'}
246
247     @openerpweb.httprequest
248     def backup(self, req, backup_db, backup_pwd, token):
249         try:
250             db_dump = base64.decodestring(
251                 req.session.proxy("db").dump(backup_pwd, backup_db))
252             req.httpresponse.headers['Content-Type'] = "application/octet-stream; charset=binary"
253             req.httpresponse.headers['Content-Disposition'] = 'attachment; filename="' + backup_db + '.dump"'
254             req.httpresponse.cookie['fileToken'] = token
255             req.httpresponse.cookie['fileToken']['path'] = '/'
256             return db_dump
257         except xmlrpclib.Fault, e:
258             if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
259                 return 'Backup Database|' + e.faultCode
260         return 'Backup Database|Could not generate database backup'
261
262     @openerpweb.httprequest
263     def restore(self, req, db_file, restore_pwd, new_db):
264         try:
265             data = base64.encodestring(db_file.file.read())
266             req.session.proxy("db").restore(restore_pwd, new_db, data)
267             return ''
268         except xmlrpclib.Fault, e:
269             if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
270                 raise Exception("AccessDenied")
271
272     @openerpweb.jsonrequest
273     def change_password(self, req, fields):
274         old_password, new_password = operator.itemgetter(
275             'old_pwd', 'new_pwd')(
276                 dict(map(operator.itemgetter('name', 'value'), fields)))
277         try:
278             return req.session.proxy("db").change_admin_password(old_password, new_password)
279         except xmlrpclib.Fault, e:
280             if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
281                 return {'error': e.faultCode, 'title': 'Change Password'}
282         return {'error': 'Error, password not changed !', 'title': 'Change Password'}
283
284 class Session(openerpweb.Controller):
285     _cp_path = "/base/session"
286
287     @openerpweb.jsonrequest
288     def login(self, req, db, login, password):
289         req.session.login(db, login, password)
290         ctx = req.session.get_context()
291
292         return {
293             "session_id": req.session_id,
294             "uid": req.session._uid,
295             "context": ctx
296         }
297
298     @openerpweb.jsonrequest
299     def sc_list(self, req):
300         return req.session.model('ir.ui.view_sc').get_sc(
301             req.session._uid, "ir.ui.menu", req.session.eval_context(req.context))
302
303     @openerpweb.jsonrequest
304     def get_lang_list(self, req):
305         try:
306             return {
307                 'lang_list': (req.session.proxy("db").list_lang() or []),
308                 'error': ""
309             }
310         except Exception, e:
311             return {"error": e, "title": "Languages"}
312
313     @openerpweb.jsonrequest
314     def modules(self, req):
315         # TODO query server for installed web modules
316         mods = []
317         for name, manifest in openerpweb.addons_manifest.items():
318             if name != 'base' and manifest.get('active', True):
319                 mods.append(name)
320         return mods
321
322     @openerpweb.jsonrequest
323     def eval_domain_and_context(self, req, contexts, domains,
324                                 group_by_seq=None):
325         """ Evaluates sequences of domains and contexts, composing them into
326         a single context, domain or group_by sequence.
327
328         :param list contexts: list of contexts to merge together. Contexts are
329                               evaluated in sequence, all previous contexts
330                               are part of their own evaluation context
331                               (starting at the session context).
332         :param list domains: list of domains to merge together. Domains are
333                              evaluated in sequence and appended to one another
334                              (implicit AND), their evaluation domain is the
335                              result of merging all contexts.
336         :param list group_by_seq: list of domains (which may be in a different
337                                   order than the ``contexts`` parameter),
338                                   evaluated in sequence, their ``'group_by'``
339                                   key is extracted if they have one.
340         :returns:
341             a 3-dict of:
342
343             context (``dict``)
344                 the global context created by merging all of
345                 ``contexts``
346
347             domain (``list``)
348                 the concatenation of all domains
349
350             group_by (``list``)
351                 a list of fields to group by, potentially empty (in which case
352                 no group by should be performed)
353         """
354         context, domain = eval_context_and_domain(req.session,
355                                                   openerpweb.nonliterals.CompoundContext(*(contexts or [])),
356                                                   openerpweb.nonliterals.CompoundDomain(*(domains or [])))
357
358         group_by_sequence = []
359         for candidate in (group_by_seq or []):
360             ctx = req.session.eval_context(candidate, context)
361             group_by = ctx.get('group_by')
362             if not group_by:
363                 continue
364             elif isinstance(group_by, basestring):
365                 group_by_sequence.append(group_by)
366             else:
367                 group_by_sequence.extend(group_by)
368
369         return {
370             'context': context,
371             'domain': domain,
372             'group_by': group_by_sequence
373         }
374
375     @openerpweb.jsonrequest
376     def save_session_action(self, req, the_action):
377         """
378         This method store an action object in the session object and returns an integer
379         identifying that action. The method get_session_action() can be used to get
380         back the action.
381
382         :param the_action: The action to save in the session.
383         :type the_action: anything
384         :return: A key identifying the saved action.
385         :rtype: integer
386         """
387         saved_actions = req.httpsession.get('saved_actions')
388         if not saved_actions:
389             saved_actions = {"next":0, "actions":{}}
390             req.httpsession['saved_actions'] = saved_actions
391         # we don't allow more than 10 stored actions
392         if len(saved_actions["actions"]) >= 10:
393             del saved_actions["actions"][min(saved_actions["actions"].keys())]
394         key = saved_actions["next"]
395         saved_actions["actions"][key] = the_action
396         saved_actions["next"] = key + 1
397         return key
398
399     @openerpweb.jsonrequest
400     def get_session_action(self, req, key):
401         """
402         Gets back a previously saved action. This method can return None if the action
403         was saved since too much time (this case should be handled in a smart way).
404
405         :param key: The key given by save_session_action()
406         :type key: integer
407         :return: The saved action or None.
408         :rtype: anything
409         """
410         saved_actions = req.httpsession.get('saved_actions')
411         if not saved_actions:
412             return None
413         return saved_actions["actions"].get(key)
414
415     @openerpweb.jsonrequest
416     def check(self, req):
417         req.session.assert_valid()
418         return None
419
420 def eval_context_and_domain(session, context, domain=None):
421     e_context = session.eval_context(context)
422     # should we give the evaluated context as an evaluation context to the domain?
423     e_domain = session.eval_domain(domain or [])
424
425     return e_context, e_domain
426
427 def load_actions_from_ir_values(req, key, key2, models, meta):
428     context = req.session.eval_context(req.context)
429     Values = req.session.model('ir.values')
430     actions = Values.get(key, key2, models, meta, context)
431
432     return [(id, name, clean_action(req, action))
433             for id, name, action in actions]
434
435 def clean_action(req, action):
436     context = req.session.eval_context(req.context)
437     eval_ctx = req.session.evaluation_context(context)
438     action.setdefault('flags', {})
439
440     # values come from the server, we can just eval them
441     if isinstance(action.get('context'), basestring):
442         action['context'] = eval( action['context'], eval_ctx ) or {}
443
444     if isinstance(action.get('domain'), basestring):
445         action['domain'] = eval( action['domain'], eval_ctx ) or []
446
447     return fix_view_modes(action)
448
449 # I think generate_views,fix_view_modes should go into js ActionManager
450 def generate_views(action):
451     """
452     While the server generates a sequence called "views" computing dependencies
453     between a bunch of stuff for views coming directly from the database
454     (the ``ir.actions.act_window model``), it's also possible for e.g. buttons
455     to return custom view dictionaries generated on the fly.
456
457     In that case, there is no ``views`` key available on the action.
458
459     Since the web client relies on ``action['views']``, generate it here from
460     ``view_mode`` and ``view_id``.
461
462     Currently handles two different cases:
463
464     * no view_id, multiple view_mode
465     * single view_id, single view_mode
466
467     :param dict action: action descriptor dictionary to generate a views key for
468     """
469     view_id = action.get('view_id', False)
470     if isinstance(view_id, (list, tuple)):
471         view_id = view_id[0]
472
473     # providing at least one view mode is a requirement, not an option
474     view_modes = action['view_mode'].split(',')
475
476     if len(view_modes) > 1:
477         if view_id:
478             raise ValueError('Non-db action dictionaries should provide '
479                              'either multiple view modes or a single view '
480                              'mode and an optional view id.\n\n Got view '
481                              'modes %r and view id %r for action %r' % (
482                 view_modes, view_id, action))
483         action['views'] = [(False, mode) for mode in view_modes]
484         return
485     action['views'] = [(view_id, view_modes[0])]
486
487 def fix_view_modes(action):
488     """ For historical reasons, OpenERP has weird dealings in relation to
489     view_mode and the view_type attribute (on window actions):
490
491     * one of the view modes is ``tree``, which stands for both list views
492       and tree views
493     * the choice is made by checking ``view_type``, which is either
494       ``form`` for a list view or ``tree`` for an actual tree view
495
496     This methods simply folds the view_type into view_mode by adding a
497     new view mode ``list`` which is the result of the ``tree`` view_mode
498     in conjunction with the ``form`` view_type.
499
500     TODO: this should go into the doc, some kind of "peculiarities" section
501
502     :param dict action: an action descriptor
503     :returns: nothing, the action is modified in place
504     """
505     if 'views' not in action:
506         generate_views(action)
507
508     if action.pop('view_type') != 'form':
509         return action
510
511     action['views'] = [
512         [id, mode if mode != 'tree' else 'list']
513         for id, mode in action['views']
514     ]
515
516     return action
517
518 class Menu(openerpweb.Controller):
519     _cp_path = "/base/menu"
520
521     @openerpweb.jsonrequest
522     def load(self, req):
523         return {'data': self.do_load(req)}
524
525     def do_load(self, req):
526         """ Loads all menu items (all applications and their sub-menus).
527
528         :param req: A request object, with an OpenERP session attribute
529         :type req: < session -> OpenERPSession >
530         :return: the menu root
531         :rtype: dict('children': menu_nodes)
532         """
533         Menus = req.session.model('ir.ui.menu')
534         # menus are loaded fully unlike a regular tree view, cause there are
535         # less than 512 items
536         context = req.session.eval_context(req.context)
537         menu_ids = Menus.search([], 0, False, False, context)
538         menu_items = Menus.read(menu_ids, ['name', 'sequence', 'parent_id'], context)
539         menu_root = {'id': False, 'name': 'root', 'parent_id': [-1, '']}
540         menu_items.append(menu_root)
541
542         # make a tree using parent_id
543         menu_items_map = dict((menu_item["id"], menu_item) for menu_item in menu_items)
544         for menu_item in menu_items:
545             if menu_item['parent_id']:
546                 parent = menu_item['parent_id'][0]
547             else:
548                 parent = False
549             if parent in menu_items_map:
550                 menu_items_map[parent].setdefault(
551                     'children', []).append(menu_item)
552
553         # sort by sequence a tree using parent_id
554         for menu_item in menu_items:
555             menu_item.setdefault('children', []).sort(
556                 key=lambda x:x["sequence"])
557
558         return menu_root
559
560     @openerpweb.jsonrequest
561     def action(self, req, menu_id):
562         actions = load_actions_from_ir_values(req,'action', 'tree_but_open',
563                                              [('ir.ui.menu', menu_id)], False)
564         return {"action": actions}
565
566 class DataSet(openerpweb.Controller):
567     _cp_path = "/base/dataset"
568
569     @openerpweb.jsonrequest
570     def fields(self, req, model):
571         return {'fields': req.session.model(model).fields_get(False,
572                                                               req.session.eval_context(req.context))}
573
574     @openerpweb.jsonrequest
575     def search_read(self, req, model, fields=False, offset=0, limit=False, domain=None, sort=None):
576         return self.do_search_read(req, model, fields, offset, limit, domain, sort)
577     def do_search_read(self, req, model, fields=False, offset=0, limit=False, domain=None
578                        , sort=None):
579         """ Performs a search() followed by a read() (if needed) using the
580         provided search criteria
581
582         :param req: a JSON-RPC request object
583         :type req: openerpweb.JsonRequest
584         :param str model: the name of the model to search on
585         :param fields: a list of the fields to return in the result records
586         :type fields: [str]
587         :param int offset: from which index should the results start being returned
588         :param int limit: the maximum number of records to return
589         :param list domain: the search domain for the query
590         :param list sort: sorting directives
591         :returns: A structure (dict) with two keys: ids (all the ids matching
592                   the (domain, context) pair) and records (paginated records
593                   matching fields selection set)
594         :rtype: list
595         """
596         Model = req.session.model(model)
597
598         context, domain = eval_context_and_domain(
599             req.session, req.context, domain)
600
601         ids = Model.search(domain, 0, False, sort or False, context)
602         # need to fill the dataset with all ids for the (domain, context) pair,
603         # so search un-paginated and paginate manually before reading
604         paginated_ids = ids[offset:(offset + limit if limit else None)]
605         if fields and fields == ['id']:
606             # shortcut read if we only want the ids
607             return {
608                 'ids': ids,
609                 'records': map(lambda id: {'id': id}, paginated_ids)
610             }
611
612         records = Model.read(paginated_ids, fields or False, context)
613         records.sort(key=lambda obj: ids.index(obj['id']))
614         return {
615             'ids': ids,
616             'records': records
617         }
618
619
620     @openerpweb.jsonrequest
621     def read(self, req, model, ids, fields=False):
622         return self.do_search_read(req, model, ids, fields)
623
624     @openerpweb.jsonrequest
625     def get(self, req, model, ids, fields=False):
626         return self.do_get(req, model, ids, fields)
627
628     def do_get(self, req, model, ids, fields=False):
629         """ Fetches and returns the records of the model ``model`` whose ids
630         are in ``ids``.
631
632         The results are in the same order as the inputs, but elements may be
633         missing (if there is no record left for the id)
634
635         :param req: the JSON-RPC2 request object
636         :type req: openerpweb.JsonRequest
637         :param model: the model to read from
638         :type model: str
639         :param ids: a list of identifiers
640         :type ids: list
641         :param fields: a list of fields to fetch, ``False`` or empty to fetch
642                        all fields in the model
643         :type fields: list | False
644         :returns: a list of records, in the same order as the list of ids
645         :rtype: list
646         """
647         Model = req.session.model(model)
648         records = Model.read(ids, fields, req.session.eval_context(req.context))
649
650         record_map = dict((record['id'], record) for record in records)
651
652         return [record_map[id] for id in ids if record_map.get(id)]
653
654     @openerpweb.jsonrequest
655     def load(self, req, model, id, fields):
656         m = req.session.model(model)
657         value = {}
658         r = m.read([id], False, req.session.eval_context(req.context))
659         if r:
660             value = r[0]
661         return {'value': value}
662
663     @openerpweb.jsonrequest
664     def create(self, req, model, data):
665         m = req.session.model(model)
666         r = m.create(data, req.session.eval_context(req.context))
667         return {'result': r}
668
669     @openerpweb.jsonrequest
670     def save(self, req, model, id, data):
671         m = req.session.model(model)
672         r = m.write([id], data, req.session.eval_context(req.context))
673         return {'result': r}
674
675     @openerpweb.jsonrequest
676     def unlink(self, req, model, ids=()):
677         Model = req.session.model(model)
678         return Model.unlink(ids, req.session.eval_context(req.context))
679
680     def call_common(self, req, model, method, args, domain_id=None, context_id=None):
681         domain = args[domain_id] if domain_id and len(args) - 1 >= domain_id  else []
682         context = args[context_id] if context_id and len(args) - 1 >= context_id  else {}
683         c, d = eval_context_and_domain(req.session, context, domain)
684         if domain_id and len(args) - 1 >= domain_id:
685             args[domain_id] = d
686         if context_id and len(args) - 1 >= context_id:
687             args[context_id] = c
688
689         return getattr(req.session.model(model), method)(*args)
690
691     @openerpweb.jsonrequest
692     def call(self, req, model, method, args, domain_id=None, context_id=None):
693         return self.call_common(req, model, method, args, domain_id, context_id)
694
695     @openerpweb.jsonrequest
696     def call_button(self, req, model, method, args, domain_id=None, context_id=None):
697         action = self.call_common(req, model, method, args, domain_id, context_id)
698         if isinstance(action, dict) and action.get('type') != '':
699             return {'result': clean_action(req, action)}
700         return {'result': False}
701
702     @openerpweb.jsonrequest
703     def exec_workflow(self, req, model, id, signal):
704         r = req.session.exec_workflow(model, id, signal)
705         return {'result': r}
706
707     @openerpweb.jsonrequest
708     def default_get(self, req, model, fields):
709         Model = req.session.model(model)
710         return Model.default_get(fields, req.session.eval_context(req.context))
711
712     @openerpweb.jsonrequest
713     def name_search(self, req, model, search_str, domain=[], context={}):
714         m = req.session.model(model)
715         r = m.name_search(search_str+'%', domain, '=ilike', context)
716         return {'result': r}
717
718 class DataGroup(openerpweb.Controller):
719     _cp_path = "/base/group"
720     @openerpweb.jsonrequest
721     def read(self, req, model, fields, group_by_fields, domain=None, sort=None):
722         Model = req.session.model(model)
723         context, domain = eval_context_and_domain(req.session, req.context, domain)
724
725         return Model.read_group(
726             domain or [], fields, group_by_fields, 0, False,
727             dict(context, group_by=group_by_fields), sort or False)
728
729 class View(openerpweb.Controller):
730     _cp_path = "/base/view"
731
732     def fields_view_get(self, req, model, view_id, view_type,
733                         transform=True, toolbar=False, submenu=False):
734         Model = req.session.model(model)
735         context = req.session.eval_context(req.context)
736         fvg = Model.fields_view_get(view_id, view_type, context, toolbar, submenu)
737         # todo fme?: check that we should pass the evaluated context here
738         self.process_view(req.session, fvg, context, transform)
739         return fvg
740
741     def process_view(self, session, fvg, context, transform):
742         # depending on how it feels, xmlrpclib.ServerProxy can translate
743         # XML-RPC strings to ``str`` or ``unicode``. ElementTree does not
744         # enjoy unicode strings which can not be trivially converted to
745         # strings, and it blows up during parsing.
746
747         # So ensure we fix this retardation by converting view xml back to
748         # bit strings.
749         if isinstance(fvg['arch'], unicode):
750             arch = fvg['arch'].encode('utf-8')
751         else:
752             arch = fvg['arch']
753
754         if transform:
755             evaluation_context = session.evaluation_context(context or {})
756             xml = self.transform_view(arch, session, evaluation_context)
757         else:
758             xml = ElementTree.fromstring(arch)
759         fvg['arch'] = Xml2Json.convert_element(xml)
760
761         for field in fvg['fields'].itervalues():
762             if field.get('views'):
763                 for view in field["views"].itervalues():
764                     self.process_view(session, view, None, transform)
765             if field.get('domain'):
766                 field["domain"] = self.parse_domain(field["domain"], session)
767             if field.get('context'):
768                 field["context"] = self.parse_context(field["context"], session)
769
770     @openerpweb.jsonrequest
771     def add_custom(self, req, view_id, arch):
772         CustomView = req.session.model('ir.ui.view.custom')
773         CustomView.create({
774             'user_id': req.session._uid,
775             'ref_id': view_id,
776             'arch': arch
777         }, req.session.eval_context(req.context))
778         return {'result': True}
779
780     @openerpweb.jsonrequest
781     def undo_custom(self, req, view_id, reset=False):
782         CustomView = req.session.model('ir.ui.view.custom')
783         context = req.session.eval_context(req.context)
784         vcustom = CustomView.search([('user_id', '=', req.session._uid), ('ref_id' ,'=', view_id)],
785                                     0, False, False, context)
786         if vcustom:
787             if reset:
788                 CustomView.unlink(vcustom, context)
789             else:
790                 CustomView.unlink([vcustom[0]], context)
791             return {'result': True}
792         return {'result': False}
793
794     def transform_view(self, view_string, session, context=None):
795         # transform nodes on the fly via iterparse, instead of
796         # doing it statically on the parsing result
797         parser = ElementTree.iterparse(StringIO(view_string), events=("start",))
798         root = None
799         for event, elem in parser:
800             if event == "start":
801                 if root is None:
802                     root = elem
803                 self.parse_domains_and_contexts(elem, session)
804         return root
805
806     def parse_domain(self, domain, session):
807         """ Parses an arbitrary string containing a domain, transforms it
808         to either a literal domain or a :class:`openerpweb.nonliterals.Domain`
809
810         :param domain: the domain to parse, if the domain is not a string it
811                        is assumed to be a literal domain and is returned as-is
812         :param session: Current OpenERP session
813         :type session: openerpweb.openerpweb.OpenERPSession
814         """
815         if not isinstance(domain, (str, unicode)):
816             return domain
817         try:
818             return openerpweb.ast.literal_eval(domain)
819         except ValueError:
820             # not a literal
821             return openerpweb.nonliterals.Domain(session, domain)
822
823     def parse_context(self, context, session):
824         """ Parses an arbitrary string containing a context, transforms it
825         to either a literal context or a :class:`openerpweb.nonliterals.Context`
826
827         :param context: the context to parse, if the context is not a string it
828                is assumed to be a literal domain and is returned as-is
829         :param session: Current OpenERP session
830         :type session: openerpweb.openerpweb.OpenERPSession
831         """
832         if not isinstance(context, (str, unicode)):
833             return context
834         try:
835             return openerpweb.ast.literal_eval(context)
836         except ValueError:
837             return openerpweb.nonliterals.Context(session, context)
838
839     def parse_domains_and_contexts(self, elem, session):
840         """ Converts domains and contexts from the view into Python objects,
841         either literals if they can be parsed by literal_eval or a special
842         placeholder object if the domain or context refers to free variables.
843
844         :param elem: the current node being parsed
845         :type param: xml.etree.ElementTree.Element
846         :param session: OpenERP session object, used to store and retrieve
847                         non-literal objects
848         :type session: openerpweb.openerpweb.OpenERPSession
849         """
850         for el in ['domain', 'filter_domain']:
851             domain = elem.get(el, '').strip()
852             if domain:
853                 elem.set(el, self.parse_domain(domain, session))
854         for el in ['context', 'default_get']:
855             context_string = elem.get(el, '').strip()
856             if context_string:
857                 elem.set(el, self.parse_context(context_string, session))
858
859 class FormView(View):
860     _cp_path = "/base/formview"
861
862     @openerpweb.jsonrequest
863     def load(self, req, model, view_id, toolbar=False):
864         fields_view = self.fields_view_get(req, model, view_id, 'form', toolbar=toolbar)
865         return {'fields_view': fields_view}
866
867 class ListView(View):
868     _cp_path = "/base/listview"
869
870     @openerpweb.jsonrequest
871     def load(self, req, model, view_id, toolbar=False):
872         fields_view = self.fields_view_get(req, model, view_id, 'tree', toolbar=toolbar)
873         return {'fields_view': fields_view}
874
875     def process_colors(self, view, row, context):
876         colors = view['arch']['attrs'].get('colors')
877
878         if not colors:
879             return None
880
881         color = [
882             pair.split(':')[0]
883             for pair in colors.split(';')
884             if eval(pair.split(':')[1], dict(context, **row))
885         ]
886
887         if not color:
888             return None
889         elif len(color) == 1:
890             return color[0]
891         return 'maroon'
892
893 class SearchView(View):
894     _cp_path = "/base/searchview"
895
896     @openerpweb.jsonrequest
897     def load(self, req, model, view_id):
898         fields_view = self.fields_view_get(req, model, view_id, 'search')
899         return {'fields_view': fields_view}
900
901     @openerpweb.jsonrequest
902     def fields_get(self, req, model):
903         Model = req.session.model(model)
904         fields = Model.fields_get(False, req.session.eval_context(req.context))
905         for field in fields.values():
906             # shouldn't convert the views too?
907             if field.get('domain'):
908                 field["domain"] = self.parse_domain(field["domain"], req.session)
909             if field.get('context'):
910                 field["context"] = self.parse_domain(field["context"], req.session)
911         return {'fields': fields}
912
913     @openerpweb.jsonrequest
914     def get_filters(self, req, model):
915         Model = req.session.model("ir.filters")
916         filters = Model.get_filters(model)
917         for filter in filters:
918             filter["context"] = req.session.eval_context(self.parse_context(filter["context"], req.session))
919             filter["domain"] = req.session.eval_domain(self.parse_domain(filter["domain"], req.session))
920         return filters
921
922     @openerpweb.jsonrequest
923     def save_filter(self, req, model, name, context_to_save, domain):
924         Model = req.session.model("ir.filters")
925         ctx = openerpweb.nonliterals.CompoundContext(context_to_save)
926         ctx.session = req.session
927         ctx = ctx.evaluate()
928         domain = openerpweb.nonliterals.CompoundDomain(domain)
929         domain.session = req.session
930         domain = domain.evaluate()
931         uid = req.session._uid
932         context = req.session.eval_context(req.context)
933         to_return = Model.create_or_replace({"context": ctx,
934                                              "domain": domain,
935                                              "model_id": model,
936                                              "name": name,
937                                              "user_id": uid
938                                              }, context)
939         return to_return
940
941 class Binary(openerpweb.Controller):
942     _cp_path = "/base/binary"
943
944     @openerpweb.httprequest
945     def image(self, req, model, id, field, **kw):
946         req.httpresponse.headers['Content-Type'] = 'image/png'
947         Model = req.session.model(model)
948         context = req.session.eval_context(req.context)
949         try:
950             if not id:
951                 res = Model.default_get([field], context).get(field, '')
952             else:
953                 res = Model.read([int(id)], [field], context)[0].get(field, '')
954             return base64.decodestring(res)
955         except: # TODO: what's the exception here?
956             return self.placeholder()
957     def placeholder(self):
958         return open(os.path.join(openerpweb.path_addons, 'base', 'static', 'src', 'img', 'placeholder.png'), 'rb').read()
959
960     @openerpweb.httprequest
961     def saveas(self, req, model, id, field, fieldname, **kw):
962         Model = req.session.model(model)
963         context = req.session.eval_context(req.context)
964         res = Model.read([int(id)], [field, fieldname], context)[0]
965         filecontent = res.get(field, '')
966         if not filecontent:
967             raise cherrypy.NotFound
968         else:
969             req.httpresponse.headers['Content-Type'] = 'application/octet-stream'
970             filename = '%s_%s' % (model.replace('.', '_'), id)
971             if fieldname:
972                 filename = res.get(fieldname, '') or filename
973             req.httpresponse.headers['Content-Disposition'] = 'attachment; filename=' +  filename
974             return base64.decodestring(filecontent)
975
976     @openerpweb.httprequest
977     def upload(self, req, callback, ufile=None):
978         cherrypy.response.timeout = 500
979         headers = {}
980         for key, val in req.httprequest.headers.iteritems():
981             headers[key.lower()] = val
982         size = int(headers.get('content-length', 0))
983         # TODO: might be useful to have a configuration flag for max-length file uploads
984         try:
985             out = """<script language="javascript" type="text/javascript">
986                         var win = window.top.window,
987                             callback = win[%s];
988                         if (typeof(callback) === 'function') {
989                             callback.apply(this, %s);
990                         } else {
991                             win.jQuery('#oe_notification', win.document).notify('create', {
992                                 title: "Ajax File Upload",
993                                 text: "Could not find callback"
994                             });
995                         }
996                     </script>"""
997             data = ufile.file.read()
998             args = [size, ufile.filename, ufile.headers.getheader('Content-Type'), base64.encodestring(data)]
999         except Exception, e:
1000             args = [False, e.message]
1001         return out % (simplejson.dumps(callback), simplejson.dumps(args))
1002
1003     @openerpweb.httprequest
1004     def upload_attachment(self, req, callback, model, id, ufile=None):
1005         cherrypy.response.timeout = 500
1006         context = req.session.eval_context(req.context)
1007         Model = req.session.model('ir.attachment')
1008         try:
1009             out = """<script language="javascript" type="text/javascript">
1010                         var win = window.top.window,
1011                             callback = win[%s];
1012                         if (typeof(callback) === 'function') {
1013                             callback.call(this, %s);
1014                         }
1015                     </script>"""
1016             attachment_id = Model.create({
1017                 'name': ufile.filename,
1018                 'datas': base64.encodestring(ufile.file.read()),
1019                 'res_model': model,
1020                 'res_id': int(id)
1021             }, context)
1022             args = {
1023                 'filename': ufile.filename,
1024                 'id':  attachment_id
1025             }
1026         except Exception, e:
1027             args = { 'error': e.message }
1028         return out % (simplejson.dumps(callback), simplejson.dumps(args))
1029
1030 class Action(openerpweb.Controller):
1031     _cp_path = "/base/action"
1032
1033     @openerpweb.jsonrequest
1034     def load(self, req, action_id):
1035         Actions = req.session.model('ir.actions.actions')
1036         value = False
1037         context = req.session.eval_context(req.context)
1038         action_type = Actions.read([action_id], ['type'], context)
1039         if action_type:
1040             action = req.session.model(action_type[0]['type']).read([action_id], False,
1041                                                                     context)
1042             if action:
1043                 value = clean_action(req, action[0])
1044         return {'result': value}
1045
1046     @openerpweb.jsonrequest
1047     def run(self, req, action_id):
1048         return clean_action(req, req.session.model('ir.actions.server').run(
1049             [action_id], req.session.eval_context(req.context)))
1050
1051 class TreeView(View):
1052     _cp_path = "/base/treeview"
1053
1054     @openerpweb.jsonrequest
1055     def load(self, req, model, view_id, toolbar=False):
1056         return self.fields_view_get(req, model, view_id, 'tree', toolbar=toolbar)
1057
1058     @openerpweb.jsonrequest
1059     def action(self, req, model, id):
1060         return load_actions_from_ir_values(
1061             req,'action', 'tree_but_open',[(model, id)],
1062             False)
1063
1064 def export_csv(fields, result):
1065     fp = StringIO()
1066     writer = csv.writer(fp, quoting=csv.QUOTE_ALL)
1067
1068     writer.writerow(fields)
1069
1070     for data in result:
1071         row = []
1072         for d in data:
1073             if isinstance(d, basestring):
1074                 d = d.replace('\n',' ').replace('\t',' ')
1075                 try:
1076                     d = d.encode('utf-8')
1077                 except:
1078                     pass
1079             if d is False: d = None
1080             row.append(d)
1081         writer.writerow(row)
1082
1083     fp.seek(0)
1084     data = fp.read()
1085     fp.close()
1086     return data
1087
1088 def export_xls(fieldnames, table):
1089     try:
1090         import xlwt
1091     except ImportError:
1092         common.error(_('Import Error.'), _('Please install xlwt library to export to MS Excel.'))
1093
1094     workbook = xlwt.Workbook()
1095     worksheet = workbook.add_sheet('Sheet 1')
1096
1097     for i, fieldname in enumerate(fieldnames):
1098         worksheet.write(0, i, str(fieldname))
1099         worksheet.col(i).width = 8000 # around 220 pixels
1100
1101     style = xlwt.easyxf('align: wrap yes')
1102
1103     for row_index, row in enumerate(table):
1104         for cell_index, cell_value in enumerate(row):
1105             cell_value = str(cell_value)
1106             cell_value = re.sub("\r", " ", cell_value)
1107             worksheet.write(row_index + 1, cell_index, cell_value, style)
1108
1109
1110     fp = StringIO()
1111     workbook.save(fp)
1112     fp.seek(0)
1113     data = fp.read()
1114     fp.close()
1115     #return data.decode('ISO-8859-1')
1116     return unicode(data, 'utf-8', 'replace')
1117
1118 class Export(View):
1119     _cp_path = "/base/export"
1120
1121     def fields_get(self, req, model):
1122         Model = req.session.model(model)
1123         fields = Model.fields_get(False, req.session.eval_context(req.context))
1124         return fields
1125
1126     @openerpweb.jsonrequest
1127     def get_fields(self, req, model, prefix='', name= '', field_parent=None, params={}):
1128         import_compat = params.get("import_compat", False)
1129
1130         fields = self.fields_get(req, model)
1131         field_parent_type = params.get("parent_field_type",False)
1132
1133         if import_compat and field_parent_type and field_parent_type == "many2one":
1134             fields = {}
1135
1136         fields.update({'id': {'string': 'ID'}, '.id': {'string': 'Database ID'}})
1137         records = []
1138         fields_order = fields.keys()
1139         fields_order.sort(lambda x,y: -cmp(fields[x].get('string', ''), fields[y].get('string', '')))
1140
1141         for index, field in enumerate(fields_order):
1142             value = fields[field]
1143             record = {}
1144             if import_compat and value.get('readonly', False):
1145                 ok = False
1146                 for sl in value.get('states', {}).values():
1147                     for s in sl:
1148                         ok = ok or (s==['readonly',False])
1149                 if not ok: continue
1150
1151             id = prefix + (prefix and '/'or '') + field
1152             nm = name + (name and '/' or '') + value['string']
1153             record.update(id=id, string= nm, action='javascript: void(0)',
1154                           target=None, icon=None, children=[], field_type=value.get('type',False), required=value.get('required', False))
1155             records.append(record)
1156
1157             if len(nm.split('/')) < 3 and value.get('relation', False):
1158                 if import_compat:
1159                     ref = value.pop('relation')
1160                     cfields = self.fields_get(req, ref)
1161                     if (value['type'] == 'many2many'):
1162                         record['children'] = []
1163                         record['params'] = {'model': ref, 'prefix': id, 'name': nm}
1164
1165                     elif value['type'] == 'many2one':
1166                         record['children'] = [id + '/id', id + '/.id']
1167                         record['params'] = {'model': ref, 'prefix': id, 'name': nm}
1168
1169                     else:
1170                         cfields_order = cfields.keys()
1171                         cfields_order.sort(lambda x,y: -cmp(cfields[x].get('string', ''), cfields[y].get('string', '')))
1172                         children = []
1173                         for j, fld in enumerate(cfields_order):
1174                             cid = id + '/' + fld
1175                             cid = cid.replace(' ', '_')
1176                             children.append(cid)
1177                         record['children'] = children or []
1178                         record['params'] = {'model': ref, 'prefix': id, 'name': nm}
1179                 else:
1180                     ref = value.pop('relation')
1181                     cfields = self.fields_get(req, ref)
1182                     cfields_order = cfields.keys()
1183                     cfields_order.sort(lambda x,y: -cmp(cfields[x].get('string', ''), cfields[y].get('string', '')))
1184                     children = []
1185                     for j, fld in enumerate(cfields_order):
1186                         cid = id + '/' + fld
1187                         cid = cid.replace(' ', '_')
1188                         children.append(cid)
1189                     record['children'] = children or []
1190                     record['params'] = {'model': ref, 'prefix': id, 'name': nm}
1191
1192         records.reverse()
1193         return records
1194
1195     @openerpweb.jsonrequest
1196     def save_export_lists(self, req, name, model, field_list):
1197         result = {'resource':model, 'name':name, 'export_fields': []}
1198         for field in field_list:
1199             result['export_fields'].append((0, 0, {'name': field}))
1200         return req.session.model("ir.exports").create(result, req.session.eval_context(req.context))
1201
1202     @openerpweb.jsonrequest
1203     def exist_export_lists(self, req, model):
1204         export_model = req.session.model("ir.exports")
1205         return export_model.read(export_model.search([('resource', '=', model)]), ['name'])
1206
1207     @openerpweb.jsonrequest
1208     def delete_export(self, req, export_id):
1209         req.session.model("ir.exports").unlink(export_id, req.session.eval_context(req.context))
1210         return True
1211
1212     @openerpweb.jsonrequest
1213     def namelist(self,req,  model, export_id):
1214
1215         result = self.get_data(req, model, req.session.eval_context(req.context))
1216         ir_export_obj = req.session.model("ir.exports")
1217         ir_export_line_obj = req.session.model("ir.exports.line")
1218
1219         field = ir_export_obj.read(export_id)
1220         fields = ir_export_line_obj.read(field['export_fields'])
1221
1222         name_list = {}
1223         [name_list.update({field['name']: result.get(field['name'])}) for field in fields]
1224         return name_list
1225
1226     def get_data(self, req, model, context=None):
1227         ids = []
1228         context = context or {}
1229         fields_data = {}
1230         proxy = req.session.model(model)
1231         fields = self.fields_get(req, model)
1232         if not ids:
1233             f1 = proxy.fields_view_get(False, 'tree', context)['fields']
1234             f2 = proxy.fields_view_get(False, 'form', context)['fields']
1235
1236             fields = dict(f1)
1237             fields.update(f2)
1238             fields.update({'id': {'string': 'ID'}, '.id': {'string': 'Database ID'}})
1239
1240         def rec(fields):
1241             _fields = {'id': 'ID' , '.id': 'Database ID' }
1242             def model_populate(fields, prefix_node='', prefix=None, prefix_value='', level=2):
1243                 fields_order = fields.keys()
1244                 fields_order.sort(lambda x,y: -cmp(fields[x].get('string', ''), fields[y].get('string', '')))
1245
1246                 for field in fields_order:
1247                     fields_data[prefix_node+field] = fields[field]
1248                     if prefix_node:
1249                         fields_data[prefix_node + field]['string'] = '%s%s' % (prefix_value, fields_data[prefix_node + field]['string'])
1250                     st_name = fields[field]['string'] or field
1251                     _fields[prefix_node+field] = st_name
1252                     if fields[field].get('relation', False) and level>0:
1253                         fields2 = self.fields_get(req,  fields[field]['relation'])
1254                         fields2.update({'id': {'string': 'ID'}, '.id': {'string': 'Database ID'}})
1255                         model_populate(fields2, prefix_node+field+'/', None, st_name+'/', level-1)
1256             model_populate(fields)
1257             return _fields
1258         return rec(fields)
1259
1260     @openerpweb.jsonrequest
1261     def export_data(self, req, model, fields, ids, domain, import_compat=False, export_format="csv", context=None):
1262         context = req.session.eval_context(req.context)
1263         modle_obj = req.session.model(model)
1264
1265         ids = ids or modle_obj.search(domain, context=context)
1266
1267         field = fields.keys()
1268         result = modle_obj.export_data(ids, field , context).get('datas',[])
1269
1270         if not import_compat:
1271             field = [val.strip() for val in fields.values()]
1272
1273         if export_format == 'xls':
1274             return export_xls(field, result)
1275         else:
1276             return export_csv(field, result)
1277
1278 class Import(View):
1279     _cp_path = "/base/import"
1280
1281     def fields_get(self, req, model):
1282         Model = req.session.model(model)
1283         fields = Model.fields_get(False, req.session.eval_context(req.context))
1284         return fields
1285
1286     @openerpweb.httprequest
1287     def detect_data(self, req, **params):
1288         import StringIO
1289         _fields = {}
1290         _fields_invert = {}
1291         error = None
1292         all_fields = []
1293         fields = dict(req.session.model(params.get('model')).fields_get(False, req.session.eval_context(req.context)))
1294         fields.update({'id': {'string': 'ID'}, '.id': {'string': 'Database ID'}})
1295
1296         def model_populate(fields, prefix_node='', prefix=None, prefix_value='', level=2):
1297             def str_comp(x,y):
1298                 if x<y: return 1
1299                 elif x>y: return -1
1300                 else: return 0
1301
1302             fields_order = fields.keys()
1303             fields_order.sort(lambda x,y: str_comp(fields[x].get('string', ''), fields[y].get('string', '')))
1304             for field in fields_order:
1305                 if (fields[field].get('type','') not in ('reference',))\
1306                             and (not fields[field].get('readonly')\
1307                             or not dict(fields[field].get('states', {}).get(
1308                             'draft', [('readonly', True)])).get('readonly',True)):
1309
1310                     st_name = prefix_value+fields[field]['string'] or field
1311                     _fields[prefix_node+field] = st_name
1312                     _fields_invert[st_name] = prefix_node+field
1313
1314                     if fields[field].get('type','')=='one2many' and level>0:
1315                         fields2 = self.fields_get(req,  fields[field]['relation'])
1316                         model_populate(fields2, prefix_node+field+'/', None, st_name+'/', level-1)
1317
1318                     if fields[field].get('relation',False) and level>0:
1319                         model_populate({'/id': {'type': 'char', 'string': 'ID'}, '.id': {'type': 'char', 'string': 'Database ID'}},
1320                                        prefix_node+field, None, st_name+'/', level-1)
1321         fields.update({'id':{'string':'ID'},'.id':{'string':'Database ID'}})
1322         model_populate(fields)
1323         all_fields = fields.keys()
1324         try:
1325             data = csv.reader(params.get('csvfile').file, quotechar=str(params.get('csvdel')), delimiter=str(params.get('csvsep')))
1326         except:
1327             error={'message': 'error opening .CSV file. Input Error.'}
1328             return simplejson.dumps({'error':error})
1329
1330         records = []
1331         fields = []
1332         word=''
1333         limit = 4
1334         try:
1335             for i, row in enumerate(data):
1336                 records.append(row)
1337                 if i == limit:
1338                     break
1339
1340             for line in records:
1341                 for word in line:
1342                     word = str(word.decode(params.get('csvcode')))
1343                     if word in _fields:
1344                         fields.append((word, _fields[word]))
1345                     elif word in _fields_invert.keys():
1346                         fields.append((_fields_invert[word], word))
1347                     else:
1348                         fields.append((word, word))
1349 #                        error = {'message':("You cannot import the field '%s', because we cannot auto-detect it" % (word,))}
1350                 break
1351         except:
1352             error = {'message':('Error processing the first line of the file. Field "%s" is unknown') % (word,)}
1353
1354         if error:
1355             params.get('csvfile').file.seek(0)
1356             error=dict(error, preview=params.get('csvfile').file.read(200))
1357             return simplejson.dumps({'error':error})
1358
1359         return simplejson.dumps({'records':records[1:],'fields':fields,'all_fields':all_fields})
1360
1361     @openerpweb.httprequest
1362     def import_data(self, req, **params):
1363         import StringIO
1364
1365         context = req.session.eval_context(req.context)
1366         modle_obj = req.session.model(params.get('model'))
1367         res = None
1368         content = params.get('csvfile').file.read()
1369         input=StringIO.StringIO(content)
1370         limit = 0
1371         data = []
1372
1373         if not (params.get('csvdel') and len(params.get('csvdel')) == 1):
1374             error={'message': "The CSV delimiter must be a single character"}
1375             return simplejson.dumps({'error':error})
1376
1377         try:
1378             for j, line in enumerate(csv.reader(input, quotechar=str(params.get('csvdel')), delimiter=str(params.get('csvsep')))):
1379                 # If the line contains no data, we should skip it.
1380                 if not line:
1381                     continue
1382                 if j == limit:
1383                     fields = line
1384                 else:
1385                     data.append(line)
1386         except csv.Error, e:
1387             error={'message': str(e),'title': 'File Format Error'}
1388             return simplejson.dumps({'error':error})
1389
1390         datas = []
1391         ctx = context
1392
1393         if not isinstance(fields, list):
1394             fields = [fields]
1395
1396         for line in data:
1397             try:
1398                 datas.append(map(lambda x:x.decode(params.get('csvcode')).encode('utf-8'), line))
1399             except:
1400                 datas.append(map(lambda x:x.decode('latin').encode('utf-8'), line))
1401
1402         # If the file contains nothing,
1403         if not datas:
1404             error = {'message': 'The file is empty !', 'title': 'Importation !'}
1405             return simplejson.dumps({'error':error})
1406
1407         #Inverting the header into column names
1408         try:
1409             res = modle_obj.import_data(fields, datas, 'init', '', False, ctx)
1410         except xmlrpclib.Fault, e:
1411             error = {"message":e.faultCode}
1412             return simplejson.dumps({'error':error})
1413
1414         if res[0]>=0:
1415             success={'message':'Imported %d objects' % (res[0],)}
1416             return simplejson.dumps({'success':success})
1417
1418         d = ''
1419         for key,val in res[1].items():
1420             d+= ('%s: %s' % (str(key),str(val)))
1421         msg = 'Error trying to import this record:%s. ErrorMessage:%s %s' % (d,res[2],res[3])
1422         error = {'message':str(msg), 'title':'ImportationError'}
1423
1424         return simplejson.dumps({'error':error})