[FIX] tools: some import cleaning and fixing.
[odoo/odoo.git] / openerp / tools / yaml_import.py
index f7f5f63..ca7ab92 100644 (file)
@@ -286,8 +286,11 @@ class YamlInterpreter(object):
         model = self.get_model(record.model)
 
         view_id = record.view
-        if view_id and (view_id is not True):
-            view_id = self.pool.get('ir.model.data').get_object_reference(self.cr, SUPERUSER_ID, self.module, record.view)[1]
+        if view_id and (view_id is not True) and isinstance(view_id, basestring):
+            module = self.module
+            if '.' in view_id:
+                module, view_id = view_id.split('.',1)
+            view_id = self.pool.get('ir.model.data').get_object_reference(self.cr, SUPERUSER_ID, module, view_id)[1]
 
         if model.is_transient():
             record_dict=self.create_osv_memory_record(record, fields)
@@ -328,15 +331,23 @@ class YamlInterpreter(object):
                 self.cr.commit()
 
     def _create_record(self, model, fields, view_info=False, parent={}, default=True):
-        view = view_info and etree.fromstring(view_info['arch'].encode('utf-8')) or False
-        if view is not False:
-            defaults = default and model._add_missing_default_values(self.cr, SUPERUSER_ID, {}, context=self.context) or {}
-            fg = model.fields_get(self.cr, SUPERUSER_ID, context=self.context)
-        else:
-            defaults = {}
-            fg = {}
-        record_dict = defaults.copy()
-        fields = fields or {}
+        """This function processes the !record tag in yalm files. It simulates the record creation through an xml
+            view (either specified on the !record tag or the default one for this object), including the calls to
+            on_change() functions, and sending only values for fields that aren't set as readonly.
+            :param model: model instance
+            :param fields: dictonary mapping the field names and their values
+            :param view_info: result of fields_view_get() called on the object
+            :param parent: dictionary containing the values already computed for the parent, in case of one2many fields
+            :param default: if True, the default values must be processed too or not
+            :return: dictionary mapping the field names and their values, ready to use when calling the create() function
+            :rtype: dict
+        """
+        def _get_right_one2many_view(fg, field_name, view_type):
+            one2many_view = fg[field_name]['views'].get(view_type)
+            # if the view is not defined inline, we call fields_view_get()
+            if not one2many_view:
+                one2many_view = self.pool.get(fg[field_name]['relation']).fields_view_get(self.cr, SUPERUSER_ID, False, view_type, self.context)
+            return one2many_view
 
         def process_val(key, val):
             if fg[key]['type']=='many2one':
@@ -346,71 +357,110 @@ class YamlInterpreter(object):
                 if val is False:
                     val = []
                 if len(val) and type(val[0]) == dict:
+                    #we want to return only the fields that aren't readonly
+                    #For that, we need to first get the right tree view to consider for the field `key´
+                    one2many_tree_view = _get_right_one2many_view(fg, key, 'tree')
+                    for rec in val:
+                        #make a copy for the iteration, as we will alterate the size of `rec´ dictionary
+                        rec_copy = rec.copy()
+                        for field_key in rec_copy:
+                            #seek in the view for the field `field_key´ and removing it from `key´ values, as this column is readonly in the tree view
+                            subfield_obj = etree.fromstring(one2many_tree_view['arch'].encode('utf-8')).xpath("//field[@name='%s']"%(field_key))
+                            if subfield_obj and (subfield_obj[0].get('modifiers', '{}').find('"readonly": true') >= 0):
+                                #TODO: currently we only support if readonly is True in the modifiers. Some improvement may be done in 
+                                #order to support also modifiers that look like {"readonly": [["state", "not in", ["draft", "confirm"]]]}
+                                del(rec[field_key])
+
+                    #now that unwanted values have been removed from val, we can encapsulate it in a tuple as returned value
                     val = map(lambda x: (0,0,x), val)
+
+            #we want to return only the fields that aren't readonly
+            if el.get('modifiers', '{}').find('"readonly": true') >= 0:
+                #TODO: currently we only support if readonly is True in the modifiers. Some improvement may be done in 
+                #order to support also modifiers that look like {"readonly": [["state", "not in", ["draft", "confirm"]]]}
+                return False
             return val
 
-        # Process all on_change calls
-        nodes = (view is not False) and [view] or []
-        while nodes:
-            el = nodes.pop(0)
-            if el.tag=='field':
-                field_name = el.attrib['name']
-                assert field_name in fg, "The field '%s' is defined in the form view but not on the object '%s'!" % (field_name, model._name)
-                if field_name in fields:
-                    one2many_form_view = None
-                    # if the form view is not inline, we call fields_view_get
-                    if (view is not False) and (fg[field_name]['type']=='one2many'):
-                        one2many_form_view = view_info['fields'][field_name]['views'].get('form')
-                        if not one2many_form_view:
-                            one2many_form_view = self.pool.get(fg[field_name]['relation']).fields_view_get(self.cr, SUPERUSER_ID, False, 'form', self.context)
-
-                    field_value = self._eval_field(model, field_name, fields[field_name], one2many_form_view or view_info, parent=record_dict, default=default)
-                    record_dict[field_name] = field_value
-                    #if (field_name in defaults) and defaults[field_name] == field_value:
-                    #    print '*** You can remove these lines:', field_name, field_value
-
-                #if field_name has a default value or a value is given in the yaml file, we must call its on_change()
-                elif field_name not in defaults:
-                    continue
-
-                if not el.attrib.get('on_change', False):
-                    continue
-                match = re.match("([a-z_1-9A-Z]+)\((.*)\)", el.attrib['on_change'])
-                assert match, "Unable to parse the on_change '%s'!" % (el.attrib['on_change'], )
-
-                # creating the context
-                class parent2(object):
-                    def __init__(self, d):
-                        self.d = d
-                    def __getattr__(self, name):
-                        return self.d.get(name, False)
-
-                ctx = record_dict.copy()
-                ctx['context'] = self.context
-                ctx['uid'] = SUPERUSER_ID
-                ctx['parent'] = parent2(parent)
-                for a in fg:
-                    if a not in ctx:
-                        ctx[a]=process_val(a, defaults.get(a, False))
-
-                # Evaluation args
-                args = map(lambda x: eval(x, ctx), match.group(2).split(','))
-                result = getattr(model, match.group(1))(self.cr, SUPERUSER_ID, [], *args)
-                for key, val in (result or {}).get('value', {}).items():
-                    if key not in fields:
-                        assert key in fg, "The returning field '%s' from your on_change call '%s' does not exist on the object '%s'" % (key, match.group(1), model._name)
+        if view_info:
+            arch = etree.fromstring(view_info['arch'].encode('utf-8'))
+            view = arch if len(arch) else False
+        else:
+            view = False
+        fields = fields or {}
+        if view is not False:
+            fg = view_info['fields']
+            # gather the default values on the object. (Can't use `fields´ as parameter instead of {} because we may
+            # have references like `base.main_company´ in the yaml file and it's not compatible with the function)
+            defaults = default and model._add_missing_default_values(self.cr, SUPERUSER_ID, {}, context=self.context) or {}
+
+            # copy the default values in record_dict, only if they are in the view (because that's what the client does)
+            # the other default values will be added later on by the create().
+            record_dict = dict([(key, val) for key, val in defaults.items() if key in fg])
+
+            # Process all on_change calls
+            nodes = [view]
+            while nodes:
+                el = nodes.pop(0)
+                if el.tag=='field':
+                    field_name = el.attrib['name']
+                    assert field_name in fg, "The field '%s' is defined in the form view but not on the object '%s'!" % (field_name, model._name)
+                    if field_name in fields:
+                        one2many_form_view = None
+                        if (view is not False) and (fg[field_name]['type']=='one2many'):
+                            # for one2many fields, we want to eval them using the inline form view defined on the parent
+                            one2many_form_view = _get_right_one2many_view(fg, field_name, 'form')
+
+                        field_value = self._eval_field(model, field_name, fields[field_name], one2many_form_view or view_info, parent=record_dict, default=default)
+
+                        #call process_val to not update record_dict if values were given for readonly fields
+                        val = process_val(field_name, field_value)
+                        if val:
+                            record_dict[field_name] = val
+                        #if (field_name in defaults) and defaults[field_name] == field_value:
+                        #    print '*** You can remove these lines:', field_name, field_value
+
+                    #if field_name has a default value or a value is given in the yaml file, we must call its on_change()
+                    elif field_name not in defaults:
+                        continue
+
+                    if not el.attrib.get('on_change', False):
+                        continue
+                    match = re.match("([a-z_1-9A-Z]+)\((.*)\)", el.attrib['on_change'])
+                    assert match, "Unable to parse the on_change '%s'!" % (el.attrib['on_change'], )
+
+                    # creating the context
+                    class parent2(object):
+                        def __init__(self, d):
+                            self.d = d
+                        def __getattr__(self, name):
+                            return self.d.get(name, False)
+
+                    ctx = record_dict.copy()
+                    ctx['context'] = self.context
+                    ctx['uid'] = SUPERUSER_ID
+                    ctx['parent'] = parent2(parent)
+                    for a in fg:
+                        if a not in ctx:
+                            ctx[a] = process_val(a, defaults.get(a, False))
+
+                    # Evaluation args
+                    args = map(lambda x: eval(x, ctx), match.group(2).split(','))
+                    result = getattr(model, match.group(1))(self.cr, SUPERUSER_ID, [], *args)
+                    for key, val in (result or {}).get('value', {}).items():
+                        assert key in fg, "The returning field '%s' from your on_change call '%s' does not exist either on the object '%s', either in the view '%s' used for the creation" % (key, match.group(1), model._name, view_info['name'])
                         record_dict[key] = process_val(key, val)
                         #if (key in fields) and record_dict[key] == process_val(key, val):
                         #    print '*** You can remove these lines:', key, val
-            else:
-                nodes = list(el) + nodes
+                else:
+                    nodes = list(el) + nodes
+        else:
+            record_dict = {}
 
         for field_name, expression in fields.items():
             if field_name in record_dict:
                 continue
             field_value = self._eval_field(model, field_name, expression, default=False)
             record_dict[field_name] = field_value
-
         return record_dict
 
     def process_ref(self, node, column=None):