[merge]
[odoo/odoo.git] / addons / web / static / src / js / view_form.js
index 17b9849..e517e68 100644 (file)
@@ -11,8 +11,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
      * view should be displayed (if there is one active).
      */
     searchable: false,
-    readonly : false,
-    form_template: "FormView",
+    template: "FormView",
     display_name: _lt('Form'),
     /**
      * @constructs openerp.web.FormView
@@ -34,8 +33,6 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
         this.model = dataset.model;
         this.view_id = view_id || false;
         this.fields_view = {};
-        this.widgets = {};
-        this.widgets_counter = 0;
         this.fields = {};
         this.fields_order = [];
         this.datarecord = {};
@@ -52,6 +49,8 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
         this.mutating_mutex = new $.Mutex();
         this.on_change_mutex = new $.Mutex();
         this.reload_mutex = new $.Mutex();
+        this.set({"force_readonly": false});
+        this.rendering_engine = new openerp.web.FormRenderingEngine(this);
     },
     start: function() {
         this._super();
@@ -79,7 +78,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
             this.sidebar.attachments.destroy();
             this.sidebar.destroy();
         }
-        _.each(this.widgets, function(w) {
+        _.each(this.get_widgets(), function(w) {
             w.destroy();
         });
         this._super();
@@ -91,22 +90,17 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
     on_loaded: function(data) {
         var self = this;
         if (!data) {
-               throw "No data provided.";
+            throw new Error("No data provided.");
         }
-        if (this.root_frame) {
-               throw "Form view does not support multiple calls to on_loaded";
+        if (this.arch) {
+            throw "Form view does not support multiple calls to on_loaded";
         }
         this.fields_order = [];
         this.fields_view = data;
 
-        this.rendered = QWeb.render(this.form_template, {'widget': this});
-        this.$element.html(this.rendered);
-        
-        this.root_frame = instanciate_widget(this.registry.get_object('frame'), this, this.fields_view.arch);
-        var to_append = $(".oe_form_header", this.$element);
-        this.root_frame.appendTo(to_append.length > 0 ? to_append : this.$element);
-        this.root_frame.$element.children().unwrap();
-        
+        this.rendering_engine.set_fields_view(data);
+        this.rendering_engine.render_to(this.$element.find('.oe_form_container'));
+
         this.$form_header = this.$element.find('.oe_form_header:first');
         this.$form_header.find('div.oe_form_pager button[data-pager-action]').click(function() {
             var action = $(this).data('pager-action');
@@ -211,7 +205,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
             if (self.sidebar) {
                 self.sidebar.attachments.do_update();
             }
-            if (self.default_focus_field && !self.embedded_view) {
+            if (self.default_focus_field) {
                 self.default_focus_field.focus();
             }
             if (record.id) {
@@ -221,14 +215,13 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
         });
     },
     on_form_changed: function() {
-        for (var w in this.widgets) {
-            w = this.widgets[w];
+        _.each(this.get_widgets(), function(w) {
             w.process_modifiers();
             if (w.field) {
                 w.validate();
             }
             w.update_dom();
-        }
+        });
     },
     do_notify_change: function() {
         this.$element.addClass('oe_form_dirty');
@@ -296,7 +289,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
             }
             // form field
             if (self.fields[field]) {
-                var value = self.fields[field].get_on_change_value();
+                var value = self.fields[field].get_value();
                 return value == null ? false : value;
             }
             // parent field
@@ -366,7 +359,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
                         value = response.value[fieldname];
                     } else {
                         // otherwise get form value for field
-                        value = self.fields[fieldname].get_on_change_value();
+                        value = self.fields[fieldname].get_value();
                     }
                     var condition = fieldname + '=' + value;
 
@@ -503,7 +496,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
                     if (!first_invalid_field) {
                         first_invalid_field = f;
                     }
-                } else if (f.name !== 'id' && !f.readonly && (!self.datarecord.id || f.is_dirty())) {
+                } else if (f.name !== 'id' && !f.get("readonly") && (!self.datarecord.id || f.is_dirty())) {
                     // Special case 'id' field, do not save this field
                     // on 'create' : save all non readonly fields
                     // on 'edit' : save non readonly modified fields
@@ -542,7 +535,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
         var msg = "<ul>";
         _.each(this.fields, function(f) {
             if (!f.is_valid()) {
-                msg += "<li>" + f.string + "</li>";
+                msg += "<li>" + f.node.attrs.string + "</li>";
             }
         });
         msg += "</ul>";
@@ -607,6 +600,11 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
             }
         });
     },
+    get_widgets: function() {
+        return _.filter(this.getChildren(), function(obj) {
+            return obj instanceof openerp.web.form.Widget;
+        });
+    },
     get_fields_values: function(blacklist) {
        blacklist = blacklist || [];
         var values = {};
@@ -659,7 +657,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
                 // or m2m
                 if (!value
                         || field.invisible
-                        || field.readonly
+                        || field.get("readonly")
                         || field.field.type === 'one2many'
                         || field.field.type === 'many2many') {
                     return false;
@@ -680,7 +678,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
 
                 return {
                     name: name,
-                    string: field.string,
+                    string: field.node_atts.string,
                     value: value,
                     displayed: displayed,
                     // convert undefined to false
@@ -688,7 +686,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
                 }
             })
             .compact()
-            .sortBy(function (field) { return field.string; })
+            .sortBy(function (field) { return field.node_atts.string; })
             .value();
         var conditions = _.chain(fields)
             .filter(function (field) { return field.change_default; })
@@ -727,6 +725,276 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
         d.open();
     }
 });
+
+/**
+ * Interface to be implemented by rendering engines for the form view.
+ */
+openerp.web.FormRenderingEngineInterface = {
+    set_fields_view: function(fields_view) {},
+    render_to: function($element) {},
+};
+
+/**
+ * Default rendering engine for the form view.
+ * 
+ * It is necessary to set the view using set_view() before usage.
+ */
+openerp.web.FormRenderingEngine = openerp.web.Class.extend({
+    init: function(view) {
+        this.view = view;
+        this.legacy_mode = false;
+    },
+    set_fields_view: function(fvg) {
+        this.fvg = fvg;
+        this.legacy_mode = (this.fvg.arch.tag === 'form');
+    },
+    set_registry: function(registry) {
+        this.registry = registry;
+    },
+    render_to: function($element) {
+        var self = this;
+        this.$element = $element;
+
+        // TODO: I know this will save the world and all the kitten for a moment,
+        //       but one day, we will have to get rid of xml2json
+        var xml = openerp.web.json_node_to_xml(this.fvg.arch);
+        this.$form = $(xml);
+
+        this.process(this.$form);
+
+        this.$form.appendTo(this.$element);
+        // OpenERP views spec :
+        //      - @width is obsolete ?
+
+        this.$element.find('field, button').each(function() {
+            var $elem = $(this),
+                key = $elem.attr('widget') || $elem[0].tagName.toLowerCase();
+            if (self.view.registry.contains(key)) {
+                var obj = self.view.registry.get_object(key);
+                var w = new (obj)(self.view, openerp.web.xml_to_json($elem[0]));
+                self.alter_field(w);
+                w.replace($elem);
+            }
+        });
+        $('<button>Debug layout</button>').appendTo(this.$element).click($.proxy(this.toggle_layout_debugging, this));
+    },
+    render_element: function(template, dict) {
+        dict = dict || {};
+        dict.legacy_mode = this.legacy_mode;
+        return $(QWeb.render(template, dict));
+    },
+    alter_field: function(field) {},
+    toggle_layout_debugging: function() {
+        if (!this.$element.has('.oe_layout_debug_cell:first').length) {
+            this.$element.find('.oe_form_group_cell').each(function() {
+                var text = 'W:' + ($(this).attr('width') || '') + ' - C:' + $(this).attr('colspan'),
+                    $span = $('<span class="oe_layout_debug_cell"/>').text(text);
+                $span.prependTo($(this));
+            });
+        }
+        this.$element.toggleClass('oe_layout_debugging');
+
+    },
+    process: function($tag) {
+        var self = this;
+        var tagname = $tag[0].nodeName.toLowerCase();
+        var fn = self['process_' + tagname]; 
+        if (this.registry && this.registry.contains(tagname)) {
+            fn = this.registry.get_object(tagname);
+        }
+        if (fn) {
+            var args = [].slice.call(arguments);
+            args[0] = $tag;
+            return fn.apply(self, args);
+        } else {
+            // generic tag handling, just process children
+            $tag.children().each(function() {
+                self.process($(this));
+            });
+            return $tag;
+        }
+    },
+    process_form: function($form) {
+        var $new_form = this.render_element('FormRenderingForm', $form.getAttributes());
+            $dst = this.legacy_mode ? $new_form.find('group:first') : $new_form;
+        $form.children().appendTo($dst);
+        if ($form[0] === this.$form[0]) {
+            // If root element, replace it
+            this.$form = $new_form;
+        } else {
+            $form.before($new_form).remove();
+        }
+        this.process($new_form);
+    },
+    preprocess_field: function($field) {
+        var name = $field.attr('name'),
+            field_orm = this.fvg.fields[name],
+            field_string = $field.attr('string') || field_orm.string || '',
+            field_help = $field.attr('help') || field_orm.help || '',
+            field_colspan = parseInt($field.attr('colspan'), 10);
+        if ($field.attr('nolabel') !== '1') {
+            $field.attr('nolabel', '1');
+            var $label = this.$form.find('label[for="' + name + '"]');
+            if (!$label.length) {
+                field_string = $label.attr('string') || $label.text() || field_string;
+                field_help = $label.attr('help') || field_help;
+                $label = $('<label/>').attr({
+                    'for' : name,
+                    'string' : field_string,
+                    'help' :  field_help
+                });
+                $label.insertBefore($field);
+                if (field_colspan > 1) {
+                    $field.attr('colspan', field_colspan - 1);
+                }
+            }
+            return $label;
+        }
+    },
+    process_field: function($field) {
+        var $label = this.preprocess_field($field);
+        if ($label)
+            this.process($label);
+        var name = $field.attr('name'),
+            field_orm = this.fvg.fields[name],
+            field_string = $field.attr('string') || field_orm.string || '',
+            field_help = $field.attr('help') || field_orm.help || '',
+            field_colspan = parseInt($field.attr('colspan'), 10);
+
+        if (!field_orm) {
+            throw new Error("Field '" + name + "' specified in view could not be found.");
+        }
+
+        $field.attr({
+            'widget' : $field.attr('widget') || field_orm.type,
+            'string' : field_string,
+            'help' : field_help
+        });
+        return $field;
+    },
+    process_group: function($group) {
+        var self = this;
+        $group.children('field').each(function() {
+            self.preprocess_field($(this));
+        });
+        var $new_group = $(QWeb.render('FormRenderingGroup', $group.getAttributes())),
+            $table;
+        if ($new_group.is('table')) {
+            $table = $new_group;
+        } else {
+            $table = $new_group.find('table:first');
+        }
+        $table.addClass('oe_form_group');
+        var $tr, $td,
+            cols = parseInt($group.attr('col') || 4, 10),
+            row_cols = cols;
+
+        var children = [];
+        $group.children().each(function(a,b,c) {
+            var $child = $(this),
+                colspan = parseInt($child.attr('colspan') || 1, 10),
+                tagName = $child[0].tagName.toLowerCase();
+            if (tagName === 'newline') {
+                $tr = null;
+                return;
+            }
+            if (!$tr || row_cols < colspan) {
+                $tr = $('<tr/>').addClass('oe_form_group_row').appendTo($table);
+                row_cols = cols;
+            }
+            row_cols -= colspan;
+            $td = $('<td/>').addClass('oe_form_group_cell').attr('colspan', colspan);
+            $tr.append($td.append($child));
+            children.push($child[0]);
+        });
+        if (row_cols) {
+            $td.attr('colspan', parseInt($td.attr('colspan'), 10) + row_cols);
+        }
+        $group.before($new_group).remove();
+
+        // Now compute width of cells
+        $table.find('tbody > tr').each(function() {
+            var to_compute = [],
+                row_cols = cols,
+                total = 100;
+            $(this).children().each(function() {
+                var $td = $(this),
+                    $child = $td.children(':first');
+                switch ($child[0].tagName.toLowerCase()) {
+                    case 'separator':
+                        if ($child.attr('orientation') === 'vertical') {
+                            $td.addClass('oe_vertical_separator').attr('width', '1');
+                            $td.empty();
+                            row_cols--;
+                        }
+                        break;
+                    case 'label':
+                        if ($child.attr('for')) {
+                            $td.attr('width', '1%');
+                            row_cols--;
+                            total--;
+                        }
+                        break;
+                    default:
+                        to_compute.push($td);
+                }
+            });
+            var unit = Math.floor(total / row_cols);
+            _.each(to_compute, function($td, i) {
+                var width = parseInt($td.attr('colspan'), 10) * unit;
+                $td.attr('width', ((i == to_compute.length - 1) ? total : width) + '%');
+                total -= width;
+            });
+        });
+        _.each(children, function(el) {
+            self.process($(el));
+        });
+        return $new_group;
+    },
+    process_notebook: function($notebook) {
+        var self = this;
+        var pages = [];
+        $notebook.find('> page').each(function() {
+            var $page = $(this),
+                page_attrs = $page.getAttributes();
+            page_attrs.id = _.uniqueId('notebook_page_');
+            pages.push(page_attrs);
+            var $new_page = self.render_element('FormRenderingNotebookPage', page_attrs),
+                $dst = self.legacy_mode ? $new_page.find('group:first') : $new_page;
+            $page.children().appendTo($dst);
+            $page.before($new_page).remove();
+        });
+        var $new_notebook = $(QWeb.render('FormRenderingNotebook', { pages : pages }));
+        $notebook.children().appendTo($new_notebook);
+        $notebook.before($new_notebook).remove();
+        $new_notebook.children().each(function() {
+            self.process($(this));
+        });
+        $new_notebook.tabs();
+        return $new_notebook;
+    },
+    process_separator: function($separator) {
+        var $new_separator = $(QWeb.render('FormRenderingSeparator', $separator.getAttributes()));
+        $separator.before($new_separator).remove();
+        return $new_separator;
+    },
+    process_label: function($label) {
+        var dict = $label.getAttributes();
+        var align = parseFloat(dict.align);
+        if (isNaN(align) || align === 1) {
+            align = 'right';
+        } else if (align === 0) {
+            align = 'left';
+        } else {
+            align = 'center';
+        }
+        dict.align = align;
+        var $new_label = $(QWeb.render('FormRenderingLabel', dict));
+        $label.before($new_label).remove();
+        return $new_label;
+    }
+});
+
 openerp.web.FormDialog = openerp.web.Dialog.extend({
     init: function(parent, options, view_id, dataset) {
         this._super(parent, options);
@@ -884,11 +1152,10 @@ openerp.web.form.compute_domain = function(expr, fields) {
     return _.all(stack, _.identity);
 };
 
-openerp.web.form.Widget = openerp.web.OldWidget.extend(/** @lends openerp.web.form.Widget# */{
-    form_template: 'Widget',
+openerp.web.form.Widget = openerp.web.Widget.extend(/** @lends openerp.web.form.Widget# */{
     /**
      * @constructs openerp.web.form.Widget
-     * @extends openerp.web.OldWidget
+     * @extends openerp.web.Widget
      *
      * @param view
      * @param node
@@ -898,30 +1165,10 @@ openerp.web.form.Widget = openerp.web.OldWidget.extend(/** @lends openerp.web.fo
         this.node = node;
         this.modifiers = JSON.parse(this.node.attrs.modifiers || '{}');
         this.always_invisible = (this.modifiers.invisible && this.modifiers.invisible === true);
-        this.type = this.type || node.tag;
-        this.element_name = this.element_name || this.type;
 
         this._super(view);
 
-        this.children = node.children;
-        this.colspan = parseInt(node.attrs.colspan || 1, 10);
-        this.decrease_max_width = 0;
-
-        this.string = this.string || node.attrs.string;
-        this.help = this.help || node.attrs.help;
         this.invisible = this.modifiers['invisible'] === true;
-        this.classname = 'oe_form_' + this.type;
-
-        this.align = parseFloat(this.node.attrs.align);
-        if (isNaN(this.align) || this.align === 1) {
-            this.align = 'right';
-        } else if (this.align === 0) {
-            this.align = 'left';
-        } else {
-            this.align = 'center';
-        }
-
-        this.width = this.node.attrs.width;
     },
     destroy: function() {
         this._super.apply(this, arguments);
@@ -929,16 +1176,17 @@ openerp.web.form.Widget = openerp.web.OldWidget.extend(/** @lends openerp.web.fo
     },
     process_modifiers: function() {
         var compute_domain = openerp.web.form.compute_domain;
+        var to_set = {};
         for (var a in this.modifiers) {
-            this[a] = compute_domain(this.modifiers[a], this.view.fields);
+            var val = compute_domain(this.modifiers[a], this.view.fields);
+            this[a] = val;
+            to_set[a] = val;
         }
+        this.set(to_set);
     },
     update_dom: function() {
         this.$element.toggle(!this.invisible);
     },
-    render_element: function() {
-       this.$element.html(QWeb.render(this.form_template, { "widget": this }));
-    },
     do_attach_tooltip: function(widget, trigger, options) {
         widget = widget || this;
         trigger = trigger || this.$element;
@@ -947,7 +1195,7 @@ openerp.web.form.Widget = openerp.web.OldWidget.extend(/** @lends openerp.web.fo
                 delayOut: 0,
                 fade: true,
                 title: function() {
-                    var template = widget.form_template + '.tooltip';
+                    var template = widget.template + '.tooltip';
                     if (!QWeb.has_template(template)) {
                         template = 'WidgetLabel.tooltip';
                     }
@@ -1011,229 +1259,21 @@ openerp.web.form.Widget = openerp.web.OldWidget.extend(/** @lends openerp.web.fo
     }
 });
 
-openerp.web.form.WidgetFrame = openerp.web.form.Widget.extend({
-    form_template: 'WidgetFrame',
-    init: function(view, node) {
-        this._super(view, node);
-        this.columns = parseInt(node.attrs.col || 4, 10);
-        this.x = 0;
-        this.y = 0;
-        this.table = [];
-        this.add_row();
-        for (var i = 0; i < node.children.length; i++) {
-            var n = node.children[i];
-            if (n.tag == "newline") {
-                this.add_row();
-            } else {
-                this.handle_node(n);
-            }
-        }
-        this.set_row_cells_with(this.table[this.table.length - 1]);
-    },
-    render_element: function() {
-       this._super();
-       var self = this;
-           _.each(this.table, function(row) {
-               _.each(row, function(td) {
-                       td.$element = self.$element.find('.' + td.element_class);
-                       td.render_element();
-               });
-           });
-    },
-    start: function() {
-           _.each(this.table, function(row) {
-               _.each(row, function(td) {
-                       td.start();
-               });
-           });
-    },
-    add_row: function(){
-        if (this.table.length) {
-            this.set_row_cells_with(this.table[this.table.length - 1]);
-        }
-        var row = [];
-        this.table.push(row);
-        this.x = 0;
-        this.y += 1;
-        return row;
-    },
-    set_row_cells_with: function(row) {
-        var bypass = 0,
-            max_width = 100,
-            row_length = row.length;
-        for (var i = 0; i < row.length; i++) {
-            if (row[i].always_invisible) {
-                row_length--;
-            } else {
-                bypass += row[i].width === undefined ? 0 : 1;
-                max_width -= row[i].decrease_max_width;
-            }
-        }
-        var size_unit = Math.round(max_width / (this.columns - bypass)),
-            colspan_sum = 0;
-        for (var i = 0; i < row.length; i++) {
-            var w = row[i];
-            if (w.always_invisible) {
-                continue;
-            }
-            colspan_sum += w.colspan;
-            if (w.width === undefined) {
-                var width = (i === row_length - 1 && colspan_sum === this.columns) ? max_width : Math.round(size_unit * w.colspan);
-                max_width -= width;
-                w.width = width + '%';
-            }
-        }
-    },
-    handle_node: function(node) {
-        var type = {};
-        if (node.tag == 'field') {
-            type = this.view.fields_view.fields[node.attrs.name] || {};
-            if (node.attrs.widget == 'statusbar' && node.attrs.nolabel !== '1') {
-                // This way we can retain backward compatibility between addons and old clients
-                node.attrs.colspan = (parseInt(node.attrs.colspan, 10) || 1) + 1;
-                node.attrs.nolabel = '1';
-            }
-        }
-        var widget = instanciate_widget(this.view.registry.get_any(
-                [node.attrs.widget, type.type, node.tag]), this.view, node);
-        if (node.tag == 'field') {
-            if (!this.view.default_focus_field || node.attrs.default_focus == '1') {
-                this.view.default_focus_field = widget;
-            }
-            if (node.attrs.nolabel != '1') {
-                var label = instanciate_widget(this.view.registry.get_object('label'), this.view, node);
-                label["for"] = widget;
-                this.add_widget(label, widget.colspan + 1);
-            }
-        }
-        this.add_widget(widget);
-    },
-    add_widget: function(widget, colspan) {
-        var current_row = this.table[this.table.length - 1];
-        if (!widget.always_invisible) {
-            colspan = colspan || widget.colspan;
-            if (current_row.length && (this.x + colspan) > this.columns) {
-                current_row = this.add_row();
-            }
-            this.x += widget.colspan;
-        }
-        current_row.push(widget);
-        return widget;
-    }
-});
-
-openerp.web.form.WidgetGroup = openerp.web.form.WidgetFrame.extend({
-    form_template: 'WidgetGroup'
-}),
-
-openerp.web.form.WidgetNotebook = openerp.web.form.Widget.extend({
-    form_template: 'WidgetNotebook',
-    init: function(view, node) {
-        this._super(view, node);
-        this.pages = [];
-        for (var i = 0; i < node.children.length; i++) {
-            var n = node.children[i];
-            if (n.tag == "page") {
-                var page = instanciate_widget(this.view.registry.get_object('notebookpage'),
-                        this.view, n, this, this.pages.length);
-                this.pages.push(page);
-            }
-        }
-    },
-    render_element: function() {
-       this._super();
-       var self = this;
-           _.each(this.pages, function(page) {
-               page.$element = self.$element.find('.' + page.element_class);
-               page.render_element();
-           });
-    },
-    start: function() {
-        var self = this;
-        this._super.apply(this, arguments);
-        this.$element.find('> ul > li').each(function (index, tab_li) {
-            var page = self.pages[index],
-                id = _.uniqueId(self.element_name + '-');
-            page.element_id = id;
-            $(tab_li).find('a').attr('href', '#' + id);
-        });
-        this.$element.find('> div').each(function (index, page) {
-            page.id = self.pages[index].element_id;
-        });
-        this.$element.tabs();
-        this.view.on_button_new.add_first(this.do_select_first_visible_tab);
-        if (openerp.connection.debug) {
-            this.do_attach_tooltip(this, this.$element.find('ul:first'), {
-                gravity: 's'
-            });
-        }
-           _.each(this.pages, function(page) {
-               page.start();
-           });
-    },
-    do_select_first_visible_tab: function() {
-        for (var i = 0; i < this.pages.length; i++) {
-            var page = this.pages[i];
-            if (page.invisible === false) {
-                this.$element.tabs('select', page.index);
-                break;
-            }
-        }
-    }
-});
-
-openerp.web.form.WidgetNotebookPage = openerp.web.form.WidgetFrame.extend({
-    form_template: 'WidgetNotebookPage',
-    init: function(view, node, notebook, index) {
-        this.notebook = notebook;
-        this.index = index;
-        this.element_name = 'page_' + index;
-        this._super(view, node);
-    },
-    start: function() {
-        this._super.apply(this, arguments);
-        this.$element_tab = this.notebook.$element.find(
-                '> ul > li:eq(' + this.index + ')');
-    },
-    update_dom: function() {
-        if (this.invisible && this.index === this.notebook.$element.tabs('option', 'selected')) {
-            this.notebook.do_select_first_visible_tab();
-        }
-        this.$element_tab.toggle(!this.invisible);
-        this.$element.toggle(!this.invisible);
-    }
-});
-
-openerp.web.form.WidgetSeparator = openerp.web.form.Widget.extend({
-    form_template: 'WidgetSeparator',
-    init: function(view, node) {
-        this._super(view, node);
-        this.orientation = node.attrs.orientation || 'horizontal';
-        if (this.orientation === 'vertical') {
-            this.width = '1';
-        }
-        this.classname += '_' + this.orientation;
-    }
-});
-
 openerp.web.form.WidgetButton = openerp.web.form.Widget.extend({
-    form_template: 'WidgetButton',
+    template: 'WidgetButton',
     init: function(view, node) {
         this._super(view, node);
         this.force_disabled = false;
-        if (this.string) {
-            // We don't have button key bindings in the webclient
-            this.string = this.string.replace(/_/g, '');
-        }
-        if (node.attrs.default_focus == '1') {
+        this.string = (this.node.attrs.string || '').replace(/_/g, '');
+        if (this.node.attrs.default_focus == '1') {
             // TODO fme: provide enter key binding to widgets
             this.view.default_focus_button = this;
         }
     },
     start: function() {
         this._super.apply(this, arguments);
-        this.$element.find("button").click(this.on_click);
-        if (this.help || openerp.connection.debug) {
+        this.$element.click(this.on_click);
+        if (this.node.attrs.help || openerp.connection.debug) {
             this.do_attach_tooltip();
         }
     },
@@ -1251,7 +1291,7 @@ openerp.web.form.WidgetButton = openerp.web.form.Widget.extend({
         var exec_action = function() {
             if (self.node.attrs.confirm) {
                 var def = $.Deferred();
-                var dialog = openerp.web.dialog($('<div>' + self.node.attrs.confirm + '</div>'), {
+                var dialog = openerp.web.dialog($('<div/>').text(self.node.attrs.confirm), {
                     title: _t('Confirm'),
                     modal: true,
                     buttons: [
@@ -1300,95 +1340,126 @@ openerp.web.form.WidgetButton = openerp.web.form.Widget.extend({
         this.check_disable();
     },
     check_disable: function() {
-        var disabled = (this.readonly || this.force_disabled || !this.view.is_interactible_record());
-        this.$element.find('button').prop('disabled', disabled);
-        this.$element.find("button").css('color', disabled ? 'grey' : '');
+        var disabled = (this.force_disabled || !this.view.is_interactible_record());
+        this.$element.prop('disabled', disabled);
+        this.$element.css('color', disabled ? 'grey' : '');
     }
 });
 
-openerp.web.form.WidgetLabel = openerp.web.form.Widget.extend({
-    form_template: 'WidgetLabel',
-    init: function(view, node) {
-        this.element_name = 'label_' + node.attrs.name;
-
-        this._super(view, node);
+/**
+ * Interface implemented by the form view or any other object
+ * able to provide the features necessary for the fields to work.
+ * 
+ * Properties:
+ *     - ...
+ */
 
-        if (this.node.tag == 'label' && !this.string && this.node.children.length) {
-            this.string = this.node.children[0];
-            this.align = 'left';
-        }
+openerp.web.form.FieldManagerInterface = {
 
-        if (this.node.tag == 'label' && (this.align === 'left' || this.node.attrs.colspan || (this.string && this.string.length > 32))) {
-            this.form_template = "WidgetParagraph";
-            this.colspan = parseInt(this.node.attrs.colspan || 1, 10);
-            // Widgets default to right-aligned, but paragraph defaults to
-            // left-aligned
-            if (isNaN(parseFloat(this.node.attrs.align))) {
-                this.align = 'left';
-            }
+};
 
-            this.multilines = this.string && _.str.lines(this.string).length > 1;
-        } else {
-            this.colspan = 1;
-            this.width = '1%';
-            this.decrease_max_width = 1;
-            this.nowrap = true;
-        }
-    },
-    render_element: function() {
-       var rendered;
-        if (this['for'] && this.type !== 'label') {
-            rendered = QWeb.render(this.form_template, {widget: this['for']});
-        } else {
-               // Actual label widgets should not have a false and have type label
-               rendered = QWeb.render(this.form_template, {widget: this});
-        }
-       this.$element.html(rendered);
-    },
-    start: function() {
-        this._super();
-        var self = this;
-        if (this['for'] && (this['for'].help || openerp.connection.debug)) {
-            this.do_attach_tooltip(self['for']);
-        }
-        this.$element.find("label").dblclick(function() {
-            var widget = self['for'] || self;
-            openerp.log(widget.element_class , widget);
-            window.w = widget;
-        });
-    }
-});
+/**
+ * Interface to be implemented by fields.
+ * 
+ * Properties:
+ *     - readonly: boolean. If set to true the field should appear in readonly mode.
+ *     - force_readonly: boolean, When it is true, the field should always appear
+ *      in read only mode, no matter what the value of the "readonly" property can be.
+ * Events:
+ *     - ...
+ * 
+ */
+openerp.web.form.FieldInterface = {
+    /**
+     * Constructor takes 2 arguments:
+     * - field_manager: Implements FieldManagerInterface
+     * - node: the "<field>" node in json form
+     */
+    init: function(field_manager, node) {},
+    /**
+     * Called by the form view to indicate the value of the field.
+     * 
+     * set_value() may return an object that can be passed to $.when() that represents the moment when
+     * the field has finished all operations necessary before the user can effectively use the widget.
+     * 
+     * Multiple calls to set_value() can occur at any time and must be handled correctly by the implementation,
+     * regardless of any asynchronous operation currently running and the status of any promise that a
+     * previous call to set_value() could have returned.
+     * 
+     * set_value() must be able, at any moment, to handle the syntax returned by the "read" method of the
+     * osv class in the OpenERP server as well as the syntax used by the set_value() (see below). It must
+     * also be able to handle any other format commonly used in the _defaults key on the models in the addons
+     * as well as any format commonly returned in a on_change. It must be able to autodetect those formats as
+     * no information is ever given to know which format is used.
+     */
+    set_value: function(value) {},
+    /**
+     * Get the current value of the widget.
+     * 
+     * Must always return a syntaxically correct value to be passed to the "write" method of the osv class in
+     * the OpenERP server, although it is not assumed to respect the constraints applied to the field.
+     * For example if the field is marqued as "required", a call to get_value() can return false.
+     * 
+     * get_value() can also be called *before* a call to set_value() and, in that case, is supposed to
+     * return a defaut value according to the type of field.
+     * 
+     * This method is always assumed to perform synchronously, it can not return a promise.
+     * 
+     * If there was no user interaction to modify the value of the field, it is always assumed that
+     * get_value() return the same semantic value than the one passed in the last call to set_value(),
+     * altough the syntax can be different. This can be the case for type of fields that have a different
+     * syntax for "read" and "write" (example: m2o: set_value([0, "Administrator"]), get_value() => 0).
+     */
+    get_value: function() {},
+};
 
-openerp.web.form.Field = openerp.web.form.Widget.extend(/** @lends openerp.web.form.Field# */{
+/**
+ * Abstract class for classes implementing FieldInterface.
+ * 
+ * Properties:
+ *     - effective_readonly: when it is true, the widget is displayed as readonly. Vary depending
+ *      the values of the "readonly" property and the "force_readonly" property on the field manager.
+ * 
+ */
+openerp.web.form.AbstractField = openerp.web.form.Widget.extend(/** @lends openerp.web.form.AbstractField# */{
     /**
-     * @constructs openerp.web.form.Field
+     * @constructs openerp.web.form.AbstractField
      * @extends openerp.web.form.Widget
      *
-     * @param view
+     * @param field_manager
      * @param node
      */
-    init: function(view, node) {
-        this.name = node.attrs.name;
-        this.value = undefined;
-        view.fields[this.name] = this;
-        view.fields_order.push(this.name);
-        this.type = node.attrs.widget || view.fields_view.fields[node.attrs.name].type;
-        this.element_name = "field_" + this.name + "_" + this.type;
-
-        this._super(view, node);
-
-        if (node.attrs.nolabel != '1' && this.colspan > 1) {
-            this.colspan--;
-        }
-        this.field = view.fields_view.fields[node.attrs.name] || {};
-        this.string = node.attrs.string || this.field.string;
-        this.help = node.attrs.help || this.field.help;
-        this.nolabel = (this.field.nolabel || node.attrs.nolabel) === '1';
-        this.readonly = this.modifiers['readonly'] === true;
+    init: function(field_manager, node) {
+        this._super(field_manager, node);
+        this.name = this.node.attrs.name;
+        this.value = false;
+        this.view.fields[this.name] = this;
+        this.view.fields_order.push(this.name);
+        this.type = this.node.attrs.widget;
+        this.field = this.view.fields_view.fields[this.name] || {};
         this.required = this.modifiers['required'] === true;
         this.invalid = this.dirty = false;
-
-        this.classname = 'oe_form_field_' + this.type;
+        
+        // some events to make the property "effective_readonly" sync automatically with "readonly" and
+        // "force_readonly"
+        this.set({"readonly": this.modifiers['readonly'] === true});
+        var test_effective_readonly = function() {
+            this.set({"effective_readonly": this.get("readonly") || this.get("force_readonly")});
+        };
+        this.on("change:readonly", this, test_effective_readonly);
+        this.on("change:force_readonly", this, test_effective_readonly);
+        _.bind(test_effective_readonly, this)();
+
+        // TODO: do something good about this
+        if (this.view) {
+            this.$label = this.view.$element.find('label[for="' + this.name + '"]');
+            if (this.$label.length) {
+                this.id_for_label = _.uniqueId(['field', this.type, this.name, ''].join('_'));
+                this.$label.attr('for', this.id_for_label);
+            } else {
+                this.$label;
+            }
+        }
     },
     start: function() {
         this._super.apply(this, arguments);
@@ -1397,7 +1468,7 @@ openerp.web.form.Field = openerp.web.form.Widget.extend(/** @lends openerp.web.f
             this.$element.addClass('oe_form_field_translatable');
             this.$element.find('.oe_field_translate').click(this.on_translate);
         }
-        if (this.nolabel && openerp.connection.debug) {
+        if (this.node.attrs.nolabel && openerp.connection.debug) {
             this.do_attach_tooltip(this, this.$element);
         }
     },
@@ -1422,10 +1493,7 @@ openerp.web.form.Field = openerp.web.form.Widget.extend(/** @lends openerp.web.f
         return !this.invalid;
     },
     is_dirty: function() {
-        return this.dirty && !this.readonly;
-    },
-    get_on_change_value: function() {
-        return this.get_value();
+        return this.dirty && !this.get("effective_readonly");
     },
     update_dom: function(show_invalid) {
         this._super.apply(this, arguments);
@@ -1433,7 +1501,7 @@ openerp.web.form.Field = openerp.web.form.Widget.extend(/** @lends openerp.web.f
             this.$element.find('.oe_field_translate').toggle(!!this.view.datarecord.id);
         }
         if (!this.disable_utility_classes) {
-            this.$element.toggleClass('disabled', this.readonly);
+            this.$element.toggleClass('disabled', this.get("effective_readonly"));
             this.$element.toggleClass('required', this.required);
             if (show_invalid) {
                 this.$element.toggleClass('invalid', !this.is_valid());
@@ -1474,25 +1542,66 @@ openerp.web.form.Field = openerp.web.form.Widget.extend(/** @lends openerp.web.f
     }
 });
 
-openerp.web.form.FieldChar = openerp.web.form.Field.extend({
-    form_template: 'FieldChar',
+/**
+ * A mixin to apply on any field that has to completely re-render when its readonly state
+ * switch.
+ */
+openerp.web.form.ReinitializeFieldMixin =  {
+    /**
+     * Default implementation of start(), use it or call explicitly initialize_field().
+     */
+    start: function() {
+        this._super();
+        this.initialize_field();
+    },
+    initialize_field: function() {
+        this.on("change:effective_readonly", this, function() {
+            this.destroy_content();
+            this.renderElement();
+            this.initialize_content();
+            this.render_value();
+        });
+        this.initialize_content();
+        this.render_value();
+    },
+    /**
+     * Called to destroy anything that could have been created previously, called before a
+     * re-initialization.
+     */
+    destroy_content: function() {},
+    /**
+     * Called to initialize the content.
+     */
+    initialize_content: function() {},
+    /**
+     * Called to render the value. Should also be explicitly called at the end of a set_value().
+     */
+    render_value: function() {},
+};
+
+openerp.web.form.FieldChar = openerp.web.form.AbstractField.extend(_.extend({}, openerp.web.form.ReinitializeFieldMixin, {
+    template: 'FieldChar',
     init: function (view, node) {
         this._super(view, node);
         this.password = this.node.attrs.password === 'True' || this.node.attrs.password === '1';
     },
-    start: function() {
-        this._super.apply(this, arguments);
+    initialize_content: function() {
         this.$element.find('input').change(this.on_ui_change);
     },
     set_value: function(value) {
         this._super.apply(this, arguments);
-        var show_value = openerp.web.format_value(value, this, '');
-        this.$element.find('input').val(show_value);
-        return show_value;
+        this.render_value();
     },
-    update_dom: function() {
-        this._super.apply(this, arguments);
-        this.$element.find('input').prop('readonly', this.readonly);
+    render_value: function() {
+        var show_value = openerp.web.format_value(this.value, this, '');
+        if (!this.get("effective_readonly")) {
+            this.$element.find('input').val(show_value);
+        } else {
+            if (this.password) {
+                show_value = new Array(show_value.length + 1).join('*');
+            }
+            this.$element.text(show_value);
+        }
     },
     set_value_from_ui: function() {
         this.value = openerp.web.parse_value(this.$element.find('input').val(), this);
@@ -1500,31 +1609,39 @@ openerp.web.form.FieldChar = openerp.web.form.Field.extend({
     },
     validate: function() {
         this.invalid = false;
-        try {
-            var value = openerp.web.parse_value(this.$element.find('input').val(), this, '');
-            this.invalid = this.required && value === '';
-        } catch(e) {
-            this.invalid = true;
+        if (!this.get("effective_readonly")) {
+            try {
+                var value = openerp.web.parse_value(this.$element.find('input').val(), this, '');
+                this.invalid = this.required && value === '';
+            } catch(e) {
+                this.invalid = true;
+            }
         }
     },
     focus: function($element) {
         this._super($element || this.$element.find('input:first'));
     }
-});
+}));
 
 openerp.web.form.FieldID = openerp.web.form.FieldChar.extend({
-    update_dom: function() {
-        this._super.apply(this, arguments);
-        this.$element.find('input').prop('readonly', true);
-    }
+    
 });
 
 openerp.web.form.FieldEmail = openerp.web.form.FieldChar.extend({
-    form_template: 'FieldEmail',
-    start: function() {
-        this._super.apply(this, arguments);
+    template: 'FieldEmail',
+    initialize_content: function() {
+        this._super();
         this.$element.find('button').click(this.on_button_clicked);
     },
+    render_value: function() {
+        if (!this.get("effective_readonly")) {
+            this._super();
+        } else {
+            this.$element.find('a')
+                    .attr('href', 'mailto:' + this.value)
+                    .text(this.value);
+        }
+    },
     on_button_clicked: function() {
         if (!this.value || !this.is_valid()) {
             this.do_warn("E-mail error", "Can't send email to invalid e-mail address");
@@ -1535,11 +1652,23 @@ openerp.web.form.FieldEmail = openerp.web.form.FieldChar.extend({
 });
 
 openerp.web.form.FieldUrl = openerp.web.form.FieldChar.extend({
-    form_template: 'FieldUrl',
-    start: function() {
-        this._super.apply(this, arguments);
+    template: 'FieldUrl',
+    initialize_content: function() {
+        this._super();
         this.$element.find('button').click(this.on_button_clicked);
     },
+    render_value: function() {
+        if (!this.get("effective_readonly")) {
+            this._super();
+        } else {
+            var tmp = this.value;
+            var s = /(\w+):(.+)/.exec(tmp);
+            if (!s) {
+                tmp = "http://" + this.value;
+            }
+            this.$element.find('a').attr('href', tmp).text(tmp);
+        }
+    },
     on_button_clicked: function() {
         if (!this.value) {
             this.do_warn("Resource error", "This resource is empty");
@@ -1552,17 +1681,13 @@ openerp.web.form.FieldUrl = openerp.web.form.FieldChar.extend({
 openerp.web.form.FieldFloat = openerp.web.form.FieldChar.extend({
     init: function (view, node) {
         this._super(view, node);
-        if (node.attrs.digits) {
-            this.parse_digits(node.attrs.digits);
+        this.value = 0;
+        if (this.node.attrs.digits) {
+            this.digits = py.eval(this.node.attrs.digits).toJSON();
         } else {
-            this.digits = view.fields_view.fields[node.attrs.name].digits;
+            this.digits = this.field.digits;
         }
     },
-    parse_digits: function (digits_attr) {
-        // could use a Python parser instead.
-        var match = /^\s*[\(\[](\d+),\s*(\d+)/.exec(digits_attr);
-        return [parseInt(match[1], 10), parseInt(match[2], 10)];
-    },
     set_value: function(value) {
         if (value === false || value === undefined) {
             // As in GTK client, floats default to 0
@@ -1593,7 +1718,7 @@ openerp.web.DateTimeWidget = openerp.web.OldWidget.extend({
             showButtonPanel: true
         });
         this.$element.find('img.oe_datepicker_trigger').click(function() {
-            if (!self.readonly && !self.picker('widget').is(':visible')) {
+            if (!self.get("effective_readonly") && !self.picker('widget').is(':visible')) {
                 self.picker('setDate', self.value ? openerp.web.auto_str_to_date(self.value) : new Date());
                 self.$input_picker.show();
                 self.picker('show');
@@ -1657,36 +1782,49 @@ openerp.web.DateWidget = openerp.web.DateTimeWidget.extend({
     type_of_date: "date"
 });
 
-openerp.web.form.FieldDatetime = openerp.web.form.Field.extend({
-    form_template: "EmptyComponent",
+openerp.web.form.FieldDatetime = openerp.web.form.AbstractField.extend(_.extend({}, openerp.web.form.ReinitializeFieldMixin, {
+    template: "EmptyComponent",
     build_widget: function() {
         return new openerp.web.DateTimeWidget(this);
     },
-    start: function() {
-        var self = this;
-        this._super.apply(this, arguments);
-        this.datewidget = this.build_widget();
-        this.datewidget.on_change.add_last(this.on_ui_change);
-        this.datewidget.appendTo(this.$element);
+    destroy_content: function() {
+        if (this.datewidget) {
+            this.datewidget.destroy();
+            this.datewidget = undefined;
+        }
+    },
+    initialize_content: function() {
+        if (!this.get("effective_readonly")) {
+            this.datewidget = this.build_widget();
+            this.datewidget.on_change.add_last(this.on_ui_change);
+            this.datewidget.appendTo(this.$element);
+        }
     },
     set_value: function(value) {
         this._super(value);
-        this.datewidget.set_value(value);
+        this.render_value();
     },
-    get_value: function() {
-        return this.datewidget.get_value();
+    render_value: function() {
+        if (!this.get("effective_readonly")) {
+            this.datewidget.set_value(this.value);
+        } else {
+            this.$element.text(openerp.web.format_value(this.value, this, ''));
+        }
     },
-    update_dom: function() {
-        this._super.apply(this, arguments);
-        this.datewidget.set_readonly(this.readonly);
+    get_value: function() {
+        if (!this.get("effective_readonly")) {
+            return this.datewidget.get_value();
+        } else {
+            return this.value;
+        }
     },
     validate: function() {
-        this.invalid = !this.datewidget.is_valid(this.required);
+        this.invalid = this.get("effective_readonly") || !this.datewidget.is_valid(this.required);
     },
     focus: function($element) {
-        this._super($element || this.datewidget.$input);
+        this._super($element || (this.datewidget && this.datewidget.$input));
     }
-});
+}));
 
 openerp.web.form.FieldDate = openerp.web.form.FieldDatetime.extend({
     build_widget: function() {
@@ -1694,45 +1832,52 @@ openerp.web.form.FieldDate = openerp.web.form.FieldDatetime.extend({
     }
 });
 
-openerp.web.form.FieldText = openerp.web.form.Field.extend({
-    form_template: 'FieldText',
-    start: function() {
-        this._super.apply(this, arguments);
-        this.$element.find('textarea').change(this.on_ui_change);
-        this.resized = false;
+openerp.web.form.FieldText = openerp.web.form.AbstractField.extend(_.extend({}, openerp.web.form.ReinitializeFieldMixin, {
+    template: 'FieldText',
+    initialize_content: function() {
+        this.$textarea = undefined;
+        if (!this.get("effective_readonly")) {
+            this.$textarea = this.$element.find('textarea').change(this.on_ui_change);
+            this.resized = false;
+        }
     },
     set_value: function(value) {
         this._super.apply(this, arguments);
-        var show_value = openerp.web.format_value(value, this, '');
-        this.$element.find('textarea').val(show_value);
-        if (!this.resized && this.view.options.resize_textareas) {
-            this.do_resize(this.view.options.resize_textareas);
-            this.resized = true;
-        }
+        this.render_value();
     },
-    update_dom: function() {
-        this._super.apply(this, arguments);
-        this.$element.find('textarea').prop('readonly', this.readonly);
+    render_value: function() {
+        var show_value = openerp.web.format_value(this.value, this, '');
+        if (!this.get("effective_readonly")) {
+            this.$textarea.val(show_value);
+            if (!this.resized && this.view.options.resize_textareas) {
+                this.do_resize(this.view.options.resize_textareas);
+                this.resized = true;
+            }
+        } else {
+            this.$element.text(show_value);
+        }
     },
     set_value_from_ui: function() {
-        this.value = openerp.web.parse_value(this.$element.find('textarea').val(), this);
+        this.value = openerp.web.parse_value(this.$textarea.val(), this);
         this._super();
     },
     validate: function() {
         this.invalid = false;
-        try {
-            var value = openerp.web.parse_value(this.$element.find('textarea').val(), this, '');
-            this.invalid = this.required && value === '';
-        } catch(e) {
-            this.invalid = true;
+        if (!this.get("effective_readonly")) {
+            try {
+                var value = openerp.web.parse_value(this.$textarea.val(), this, '');
+                this.invalid = this.required && value === '';
+            } catch(e) {
+                this.invalid = true;
+            }
         }
     },
     focus: function($element) {
-        this._super($element || this.$element.find('textarea:first'));
+        this._super($element || this.$textarea);
     },
     do_resize: function(max_height) {
         max_height = parseInt(max_height, 10);
-        var $input = this.$element.find('textarea'),
+        var $input = this.$textarea,
             $div = $('<div style="position: absolute; z-index: 1000; top: 0"/>').width($input.width()),
             new_height;
         $div.text($input.val());
@@ -1753,39 +1898,40 @@ openerp.web.form.FieldText = openerp.web.form.Field.extend({
     reset: function() {
         this.resized = false;
     }
-});
+}));
 
-openerp.web.form.FieldBoolean = openerp.web.form.Field.extend({
-    form_template: 'FieldBoolean',
+openerp.web.form.FieldBoolean = openerp.web.form.AbstractField.extend({
+    template: 'FieldBoolean',
     start: function() {
-        var self = this;
         this._super.apply(this, arguments);
-        this.$element.find('input').click(self.on_ui_change);
+        this.$checkbox = $("input", this.$element);
+        this.$element.click(this.on_ui_change);
+        var check_readonly = function() {
+            this.$checkbox.prop('disabled', this.get("effective_readonly"));
+        };
+        this.on("change:effective_readonly", this, check_readonly);
+        _.bind(check_readonly, this)();
     },
     set_value: function(value) {
         this._super.apply(this, arguments);
-        this.$element.find('input')[0].checked = value;
+        this.$checkbox[0].checked = value;
     },
     set_value_from_ui: function() {
-        this.value = this.$element.find('input').is(':checked');
-        this._super();
-    },
-    update_dom: function() {
+        this.value = this.$checkbox.is(':checked');
         this._super.apply(this, arguments);
-        this.$element.find('input').prop('disabled', this.readonly);
     },
     focus: function($element) {
-        this._super($element || this.$element.find('input:first'));
+        this._super($element || this.$checkbox);
     }
 });
 
-openerp.web.form.FieldProgressBar = openerp.web.form.Field.extend({
-    form_template: 'FieldProgressBar',
+openerp.web.form.FieldProgressBar = openerp.web.form.AbstractField.extend({
+    template: 'FieldProgressBar',
     start: function() {
         this._super.apply(this, arguments);
-        this.$element.find('div').progressbar({
+        this.$element.progressbar({
             value: this.value,
-            disabled: this.readonly
+            disabled: this.get("effective_readonly")
         });
     },
     set_value: function(value) {
@@ -1795,16 +1941,16 @@ openerp.web.form.FieldProgressBar = openerp.web.form.Field.extend({
             show_value = 0;
         }
         var formatted_value = openerp.web.format_value(show_value, { type : 'float' }, '0');
-        this.$element.find('div').progressbar('option', 'value', show_value).find('span').html(formatted_value + '%');
+        this.$element.progressbar('option', 'value', show_value).find('span').html(formatted_value + '%');
     }
 });
 
-openerp.web.form.FieldTextXml = openerp.web.form.Field.extend({
+openerp.web.form.FieldTextXml = openerp.web.form.AbstractField.extend({
 // to replace view editor
 });
 
-openerp.web.form.FieldSelection = openerp.web.form.Field.extend({
-    form_template: 'FieldSelection',
+openerp.web.form.FieldSelection = openerp.web.form.AbstractField.extend(_.extend({}, openerp.web.form.ReinitializeFieldMixin, {
+    template: 'FieldSelection',
     init: function(view, node) {
         var self = this;
         this._super(view, node);
@@ -1816,7 +1962,7 @@ openerp.web.form.FieldSelection = openerp.web.form.Field.extend({
         });
         this.values.unshift([false, '']);
     },
-    start: function() {
+    initialize_content: function() {
         // Flag indicating whether we're in an event chain containing a change
         // event on the select, in order to know what to do on keyup[RETURN]:
         // * If the user presses [RETURN] as part of changing the value of a
@@ -1829,7 +1975,6 @@ openerp.web.form.FieldSelection = openerp.web.form.Field.extend({
         //   changing the selected value), takes the action as validating the
         //   row
         var ischanging = false;
-        this._super.apply(this, arguments);
         this.$element.find('select')
             .change(this.on_ui_change)
             .change(function () { ischanging = true; })
@@ -1844,28 +1989,38 @@ openerp.web.form.FieldSelection = openerp.web.form.Field.extend({
         value = value === null ? false : value;
         value = value instanceof Array ? value[0] : value;
         this._super(value);
-        var index = 0;
-        for (var i = 0, ii = this.values.length; i < ii; i++) {
-            if (this.values[i][0] === value) index = i;
+        this.render_value();
+    },
+    render_value: function() {
+        if (!this.get("effective_readonly")) {
+            var index = 0;
+            for (var i = 0, ii = this.values.length; i < ii; i++) {
+                if (this.values[i][0] === this.value) index = i;
+            }
+            this.$element.find('select')[0].selectedIndex = index;
+        } else {
+            var self = this;
+            var option = _(this.values)
+                .detect(function (record) { return record[0] === self.value; }); 
+            this.$element.text(option ? option[1] : this.values[0][1]);
         }
-        this.$element.find('select')[0].selectedIndex = index;
     },
     set_value_from_ui: function() {
         this.value = this.values[this.$element.find('select')[0].selectedIndex][0];
         this._super();
     },
-    update_dom: function() {
-        this._super.apply(this, arguments);
-        this.$element.find('select').prop('disabled', this.readonly);
-    },
     validate: function() {
+        if (this.get("effective_readonly")) {
+            this.invalid = false;
+            return;
+        }
         var value = this.values[this.$element.find('select')[0].selectedIndex];
         this.invalid = !(value && !(this.required && value[0] === false));
     },
     focus: function($element) {
         this._super($element || this.$element.find('select:first'));
     }
-});
+}));
 
 // jquery autocomplete tweak to allow html
 (function() {
@@ -1909,8 +2064,8 @@ openerp.web.form.dialog = function(content, options) {
     return dialog.$element;
 };
 
-openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({
-    form_template: 'FieldMany2One',
+openerp.web.form.FieldMany2One = openerp.web.form.AbstractField.extend(_.extend({}, openerp.web.form.ReinitializeFieldMixin, {
+    template: "FieldMany2One",
     init: function(view, node) {
         this._super(view, node);
         this.limit = 7;
@@ -1919,8 +2074,12 @@ openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({
         this.last_search = [];
         this.tmp_value = undefined;
     },
-    start: function() {
-        this._super();
+    initialize_content: function() {
+        if (!this.get("effective_readonly"))
+            this.render_editable();
+        this.render_value();
+    },
+    render_editable: function() {
         var self = this;
         this.$input = this.$element.find("input");
         this.$drop_down = this.$element.find(".oe-m2o-drop-down-button");
@@ -1938,9 +2097,13 @@ openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({
                 $cmenu.append(QWeb.render("FieldMany2One.context_menu", {widget: self}));
                 var bindings = {};
                 bindings[self.cm_id + "_search"] = function() {
+                    if (self.get("effective_readonly"))
+                        return;
                     self._search_create_popup("search");
                 };
                 bindings[self.cm_id + "_create"] = function() {
+                    if (self.get("effective_readonly"))
+                        return;
                     self._search_create_popup("form");
                 };
                 bindings[self.cm_id + "_open"] = function() {
@@ -1973,7 +2136,7 @@ openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({
                         } else {
                             $("#" + self.cm_id + " .oe_m2o_menu_item_mandatory").addClass("oe-m2o-disabled-cm");
                         }
-                        if (!self.readonly) {
+                        if (!self.get("effective_readonly")) {
                             $("#" + self.cm_id + " .oe_m2o_menu_item_noreadonly").removeClass("oe-m2o-disabled-cm");
                         } else {
                             $("#" + self.cm_id + " .oe_m2o_menu_item_noreadonly").addClass("oe-m2o-disabled-cm");
@@ -1996,7 +2159,7 @@ openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({
             }
         });
         this.$drop_down.click(function() {
-            if (self.readonly)
+            if (self.get("effective_readonly"))
                 return;
             if (self.$input.autocomplete("widget").is(":visible")) {
                 self.$input.autocomplete("close");
@@ -2045,7 +2208,7 @@ openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({
             minLength: 0,
             delay: 0
         });
-        this.$input.autocomplete("widget").addClass("openerp");
+        this.$input.autocomplete("widget").addClass("openerp openerp2");
         // used to correct a bug when selecting an element by pushing 'enter' in an editable list
         this.$input.keyup(function(e) {
             if (e.which === 13) {
@@ -2151,7 +2314,28 @@ openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({
     },
     _change_int_ext_value: function(value) {
         this._change_int_value(value);
-        this.$input.val(this.value ? this.value[1] : "");
+        this.render_value();
+    },
+    render_value: function() {
+        var self = this;
+        if (!this.get("effective_readonly")) {
+            this.$input.val(this.value ? this.value[1] : "");
+        } else {
+            self.$element.find('a')
+                 .unbind('click')
+                 .text(this.value ? this.value[1] : '')
+                 .click(function () {
+                    self.do_action({
+                        type: 'ir.actions.act_window',
+                        res_model: self.field.relation,
+                        res_id: self.value[0],
+                        context: self.build_context(),
+                        views: [[false, 'page'], [false, 'form']],
+                        target: 'current'
+                    });
+                    return false;
+                 });
+        }
     },
     _change_int_value: function(value) {
         this.value = value;
@@ -2227,12 +2411,8 @@ openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({
     },
     focus: function ($element) {
         this._super($element || this.$input);
-    },
-    update_dom: function() {
-        this._super.apply(this, arguments);
-        this.$input.prop('readonly', this.readonly);
     }
-});
+}));
 
 /*
 # Values: (0, 0,  { fields })    create
@@ -2279,8 +2459,7 @@ var commands = {
         return [6, false, ids];
     }
 };
-openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({
-    form_template: 'FieldOne2Many',
+openerp.web.form.FieldOne2Many = openerp.web.form.AbstractField.extend({
     multi_selection: false,
     init: function(view, node) {
         this._super(view, node);
@@ -2308,6 +2487,16 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({
         this.is_setted.then(function() {
             self.load_views();
         });
+        this.is_loaded.then(function() {
+            self.on("change:effective_readonly", self, function() {
+                self.is_loaded = self.is_loaded.pipe(function() {
+                    self.viewmanager.destroy();
+                    return $.when(self.load_views()).then(function() {
+                        self.reload_current_view();
+                    });
+                });
+            });
+        });
     },
     trigger_on_change: function() {
         var tmp = this.doing_on_change;
@@ -2315,9 +2504,6 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({
         this.on_ui_change();
         this.doing_on_change = tmp;
     },
-    is_readonly: function() {
-        return this.readonly || this.force_readonly;
-    },
     load_views: function() {
         var self = this;
         
@@ -2335,13 +2521,13 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({
             }
             if(view.view_type === "list") {
                 view.options.selectable = self.multi_selection;
-                if (self.is_readonly()) {
+                if (self.get("effective_readonly")) {
                     view.options.addable = null;
                     view.options.deletable = null;
                     view.options.isClarkGable = false;
                 }
             } else if (view.view_type === "form") {
-                if (self.is_readonly()) {
+                if (self.get("effective_readonly")) {
                     view.view_type = 'page';
                 }
                 view.options.not_interactible_on_create = true;
@@ -2366,10 +2552,10 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({
         this.viewmanager.on_controller_inited.add_last(function(view_type, controller) {
             if (view_type == "list") {
                 controller.o2m = self;
-                if (self.is_readonly())
+                if (self.get("effective_readonly"))
                     controller.set_editable(false);
             } else if (view_type == "form" || view_type == 'page') {
-                if (view_type == 'page' || self.is_readonly()) {
+                if (view_type == 'page' || self.get("effective_readonly")) {
                     $(".oe_form_buttons", controller.$element).children().remove();
                 }
                 controller.on_record_loaded.add_last(function() {
@@ -2546,21 +2732,6 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({
     is_dirty: function() {
         this.save_any_view();
         return this._super();
-    },
-    update_dom: function() {
-        this._super.apply(this, arguments);
-        var self = this;
-        if (this.previous_readonly !== this.readonly) {
-            this.previous_readonly = this.readonly;
-            if (this.viewmanager) {
-                this.is_loaded = this.is_loaded.pipe(function() {
-                    self.viewmanager.destroy();
-                    return $.when(self.load_views()).then(function() {
-                        self.reload_current_view();
-                    });
-                });
-            }
-        }
     }
 });
 
@@ -2620,7 +2791,7 @@ openerp.web.form.One2ManyListView = openerp.web.ListView.extend({
                 return self.o2m.dataset.read_ids.apply(self.o2m.dataset, arguments);
             },
             form_view_options: {'not_interactible_on_create':true},
-            readonly: self.o2m.is_readonly()
+            readonly: self.o2m.get("effective_readonly")
         });
         pop.on_write.add(function(id, data) {
             self.o2m.dataset.write(id, data, {}, function(r) {
@@ -2653,12 +2824,13 @@ openerp.web.form.One2ManyFormView = openerp.web.FormView.extend({
     }
 });
 
-openerp.web.form.FieldMany2Many = openerp.web.form.Field.extend({
-    form_template: 'FieldMany2Many',
+/*
+ * TODO niv: clean those deferred stuff, it could be better
+ */
+openerp.web.form.FieldMany2Many = openerp.web.form.AbstractField.extend({
     multi_selection: false,
     init: function(view, node) {
         this._super(view, node);
-        this.list_id = _.uniqueId("many2many");
         this.is_loaded = $.Deferred();
         this.initial_is_loaded = this.is_loaded;
         this.is_setted = $.Deferred();
@@ -2677,6 +2849,16 @@ openerp.web.form.FieldMany2Many = openerp.web.form.Field.extend({
         this.is_setted.then(function() {
             self.load_view();
         });
+        this.is_loaded.then(function() {
+            self.on("change:effective_readonly", self, function() {
+                self.is_loaded = self.is_loaded.pipe(function() {
+                    self.list_view.destroy();
+                    return $.when(self.load_view()).then(function() {
+                        self.reload_content();
+                    });
+                });
+            });
+        })
     },
     set_value: function(value) {
         value = value || [];
@@ -2695,16 +2877,13 @@ openerp.web.form.FieldMany2Many = openerp.web.form.Field.extend({
     validate: function() {
         this.invalid = false;
     },
-    is_readonly: function() {
-        return this.readonly || this.force_readonly;
-    },
     load_view: function() {
         var self = this;
         this.list_view = new openerp.web.form.Many2ManyListView(this, this.dataset, false, {
-                    'addable': self.is_readonly() ? null : _t("Add"),
-                    'deletable': self.is_readonly() ? false : true,
+                    'addable': self.get("effective_readonly") ? null : _t("Add"),
+                    'deletable': self.get("effective_readonly") ? false : true,
                     'selectable': self.multi_selection,
-                    'isClarkGable': self.is_readonly() ? false : true
+                    'isClarkGable': self.get("effective_readonly") ? false : true
             });
         var embedded = (this.field.views || {}).tree;
         if (embedded) {
@@ -2717,7 +2896,7 @@ openerp.web.form.FieldMany2Many = openerp.web.form.Field.extend({
             loaded.resolve();
         });
         $.async_when().then(function () {
-            self.list_view.appendTo($("#" + self.list_id));
+            self.list_view.appendTo(self.$element);
         });
         return loaded;
     },
@@ -2727,21 +2906,6 @@ openerp.web.form.FieldMany2Many = openerp.web.form.Field.extend({
             return self.list_view.reload_content();
         });
     },
-    update_dom: function() {
-        this._super.apply(this, arguments);
-        var self = this;
-        if (this.previous_readonly !== this.readonly) {
-            this.previous_readonly = this.readonly;
-            if (this.list_view) {
-                this.is_loaded = this.is_loaded.pipe(function() {
-                    self.list_view.destroy();
-                    return $.when(self.load_view()).then(function() {
-                        self.reload_content();
-                    });
-                });
-            }
-        }
-    }
 });
 
 openerp.web.form.Many2ManyDataSet = openerp.web.DataSetStatic.extend({
@@ -2768,9 +2932,9 @@ openerp.web.form.Many2ManyListView = openerp.web.ListView.extend(/** @lends open
         );
         var self = this;
         pop.on_select_elements.add(function(element_ids) {
-            _.each(element_ids, function(element_id) {
-                if(! _.detect(self.dataset.ids, function(x) {return x == element_id;})) {
-                    self.dataset.set_ids([].concat(self.dataset.ids, [element_id]));
+            _.each(element_ids, function(one_id) {
+                if(! _.detect(self.dataset.ids, function(x) {return x == one_id;})) {
+                    self.dataset.set_ids([].concat(self.dataset.ids, [one_id]));
                     self.m2m_field.on_ui_change();
                     self.reload_content();
                 }
@@ -2782,7 +2946,7 @@ openerp.web.form.Many2ManyListView = openerp.web.ListView.extend(/** @lends open
         var pop = new openerp.web.form.FormOpenPopup(this);
         pop.show_element(this.dataset.model, id, this.m2m_field.build_context(), {
             title: _t("Open: ") + this.name,
-            readonly: this.getParent().is_readonly()
+            readonly: this.getParent().get("effective_readonly")
         });
         pop.on_write_completed.add_last(function() {
             self.reload_content();
@@ -2819,7 +2983,7 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope
         }, read_function: null});
         this.initial_ids = this.options.initial_ids;
         this.created_elements = [];
-        this.render_element();
+        this.renderElement();
         openerp.web.form.dialog(this.$element, {
             close: function() {
                 self.check_exit();
@@ -2863,6 +3027,10 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope
             this.new_object();
         }
     },
+    stop: function () {
+        this.$element.dialog('close');
+        this._super();
+    },
     setup_search_view: function(search_defaults) {
         var self = this;
         if (this.searchview) {
@@ -2886,7 +3054,7 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope
                         'selectable': !self.options.disable_multiple_selection
                     }, self.options.list_view_options || {}));
             self.view_list.popup = self;
-            self.view_list.appendTo($("#" + self.element_id + "_view_list")).pipe(function() {
+            self.view_list.appendTo($(".oe-select-create-popup-view-list", self.$element)).pipe(function() {
                 self.view_list.do_show();
             }).pipe(function() {
                 self.searchview.do_search();
@@ -2908,7 +3076,7 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope
                 });
             });
         });
-        this.searchview.appendTo($("#" + this.element_id + "_search"));
+        this.searchview.appendTo($(".oe-select-create-popup-view-list", self.$element));
     },
     do_search: function(domains, contexts, groupbys) {
         var self = this;
@@ -2957,7 +3125,7 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope
         if (this.options.alternative_form_view) {
             this.view_form.set_embedded_view(this.options.alternative_form_view);
         }
-        this.view_form.appendTo(this.$element.find("#" + this.element_id + "_view_form"));
+        this.view_form.appendTo(this.$element.find(".oe-select-create-popup-view-form"));
         this.view_form.on_loaded.add_last(function() {
             var $buttons = self.view_form.$element.find(".oe_form_buttons");
             $buttons.html(QWeb.render("SelectCreatePopup.form.buttons", {widget:self}));
@@ -3028,7 +3196,7 @@ openerp.web.form.FormOpenPopup = openerp.web.OldWidget.extend(/** @lends openerp
         this.row_id = row_id;
         this.context = context || {};
         this.options = _.defaults(options || {}, {"auto_write": true});
-        this.render_element();
+        this.renderElement();
         openerp.web.dialog(this.$element, {
             title: options.title || '',
             modal: true,
@@ -3068,7 +3236,7 @@ openerp.web.form.FormOpenPopup = openerp.web.OldWidget.extend(/** @lends openerp
         if (this.options.alternative_form_view) {
             this.view_form.set_embedded_view(this.options.alternative_form_view);
         }
-        this.view_form.appendTo(this.$element.find("#" + this.element_id + "_view_form"));
+        this.view_form.appendTo(this.$element.find(".oe-form-open-popup-form-view"));
         this.view_form.on_loaded.add_last(function() {
             var $buttons = self.view_form.$element.find(".oe_form_buttons");
             $buttons.html(QWeb.render("FormOpenPopup.form.buttons"));
@@ -3102,8 +3270,8 @@ openerp.web.form.FormOpenDataset = openerp.web.ProxyDataSet.extend({
     }
 });
 
-openerp.web.form.FieldReference = openerp.web.form.Field.extend({
-    form_template: 'FieldReference',
+openerp.web.form.FieldReference = openerp.web.form.AbstractField.extend(_.extend({}, openerp.web.form.ReinitializeFieldMixin, {
+    template: 'FieldReference',
     init: function(view, node) {
         this._super(view, node);
         this.fields_view = {
@@ -3120,22 +3288,10 @@ openerp.web.form.FieldReference = openerp.web.form.Field.extend({
         this.get_selected_ids = view.get_selected_ids;
         this.do_onchange = this.on_form_changed = this.do_notify_change = this.on_nop;
         this.dataset = this.view.dataset;
-        this.widgets_counter = 0;
         this.view_id = 'reference_' + _.uniqueId();
-        this.widgets = {};
         this.fields = {};
         this.fields_order = [];
-        this.selection = new openerp.web.form.FieldSelection(this, { attrs: {
-            name: 'selection',
-            widget: 'selection'
-        }});
         this.reference_ready = true;
-        this.selection.on_value_changed.add_last(this.on_selection_changed);
-        this.m2o = new openerp.web.form.FieldMany2One(this, { attrs: {
-            name: 'm2o',
-            widget: 'many2one'
-        }});
-        this.m2o.on_ui_change.add_last(this.on_ui_change);
     },
     on_nop: function() {
     },
@@ -3147,13 +3303,36 @@ openerp.web.form.FieldReference = openerp.web.form.Field.extend({
             this.m2o.$element.toggle(sel !== false);
         }
     },
-    start: function() {
-        this._super();
-        this.selection.$element = $(".oe_form_view_reference_selection", this.$element);
-        this.selection.render_element();
-        this.selection.start();
+    destroy_content: function() {
+        if (this.selection) {
+            this.selection.destroy();
+            this.selection = undefined;
+        }
+        if (this.m2o) {
+            this.m2o.destroy();
+            this.m2o.undefined;
+        }
+    },
+    initialize_content: function() {
+        if (!this.get("effective_readonly")) {
+            this.selection = new openerp.web.form.FieldSelection(this, { attrs: {
+                name: 'selection',
+                widget: 'selection'
+            }});
+            this.selection.on_value_changed.add_last(this.on_selection_changed);
+            
+            this.selection.$element = $(".oe_form_view_reference_selection", this.$element);
+            this.selection.renderElement();
+            this.selection.start();
+        }
+        this.m2o = new openerp.web.form.FieldMany2One(this, { attrs: {
+            name: 'm2o',
+            widget: 'many2one'
+        }});
+        this.m2o.set({"readonly": this.get("effective_readonly")});
+        this.m2o.on_ui_change.add_last(this.on_ui_change);
         this.m2o.$element = $(".oe_form_view_reference_m2o", this.$element);
-        this.m2o.render_element();
+        this.m2o.renderElement();
         this.m2o.start();
     },
     is_valid: function() {
@@ -3164,14 +3343,19 @@ openerp.web.form.FieldReference = openerp.web.form.Field.extend({
     },
     set_value: function(value) {
         this._super(value);
+        this.render_value();
+    },
+    render_value: function() {
         this.reference_ready = false;
         var vals = [], sel_val, m2o_val;
-        if (typeof(value) === 'string') {
-            vals = value.split(',');
+        if (typeof(this.value) === 'string') {
+            vals = this.value.split(',');
         }
         sel_val = vals[0] || false;
         m2o_val = vals[1] ? parseInt(vals[1], 10) : false;
-        this.selection.set_value(sel_val);
+        if (!this.get("effective_readonly")) {
+            this.selection.set_value(sel_val);
+        }
         this.m2o.field.relation = sel_val;
         this.m2o.set_value(m2o_val);
         this.m2o.$element.toggle(sel_val !== false);
@@ -3186,16 +3370,15 @@ openerp.web.form.FieldReference = openerp.web.form.Field.extend({
             return false;
         }
     }
-});
+}));
 
-openerp.web.form.FieldBinary = openerp.web.form.Field.extend({
+openerp.web.form.FieldBinary = openerp.web.form.AbstractField.extend(_.extend({}, openerp.web.form.ReinitializeFieldMixin, {
     init: function(view, node) {
         this._super(view, node);
         this.iframe = this.element_id + '_iframe';
         this.binary_value = false;
     },
-    start: function() {
-        this._super.apply(this, arguments);
+    initialize_content: function() {
         this.$element.find('input.oe-binary-file').change(this.on_file_change);
         this.$element.find('button.oe-binary-file-save').click(this.on_save_as);
         this.$element.find('.oe-binary-file-clear').click(this.on_clear);
@@ -3259,24 +3442,42 @@ openerp.web.form.FieldBinary = openerp.web.form.Field.extend({
         }
         return false;
     }
-});
+}));
 
 openerp.web.form.FieldBinaryFile = openerp.web.form.FieldBinary.extend({
-    form_template: 'FieldBinaryFile',
-    update_dom: function() {
-        this._super.apply(this, arguments);
-        this.$element.find('.oe-binary-file-set, .oe-binary-file-clear').toggle(!this.readonly);
-        this.$element.find('input[type=text]').prop('readonly', this.readonly);
+    template: 'FieldBinaryFile',
+    initialize_content: function() {
+        this._super();
+        if (this.get("effective_readonly")) {
+            var self = this;
+            this.$element.find('a').click(function() {
+                if (self.value) {
+                    self.on_save_as();
+                }
+                return false;
+            });
+        }
     },
     set_value: function(value) {
         this._super.apply(this, arguments);
-        var show_value;
-        if (this.node.attrs.filename) {
-            show_value = this.view.datarecord[this.node.attrs.filename] || '';
+        this.render_value();
+    },
+    render_value: function() {
+        if (!this.get("effective_readonly")) {
+            var show_value;
+            if (this.node.attrs.filename) {
+                show_value = this.view.datarecord[this.node.attrs.filename] || '';
+            } else {
+                show_value = (this.value != null && this.value !== false) ? this.value : '';
+            }
+            this.$element.find('input').eq(0).val(show_value);
         } else {
-            show_value = (value != null && value !== false) ? value : '';
+            this.$element.find('a').show(!!this.value);
+            if (this.value) {
+                var show_value = _t("Download") + " " + (this.view.datarecord[this.node.attrs.filename] || '');
+                this.$element.find('a').text(show_value);
+            }
         }
-        this.$element.find('input').eq(0).val(show_value);
     },
     on_file_uploaded_and_valid: function(size, name, content_type, file_base64) {
         this.value = file_base64;
@@ -3300,42 +3501,49 @@ openerp.web.form.FieldBinaryFile = openerp.web.form.FieldBinary.extend({
 });
 
 openerp.web.form.FieldBinaryImage = openerp.web.form.FieldBinary.extend({
-    form_template: 'FieldBinaryImage',
-    start: function() {
-        this._super.apply(this, arguments);
-        this.$image = this.$element.find('img.oe-binary-image');
-    },
-    update_dom: function() {
-        this._super.apply(this, arguments);
-        this.$element.find('.oe-binary').toggle(!this.readonly);
+    template: 'FieldBinaryImage',
+    initialize_content: function() {
+        this._super();
+        this.$placeholder = $(".oe_form_field-binary-image-placeholder", this.$element);
+        if (!this.get("effective_readonly"))
+            this.$element.find('.oe-binary').show();
+        else
+            this.$element.find('.oe-binary').hide();
     },
     set_value: function(value) {
         this._super.apply(this, arguments);
-        this.set_image_maxwidth();
-        var url = '/web/binary/image?session_id=' + this.session.session_id + '&model=' +
-            this.view.dataset.model +'&id=' + (this.view.datarecord.id || '') + '&field=' + this.name + '&t=' + (new Date().getTime());
-        this.$image.attr('src', url);
-    },
-    set_image_maxwidth: function() {
-        this.$image.css('max-width', this.$element.width());
+        this.render_value();
+    },
+    render_value: function() {
+        var url;
+        if (this.value && this.value.substr(0, 10).indexOf(' ') == -1) {
+            url = 'data:image/png;base64,' + this.value;
+        } else if (this.value) {
+            url = '/web/binary/image?session_id=' + this.session.session_id + '&model=' +
+                this.view.dataset.model +'&id=' + (this.view.datarecord.id || '') + '&field=' + this.name + '&t=' + (new Date().getTime());
+        } else {
+            url = "/web/static/src/img/placeholder.png";
+        }
+        var rendered = QWeb.render("FieldBinaryImage-img", {widget: this, url: url});;
+        this.$placeholder.html(rendered);
     },
     on_file_change: function() {
-        this.set_image_maxwidth();
+        this.render_value();
         this._super.apply(this, arguments);
     },
     on_file_uploaded_and_valid: function(size, name, content_type, file_base64) {
         this.value = file_base64;
         this.binary_value = true;
-        this.$image.attr('src', 'data:' + (content_type || 'image/png') + ';base64,' + file_base64);
+        this.render_value();
     },
     on_clear: function() {
         this._super.apply(this, arguments);
-        this.$image.attr('src', '/web/static/src/img/placeholder.png');
+        this.render_value();
     }
 });
 
-openerp.web.form.FieldStatus = openerp.web.form.Field.extend({
-    form_template: "EmptyComponent",
+openerp.web.form.FieldStatus = openerp.web.form.AbstractField.extend({
+    template: "EmptyComponent",
     start: function() {
         this._super();
         this.selected_value = null;
@@ -3399,44 +3607,10 @@ openerp.web.form.FieldStatus = openerp.web.form.Field.extend({
     }
 });
 
-openerp.web.form.WidgetHtml = openerp.web.form.Widget.extend({
-    render_element: function () {
-        var $root = $('<div class="oe_form_html_view">');
-        this.render_children(this, $root);
-        var rendered = $root.html();
-        this.$element.html(rendered);
-    },
-    render_children: function (object, $into) {
-        var self = this,
-            fields = this.view.fields_view.fields;
-        _(object.children).each(function (child) {
-            if (typeof child === 'string') {
-                $into.text(child);
-            } else if (child.tag === 'field') {
-               var widget = instanciate_widget(self.view.registry.get_object('frame'),
-                        self.view, {tag: 'ueule', attrs: {}, children: [child] });
-                widget.appendTo($into);
-            } else {
-                var $child = $(document.createElement(child.tag))
-                        .attr(child.attrs)
-                        .appendTo($into);
-                self.render_children(child, $child);
-            }
-        });
-    }
-});
-
-
 /**
  * Registry of form widgets, called by :js:`openerp.web.FormView`
  */
 openerp.web.form.widgets = new openerp.web.Registry({
-    'frame' : 'openerp.web.form.WidgetFrame',
-    'group' : 'openerp.web.form.WidgetGroup',
-    'notebook' : 'openerp.web.form.WidgetNotebook',
-    'notebookpage' : 'openerp.web.form.WidgetNotebookPage',
-    'separator' : 'openerp.web.form.WidgetSeparator',
-    'label' : 'openerp.web.form.WidgetLabel',
     'button' : 'openerp.web.form.WidgetButton',
     'char' : 'openerp.web.form.FieldChar',
     'id' : 'openerp.web.form.FieldID',
@@ -3458,19 +3632,9 @@ openerp.web.form.widgets = new openerp.web.Registry({
     'progressbar': 'openerp.web.form.FieldProgressBar',
     'image': 'openerp.web.form.FieldBinaryImage',
     'binary': 'openerp.web.form.FieldBinaryFile',
-    'statusbar': 'openerp.web.form.FieldStatus',
-    'html': 'openerp.web.form.WidgetHtml'
+    'statusbar': 'openerp.web.form.FieldStatus'
 });
 
-var instanciate_widget = function(claz, view, node, o1, o2) {
-       var widget = new (claz)(view, node, o1, o2);
-       widget.element_class = (['formview', view.view_id, widget.element_name,
-       view.widgets_counter++].join("_")).replace(/[^\r\n\f0-9A-Za-z_-]/g, "_");
-    view.widgets[widget.element_class] = widget;
-    return widget;
-}
-
-
 };
 
 // vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: