[FIX] regenerate action's views key if it is empty, not just if it's absent
[odoo/odoo.git] / addons / web / controllers / main.py
index 9f894dc..397a6a0 100644 (file)
@@ -50,19 +50,25 @@ def concat_xml(file_list):
     return ElementTree.tostring(root, 'utf-8'), files_timestamp
 
 
-def concat_files(file_list):
+def concat_files(file_list, reader=None):
     """ Concatenate file content
     return (concat,timestamp)
-    concat: concatenation of file content
+    concat: concatenation of file content, read by `reader`
     timestamp: max(os.path.getmtime of file_list)
     """
+    if reader is None:
+        def reader(f):
+            with open(f) as fp:
+                return fp.read()
+
     files_content = []
     files_timestamp = 0
     for fname in file_list:
         ftime = os.path.getmtime(fname)
         if ftime > files_timestamp:
             files_timestamp = ftime
-        files_content.append(open(fname).read())
+
+        files_content.append(reader(fname))
     files_concat = "".join(files_content)
     return files_concat,files_timestamp
 
@@ -97,6 +103,7 @@ class WebClient(openerpweb.Controller):
             addons = self.server_wide_modules(req)
         else:
             addons = addons.split(',')
+        r = []
         for addon in addons:
             manifest = openerpweb.addons_manifest.get(addon, None)
             if not manifest:
@@ -106,7 +113,8 @@ class WebClient(openerpweb.Controller):
             globlist = manifest.get(key, [])
             for pattern in globlist:
                 for path in glob.glob(os.path.normpath(os.path.join(addons_path, addon, pattern))):
-                    yield path, path[len(addons_path):]
+                    r.append( (path, path[len(addons_path):]))
+        return r
 
     def manifest_list(self, req, mods, extension):
         if not req.debug:
@@ -130,8 +138,36 @@ class WebClient(openerpweb.Controller):
 
     @openerpweb.httprequest
     def css(self, req, mods=None):
-        files = [f[0] for f in self.manifest_glob(req, mods, 'css')]
-        content,timestamp = concat_files(files)
+
+        files = list(self.manifest_glob(req, mods, 'css'))
+        file_map = dict(files)
+
+        rx_import = re.compile(r"""@import\s+('|")(?!'|"|/|https?://)""", re.U)
+        rx_url = re.compile(r"""url\s*\(\s*('|"|)(?!'|"|/|https?://)""", re.U)
+
+
+        def reader(f):
+            """read the a css file and absolutify all relative uris"""
+            with open(f) as fp:
+                data = fp.read()
+
+            web_path = file_map[f]
+            web_dir = os.path.dirname(web_path)
+
+            data = re.sub(
+                rx_import,
+                r"""@import \1%s/""" % (web_dir,),
+                data,
+            )
+
+            data = re.sub(
+                rx_url,
+                r"""url(\1%s/""" % (web_dir,),
+                data,
+            )
+            return data
+
+        content,timestamp = concat_files((f[0] for f in files), reader)
         # TODO use timestamp to set Last mofified date and E-tag
         return req.make_response(content, [('Content-Type', 'text/css')])
 
@@ -234,13 +270,18 @@ class Database(openerpweb.Controller):
             params['db_lang'],
             params['create_admin_pwd']
         )
-
+        
         try:
             return req.session.proxy("db").create(*create_attrs)
         except xmlrpclib.Fault, e:
-            if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
-                return {'error': e.faultCode, 'title': 'Create Database'}
-        return {'error': 'Could not create database !', 'title': 'Create Database'}
+            if e.faultCode and isinstance(e.faultCode, str)\
+                and e.faultCode.split(':')[0] == 'AccessDenied':
+                    return {'error': e.faultCode, 'title': 'Database creation error'}
+            return {
+                'error': "Could not create database '%s': %s" % (
+                    params['db_name'], e.faultString),
+                'title': 'Database creation error'
+            }
 
     @openerpweb.jsonrequest
     def drop(self, req, fields):
@@ -273,7 +314,7 @@ class Database(openerpweb.Controller):
     @openerpweb.httprequest
     def restore(self, req, db_file, restore_pwd, new_db):
         try:
-            data = base64.b64encode(db_file.file.read())
+            data = base64.b64encode(db_file.read())
             req.session.proxy("db").restore(restore_pwd, new_db, data)
             return ''
         except xmlrpclib.Fault, e:
@@ -304,7 +345,8 @@ class Session(openerpweb.Controller):
             "session_id": req.session_id,
             "uid": req.session._uid,
             "context": ctx,
-            "db": req.session._db
+            "db": req.session._db,
+            "login": req.session._login
         }
 
     @openerpweb.jsonrequest
@@ -313,7 +355,8 @@ class Session(openerpweb.Controller):
         return {
             "uid": req.session._uid,
             "context": req.session.get_context() if req.session._uid else False,
-            "db": req.session._db
+            "db": req.session._db,
+            "login": req.session._login
         }
 
     @openerpweb.jsonrequest
@@ -489,7 +532,7 @@ def clean_action(req, action, do_not_eval=False):
         # values come from the server, we can just eval them
         if isinstance(action.get('context'), basestring):
             action['context'] = eval( action['context'], eval_ctx ) or {}
-    
+
         if isinstance(action.get('domain'), basestring):
             action['domain'] = eval( action['domain'], eval_ctx ) or []
     else:
@@ -561,9 +604,17 @@ def fix_view_modes(action):
     :param dict action: an action descriptor
     :returns: nothing, the action is modified in place
     """
-    if 'views' not in action:
+    if not action.get('views'):
         generate_views(action)
 
+    id_form = None
+    for index, (id, mode) in enumerate(action['views']):
+        if mode == 'form':
+            id_form = id
+            break
+    if id_form is not None:
+        action['views'].insert(index + 1, (id_form, 'page'))
+
     if action.pop('view_type', 'form') != 'form':
         return action
 
@@ -737,12 +788,15 @@ class DataSet(openerpweb.Controller):
         return Model.unlink(ids, req.session.eval_context(req.context))
 
     def call_common(self, req, model, method, args, domain_id=None, context_id=None):
-        domain = args[domain_id] if domain_id and len(args) - 1 >= domain_id  else []
-        context = args[context_id] if context_id and len(args) - 1 >= context_id  else {}
+        has_domain = domain_id is not None and domain_id < len(args)
+        has_context = context_id is not None and context_id < len(args)
+
+        domain = args[domain_id] if has_domain else []
+        context = args[context_id] if has_context else {}
         c, d = eval_context_and_domain(req.session, context, domain)
-        if domain_id and len(args) - 1 >= domain_id:
+        if has_domain:
             args[domain_id] = d
-        if context_id and len(args) - 1 >= context_id:
+        if has_context:
             args[context_id] = c
 
         for i in xrange(len(args)):
@@ -800,12 +854,12 @@ class View(openerpweb.Controller):
         context = req.session.eval_context(req.context)
         fvg = Model.fields_view_get(view_id, view_type, context, toolbar, submenu)
         # todo fme?: check that we should pass the evaluated context here
-        self.process_view(req.session, fvg, context, transform)
+        self.process_view(req.session, fvg, context, transform, (view_type == 'kanban'))
         if toolbar and transform:
             self.process_toolbar(req, fvg['toolbar'])
         return fvg
 
-    def process_view(self, session, fvg, context, transform):
+    def process_view(self, session, fvg, context, transform, preserve_whitespaces=False):
         # depending on how it feels, xmlrpclib.ServerProxy can translate
         # XML-RPC strings to ``str`` or ``unicode``. ElementTree does not
         # enjoy unicode strings which can not be trivially converted to
@@ -823,7 +877,7 @@ class View(openerpweb.Controller):
             xml = self.transform_view(arch, session, evaluation_context)
         else:
             xml = ElementTree.fromstring(arch)
-        fvg['arch'] = web.common.xml2json.Xml2Json.convert_element(xml)
+        fvg['arch'] = web.common.xml2json.Xml2Json.convert_element(xml, preserve_whitespaces)
 
         for field in fvg['fields'].itervalues():
             if field.get('views'):
@@ -901,10 +955,12 @@ class View(openerpweb.Controller):
             domain = elem.get(el, '').strip()
             if domain:
                 elem.set(el, parse_domain(domain, session))
+                elem.set(el + '_string', domain)
         for el in ['context', 'default_get']:
             context_string = elem.get(el, '').strip()
             if context_string:
                 elem.set(el, parse_context(context_string, session))
+                elem.set(el + '_string', context_string)
 
     @openerpweb.jsonrequest
     def load(self, req, model, view_id, view_type, toolbar=False):
@@ -1021,6 +1077,44 @@ class SearchView(View):
                                              }, context)
         return to_return
 
+    @openerpweb.jsonrequest
+    def add_to_dashboard(self, req, menu_id, action_id, context_to_save, domain, view_mode, name=''):
+        ctx = web.common.nonliterals.CompoundContext(context_to_save)
+        ctx.session = req.session
+        ctx = ctx.evaluate()
+        domain = web.common.nonliterals.CompoundDomain(domain)
+        domain.session = req.session
+        domain = domain.evaluate()
+
+        dashboard_action = load_actions_from_ir_values(req, 'action', 'tree_but_open',
+                                             [('ir.ui.menu', menu_id)], False)
+        if dashboard_action:
+            action = dashboard_action[0][2]
+            if action['res_model'] == 'board.board' and action['views'][0][1] == 'form':
+                # Maybe should check the content instead of model board.board ?
+                view_id = action['views'][0][0]
+                board = req.session.model(action['res_model']).fields_view_get(view_id, 'form')
+                if board and 'arch' in board:
+                    xml = ElementTree.fromstring(board['arch'])
+                    column = xml.find('./board/column')
+                    if column:
+                        new_action = ElementTree.Element('action', {
+                                'name' : str(action_id),
+                                'string' : name,
+                                'view_mode' : view_mode,
+                                'context' : str(ctx),
+                                'domain' : str(domain)
+                            })
+                        column.insert(0, new_action)
+                        arch = ElementTree.tostring(xml, 'utf-8')
+                        return req.session.model('ir.ui.view.custom').create({
+                                'user_id': req.session._uid,
+                                'ref_id': view_id,
+                                'arch': arch
+                            }, req.session.eval_context(req.context))
+
+        return False
+
 class Binary(openerpweb.Controller):
     _cp_path = "/web/binary"
 
@@ -1159,19 +1253,26 @@ class Export(View):
 
     @openerpweb.jsonrequest
     def get_fields(self, req, model, prefix='', parent_name= '',
-                   import_compat=True, parent_field_type=None):
+                   import_compat=True, parent_field_type=None,
+                   exclude=None):
 
         if import_compat and parent_field_type == "many2one":
             fields = {}
         else:
             fields = self.fields_get(req, model)
-        fields['.id'] = fields.pop('id') if 'id' in fields else {'string': 'ID'}
+
+        if import_compat:
+            fields.pop('id', None)
+        else:
+            fields['.id'] = fields.pop('id', {'string': 'ID'})
 
         fields_sequence = sorted(fields.iteritems(),
             key=lambda field: field[1].get('string', ''))
 
         records = []
         for field_name, field in fields_sequence:
+            if import_compat and (exclude and field_name in exclude):
+                continue
             if import_compat and field.get('readonly'):
                 # If none of the field's states unsets readonly, skip the field
                 if all(dict(attrs).get('readonly', True)
@@ -1183,7 +1284,8 @@ class Export(View):
             record = {'id': id, 'string': name,
                       'value': id, 'children': False,
                       'field_type': field.get('type'),
-                      'required': field.get('required')}
+                      'required': field.get('required'),
+                      'relation_field': field.get('relation_field')}
             records.append(record)
 
             if len(name.split('/')) < 3 and 'relation' in field:
@@ -1215,7 +1317,6 @@ class Export(View):
     def fields_info(self, req, model, export_fields):
         info = {}
         fields = self.fields_get(req, model)
-        fields['.id'] = fields.pop('id') if 'id' in fields else {'string': 'ID'}
 
         # To make fields retrieval more efficient, fetch all sub-fields of a
         # given field at the same time. Because the order in the export list is
@@ -1298,7 +1399,7 @@ class Export(View):
 
         context = req.session.eval_context(req.context)
         Model = req.session.model(model)
-        ids = ids or Model.search(domain, context=context)
+        ids = ids or Model.search(domain, 0, False, False, context)
 
         field_names = map(operator.itemgetter('name'), fields)
         import_data = Model.export_data(ids, field_names, context).get('datas',[])
@@ -1376,6 +1477,7 @@ class ExcelExport(Export):
             for cell_index, cell_value in enumerate(row):
                 if isinstance(cell_value, basestring):
                     cell_value = re.sub("\r", " ", cell_value)
+                if cell_value is False: cell_value = None
                 worksheet.write(row_index + 1, cell_index, cell_value, style)
 
         fp = StringIO()