[MERGE] forward port of branch 8.0 up to 591e329
authorChristophe Simonis <chs@odoo.com>
Tue, 2 Dec 2014 15:18:08 +0000 (16:18 +0100)
committerChristophe Simonis <chs@odoo.com>
Tue, 2 Dec 2014 15:18:08 +0000 (16:18 +0100)
67 files changed:
1  2 
addons/account/account_bank_statement.py
addons/account/account_invoice.py
addons/analytic_user_function/analytic_user_function_view.xml
addons/calendar/calendar.py
addons/crm/crm.py
addons/crm/crm_lead_view.xml
addons/crm/report/crm_lead_report_view.xml
addons/crm/report/crm_opportunity_report_view.xml
addons/im_chat/im_chat.py
addons/mass_mailing/models/mass_mailing.py
addons/mrp/mrp_view.xml
addons/mrp/security/ir.model.access.csv
addons/payment/models/payment_acquirer.py
addons/payment/views/payment_acquirer.xml
addons/point_of_sale/point_of_sale_view.xml
addons/point_of_sale/static/src/js/models.js
addons/point_of_sale/static/src/js/screens.js
addons/point_of_sale/static/src/js/widgets.js
addons/product/pricelist_view.xml
addons/product/product.py
addons/product/product_view.xml
addons/project_timesheet/project_timesheet.py
addons/purchase/purchase.py
addons/purchase/purchase_view.xml
addons/stock/product.py
addons/stock/stock_demo.yml
addons/stock/stock_view.xml
addons/web/controllers/main.py
addons/web/static/src/css/base.css
addons/web/static/src/css/base.sass
addons/web/static/src/js/chrome.js
addons/web/static/src/js/search.js
addons/web/static/src/js/view_form.js
addons/web/static/src/js/view_list.js
addons/web/views/webclient_templates.xml
addons/web_calendar/static/src/js/web_calendar.js
addons/web_graph/static/src/js/graph_widget.js
addons/website/models/res_config.py
addons/website/models/website.py
addons/website/static/src/css/website.css
addons/website/static/src/css/website.sass
addons/website/static/src/js/website.editor.js
addons/website/views/res_config.xml
addons/website_blog/data/website_blog_data.xml
addons/website_blog/models/website_blog.py
addons/website_blog/security/ir.model.access.csv
addons/website_blog/views/website_blog_views.xml
addons/website_crm/controllers/main.py
addons/website_forum/controllers/main.py
addons/website_forum/models/forum.py
addons/website_forum/static/src/js/website_forum.js
addons/website_forum/views/website_forum.xml
addons/website_mail/views/website_email_designer.xml
addons/website_sale/controllers/main.py
addons/website_sale/models/sale_order.py
doc/conf.py
doc/reference.rst
openerp/addons/base/ir/ir_qweb.py
openerp/addons/base/ir/ir_ui_view.py
openerp/addons/base/res/res_partner.py
openerp/addons/base/res/res_partner_view.xml
openerp/addons/base/res/res_users.py
openerp/models.py
openerp/service/server.py
openerp/tools/convert.py
openerp/tools/misc.py
setup/win32/setup.nsi

@@@ -834,13 -859,13 +858,13 @@@ class account_statement_operation_templ
      _description = "Preset for the lines that can be created in a bank statement reconciliation"
      _columns = {
          'name': fields.char('Button Label', required=True),
-         'account_id': fields.many2one('account.account', 'Account', ondelete='cascade', domain=[('type','not in',('view','closed','consolidation'))]),
+         'account_id': fields.many2one('account.account', 'Account', ondelete='cascade', domain=[('type', 'not in', ('view', 'closed', 'consolidation'))]),
 -        'label': fields.char('Label'),
 -        'amount_type': fields.selection([('fixed', 'Fixed'),('percentage_of_total','Percentage of total amount'),('percentage_of_balance', 'Percentage of open balance')],
 -                                   'Amount type', required=True),
 -        'amount': fields.float('Amount', digits_compute=dp.get_precision('Account'), help="The amount will count as a debit if it is negative, as a credit if it is positive (except if amount type is 'Percentage of open balance').", required=True),
 -        'tax_id': fields.many2one('account.tax', 'Tax', ondelete='restrict', domain=[('type_tax_use', 'in', ['purchase', 'all']), ('parent_id', '=', False)]),
 +        'label': fields.char('Journal Item Label'),
 +        'amount_type': fields.selection([('fixed', 'Fixed'),('percentage_of_total','Percentage of total amount'),('percentage_of_balance', 'Percentage of open balance')], 'Amount type', required=True),
 +        'amount': fields.float('Amount', digits_compute=dp.get_precision('Account'), required=True, help="The amount will count as a debit if it is negative, as a credit if it is positive (except if amount type is 'Percentage of open balance')."),
 +        'tax_id': fields.many2one('account.tax', 'Tax', ondelete='restrict', domain=[('type_tax_use','in',('purchase','all')), ('parent_id','=',False)]),
          'analytic_account_id': fields.many2one('account.analytic.account', 'Analytic Account', ondelete='set null', domain=[('type','!=','view'), ('state','not in',('close','cancelled'))]),
 +        'company_id': fields.many2one('res.company', 'Company', required=True),
      }
      _defaults = {
          'amount_type': 'percentage_of_balance',
Simple merge
Simple merge
@@@ -69,10 -70,10 +69,10 @@@ class crm_tracking_mixin(osv.AbstractMo
      _name = 'crm.tracking.mixin'
  
      _columns = {
 -        'campaign_id': fields.many2one('crm.tracking.campaign', 'Campaign',  # old domain ="['|',('section_id','=',section_id),('section_id','=',False)]"
 +        'campaign_id': fields.many2one('crm.tracking.campaign', 'Campaign',  # old domain ="['|',('team_id','=',team_id),('team_id','=',False)]"
                                         help="This is a name that helps you keep track of your different campaign efforts Ex: Fall_Drive, Christmas_Special"),
          'source_id': fields.many2one('crm.tracking.source', 'Source', help="This is the source of the link Ex: Search Engine, another domain, or name of email list"),
-         'medium_id': fields.many2one('crm.tracking.medium', 'Channel', help="This is the method of delivery. Ex: Postcard, Email, or Banner Ad"),
+         'medium_id': fields.many2one('crm.tracking.medium', 'Channel', help="This is the method of delivery. Ex: Postcard, Email, or Banner Ad", oldname='channel_id'),
      }
  
      def tracking_fields(self):
                      <field name="partner_id" operator="child_of"/>
                      <field name="stage_id" domain="[]"/>
                      <field name="probability"/>
 +                    <field name="lost_reason"/>
                      <separator/>
                      <filter string="New" name="new"
-                             domain="[('probability', '=', 0), ('stage_id.sequence', '&lt;=', 1)]"/>
+                             domain="['&amp;', ('stage_id.probability', '=', 0), ('stage_id.sequence', '&lt;=', 1)]"/>
                      <filter string="Won" name="won"
-                             domain="[('probability', '=', 100), ('stage_id.fold', '=', True)]"/>
+                             domain="['&amp;', ('stage_id.probability', '=', 100), ('stage_id.fold', '=', True)]"/>
                      <filter string="Lost" name="lost"
-                             domain="[('probability', '=', 0), ('stage_id.fold', '=', True)]"/>
+                             domain="['&amp;', ('stage_id.probability', '=', 0), ('stage_id.fold', '=', True)]"/>
                      <separator/>
                      <filter string="My Opportunities" name="assigned_to_me"
                              domain="[('user_id', '=', uid)]"
                      <filter name="lead" string="Lead" domain="[('type','=', 'lead')]" help="Show only lead"/>
                      <filter name="opportunity" string="Opportunity" domain="[('type','=','opportunity')]" help="Show only opportunity"/>
                      <separator/>
-                     <filter string="New" name="new"
-                             domain="[('probability', '=', 0), ('stage_id.sequence', '=', 1)]"/>
                      <filter string="Won" name="won"
-                             domain="[('probability', '=', 100), ('stage_id.on_change', '=', 1)]"/>
+                             domain="['&amp;', ('stage_id.probability', '=', 100), ('stage_id.on_change', '=', 1)]"/>
                      <filter string="Lost" name="lost"
-                             domain="[('probability', '=', 0), ('stage_id.sequence', '!=', 1)]"/>
+                             domain="['&amp;', ('stage_id.probability', '=', 0), ('stage_id.sequence', '!=', 1)]"/>
 -                    <field name="section_id" context="{'invisible_section': False}"
 +                    <field name="team_id" context="{'invisible_team': False}"
                              groups="base.group_multi_salesteams"/>
                      <field name="user_id" string="Salesperson"/>
                      <group expand="0" string="Extended Filters">
                      <filter name="lead" string="Lead" domain="[('type','=', 'lead')]" help="Show only lead"/>
                      <filter name="opportunity" string="Opportunity" domain="[('type','=','opportunity')]" help="Show only opportunity"/>
                      <separator/>
-                     <filter string="New" name="new"
-                             domain="[('probability', '=', 0), ('stage_id.sequence', '&lt;=', 1)]"/>
                      <filter string="Won" name="won"
-                             domain="[('probability', '=', 100), ('stage_id.on_change', '=', 1)]"/>
+                             domain="['&amp;', ('stage_id.probability', '=', 100), ('stage_id.on_change', '=', 1)]"/>
                      <filter string="Lost" name="lost"
-                             domain="[('probability', '=', 0), ('stage_id.sequence', '!=', 1)]"/>
+                             domain="['&amp;', ('stage_id.probability', '=', 0), ('stage_id.sequence', '!=', 1)]"/>
 -                    <field name="section_id" context="{'invisible_section': False}"
 +                    <field name="team_id" context="{'invisible_team': False}"
                              groups="base.group_multi_salesteams"/>
                      <field name="user_id" string="Salesperson"/>
                      <group expand="0" string="Extended Filters">
Simple merge
Simple merge
@@@ -73,9 -76,7 +73,9 @@@ access_product_pricelist_version_mrp_ma
  access_product_pricelist_item_mrp_manager,product.pricelist.item mrp_manager,product.model_product_pricelist_item,mrp.group_mrp_manager,1,1,1,1
  access_resource_calendar_manufacturinguser,resource.calendar manufacturing.user,resource.model_resource_calendar,mrp.group_mrp_user,1,0,0,0
  access_account_journal_mrp_manager,account.journal mrp manager,account.model_account_journal,mrp.group_mrp_manager,1,0,0,0
- access_mrp_property_group,mrp.property.group,model_mrp_property_group,stock.group_stock_manager,1,1,1,1
- access_mrp_property,mrp.property,model_mrp_property,stock.group_stock_manager,1,1,1,1
+ access_mrp_property_group_manager,mrp.property.group,model_mrp_property_group,stock.group_stock_manager,1,1,1,1
+ access_mrp_property_manager,mrp.property,model_mrp_property,stock.group_stock_manager,1,1,1,1
  access_mrp_property_group,mrp.property.group,model_mrp_property_group,base.group_user,1,0,0,0
  access_mrp_property,mrp.property,model_mrp_property,base.group_user,1,0,0,0
 +access_mrp_bom_invoicing_payment,mrp.bom,model_mrp_bom,account.group_account_invoice,1,0,0,0
 +access_mrp_production_invoicing_payment,mrp.production,model_mrp_production,account.group_account_invoice,1,1,1,0
@@@ -20,7 -20,7 +20,8 @@@
                                  <field name="company_id"/>
                                  <field name="website_published"/>
                                  <field name="environment"/>
+                                 <field name="validation"/>
 +                                <field name="auto_confirm"/>
                              </group>
                              <group>
                                  <field name="fees_active"/>
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
@@@ -682,22 -594,321 +682,23 @@@ instance.web.SearchView = instance.web.
              input_to_focus.$el.focus();
          });
      },
 -
 -    search_view_loaded: function(data) {
 -        var self = this;
 -        this.fields_view = data;
 +    childFocused: function () {
 +        this.$el.addClass('active');
+         this.view_id = this.view_id || data.view_id;
 -        if (data.type !== 'search' ||
 -            data.arch.tag !== 'search') {
 -                throw new Error(_.str.sprintf(
 -                    "Got non-search view after asking for a search view: type %s, arch root %s",
 -                    data.type, data.arch.tag));
 -        }
 -
 -        return this.drawer_ready
 -            .then(this.proxy('setup_default_query'))
 -            .then(function () { 
 -                self.trigger("search_view_loaded", data);
 -                self.ready.resolve();
 -            });
 -    },
 -    setup_default_query: function () {
 -        // Hacky implementation of CustomFilters#facet_for_defaults ensure
 -        // CustomFilters will be ready (and CustomFilters#filters will be
 -        // correctly filled) by the time this method executes.
 -        var custom_filters = this.drawer.custom_filters.filters;
 -        if (!this.options.disable_custom_filters && !_(custom_filters).isEmpty()) {
 -            // Check for any is_default custom filter
 -            var personal_filter = _(custom_filters).find(function (filter) {
 -                return filter.user_id && filter.is_default;
 -            });
 -            if (personal_filter) {
 -                this.drawer.custom_filters.toggle_filter(personal_filter, true);
 -                return;
 -            }
 -
 -            var global_filter = _(custom_filters).find(function (filter) {
 -                return !filter.user_id && filter.is_default;
 -            });
 -            if (global_filter) {
 -                this.drawer.custom_filters.toggle_filter(global_filter, true);
 -                return;
 -            }
 -        }
 -        // No custom filter, or no is_default custom filter, apply view defaults
 -        this.query.reset(_(arguments).compact(), {preventSearch: true});
 -    },
 -    /**
 -     * Extract search data from the view's facets.
 -     *
 -     * Result is an object with 4 (own) properties:
 -     *
 -     * errors
 -     *     An array of any error generated during data validation and
 -     *     extraction, contains the validation error objects
 -     * domains
 -     *     Array of domains
 -     * contexts
 -     *     Array of contexts
 -     * groupbys
 -     *     Array of domains, in groupby order rather than view order
 -     *
 -     * @return {Object}
 -     */
 -    build_search_data: function () {
 -        var domains = [], contexts = [], groupbys = [], errors = [];
 -
 -        this.query.each(function (facet) {
 -            var field = facet.get('field');
 -            try {
 -                var domain = field.get_domain(facet);
 -                if (domain) {
 -                    domains.push(domain);
 -                }
 -                var context = field.get_context(facet);
 -                if (context) {
 -                    contexts.push(context);
 -                }
 -                var group_by = field.get_groupby(facet);
 -                if (group_by) {
 -                    groupbys.push.apply(groupbys, group_by);
 -                }
 -            } catch (e) {
 -                if (e instanceof instance.web.search.Invalid) {
 -                    errors.push(e);
 -                } else {
 -                    throw e;
 -                }
 -            }
 -        });
 -        return {
 -            domains: domains,
 -            contexts: contexts,
 -            groupbys: groupbys,
 -            errors: errors
 -        };
 -    }, 
 -    /**
 -     * Performs the search view collection of widget data.
 -     *
 -     * If the collection went well (all fields are valid), then triggers
 -     * :js:func:`instance.web.SearchView.on_search`.
 -     *
 -     * If at least one field failed its validation, triggers
 -     * :js:func:`instance.web.SearchView.on_invalid` instead.
 -     *
 -     * @param [_query]
 -     * @param {Object} [options]
 -     */
 -    do_search: function (_query, options) {
 -        if (options && options.preventSearch) {
 -            return;
 -        }
 -        var search = this.build_search_data();
 -        if (!_.isEmpty(search.errors)) {
 -            this.on_invalid(search.errors);
 -            return;
 -        }
 -        this.trigger('search_data', search.domains, search.contexts, search.groupbys);
 -    },
 -    /**
 -     * Triggered after the SearchView has collected all relevant domains and
 -     * contexts.
 -     *
 -     * It is provided with an Array of domains and an Array of contexts, which
 -     * may or may not be evaluated (each item can be either a valid domain or
 -     * context, or a string to evaluate in order in the sequence)
 -     *
 -     * It is also passed an array of contexts used for group_by (they are in
 -     * the correct order for group_by evaluation, which contexts may not be)
 -     *
 -     * @event
 -     * @param {Array} domains an array of literal domains or domain references
 -     * @param {Array} contexts an array of literal contexts or context refs
 -     * @param {Array} groupbys ordered contexts which may or may not have group_by keys
 -     */
 -    /**
 -     * Triggered after a validation error in the SearchView fields.
 -     *
 -     * Error objects have three keys:
 -     * * ``field`` is the name of the invalid field
 -     * * ``value`` is the invalid value
 -     * * ``message`` is the (in)validation message provided by the field
 -     *
 -     * @event
 -     * @param {Array} errors a never-empty array of error objects
 -     */
 -    on_invalid: function (errors) {
 -        this.do_notify(_t("Invalid Search"), _t("triggered from search view"));
 -        this.trigger('invalid_search', errors);
 -    },
 -
 -    // The method appendTo is overwrited to be able to insert the drawer anywhere
 -    appendTo: function ($searchview_parent, $searchview_drawer_node) {
 -        var $searchview_drawer_node = $searchview_drawer_node || $searchview_parent;
 -
 -        return $.when(
 -            this._super($searchview_parent),
 -            this.drawer.appendTo($searchview_drawer_node)
 -        );
 -    },
 -
 -    destroy: function () {
 -        this.drawer.destroy();
 -        this.getParent().destroy.call(this);
 -    }
 -});
 -
 -instance.web.SearchViewDrawer = instance.web.Widget.extend({
 -    template: "SearchViewDrawer",
 -
 -    init: function(parent, searchview) {
 -        this._super(parent);
 -        this.searchview = searchview;
 -        this.searchview.set_drawer(this);
 -        this.ready = searchview.drawer_ready;
 -        this.controls = [];
 -        this.inputs = [];
 -    },
 -
 -    toggle: function (visibility) {
 -        this.$el.toggle(visibility);
 -        var $view_manager_body = this.$el.closest('.oe_view_manager_body');
 -        if ($view_manager_body.length) {
 -            $view_manager_body.scrollTop(0);
 -        }
 -    },
 -
 -    start: function() {
 -        var self = this;
 -        if (this.searchview.headless) return $.when(this._super(), this.searchview.ready);
 -        var filters_ready = this.searchview.fields_view_get
 -                                .then(this.proxy('prepare_filters'));
 -        return $.when(this._super(), filters_ready).then(function () {
 -            var defaults = arguments[1][0];
 -            self.ready.resolve.apply(null, defaults);
 -        });
 -    },
 -    prepare_filters: function (data) {
 -        this.make_widgets(
 -            data['arch'].children,
 -            data.fields);
 -
 -        this.add_common_inputs();
 -
 -        // build drawer
 -        var in_drawer = this.select_for_drawer();
 -
 -        var $first_col = this.$(".col-md-7"),
 -            $snd_col = this.$(".col-md-5");
 -
 -        var add_custom_filters = in_drawer[0].appendTo($first_col),
 -            add_filters = in_drawer[1].appendTo($first_col),
 -            add_rest = $.when.apply(null, _(in_drawer.slice(2)).invoke('appendTo', $snd_col)),
 -            defaults_fetched = $.when.apply(null, _(this.inputs).invoke(
 -                'facet_for_defaults', this.searchview.defaults));
 -
 -        return $.when(defaults_fetched, add_custom_filters, add_filters, add_rest);
 -    },
 -    /**
 -     * Sets up thingie where all the mess is put?
 -     */
 -    select_for_drawer: function () {
 -        return _(this.inputs).filter(function (input) {
 -            return input.in_drawer();
 -        });
 -    },
 -
 -    /**
 -     * Builds a list of widget rows (each row is an array of widgets)
 -     *
 -     * @param {Array} items a list of nodes to convert to widgets
 -     * @param {Object} fields a mapping of field names to (ORM) field attributes
 -     * @param {Object} [group] group to put the new controls in
 -     */
 -    make_widgets: function (items, fields, group) {
 -        if (!group) {
 -            group = new instance.web.search.Group(
 -                this, 'q', {attrs: {string: _t("Filters")}});
 -        }
 -        var self = this;
 -        var filters = [];
 -        _.each(items, function (item) {
 -            if (filters.length && item.tag !== 'filter') {
 -                group.push(new instance.web.search.FilterGroup(filters, group));
 -                filters = [];
 -            }
 -
 -            switch (item.tag) {
 -            case 'separator': case 'newline':
 -                break;
 -            case 'filter':
 -                filters.push(new instance.web.search.Filter(item, group));
 -                break;
 -            case 'group':
 -                self.add_separator();
 -                self.make_widgets(item.children, fields,
 -                    new instance.web.search.Group(group, 'w', item));
 -                self.add_separator();
 -                break;
 -            case 'field':
 -                var field = this.make_field(
 -                    item, fields[item['attrs'].name], group);
 -                group.push(field);
 -                // filters
 -                self.make_widgets(item.children, fields, group);
 -                break;
 -            }
 -        }, this);
 -
 -        if (filters.length) {
 -            group.push(new instance.web.search.FilterGroup(filters, this));
 -        }
      },
 -
 -    add_separator: function () {
 -        if (!(_.last(this.inputs) instanceof instance.web.search.Separator))
 -            new instance.web.search.Separator(this);
 +    childBlurred: function () {
 +        this.$el.val('').removeClass('active').trigger('blur');
 +        this.autocomplete.close();
      },
      /**
 -     * Creates a field for the provided field descriptor item (which comes
 -     * from fields_view_get)
 -     *
 -     * @param {Object} item fields_view_get node for the field
 -     * @param {Object} field fields_get result for the field
 -     * @param {Object} [parent]
 -     * @returns instance.web.search.Field
 +     * Call the renderFacets method with the correct arguments.
 +     * This is due to the fact that change events are called with two arguments
 +     * (model, options) while add, reset and remove events are called with
 +     * (collection, model, options) as arguments
       */
 -    make_field: function (item, field, parent) {
 -        // M2O combined with selection widget is pointless and broken in search views,
 -        // but has been used in the past for unsupported hacks -> ignore it
 -        if (field.type === "many2one" && item.attrs.widget === "selection"){
 -            item.attrs.widget = undefined;
 -        }
 -        var obj = instance.web.search.fields.get_any( [item.attrs.widget, field.type]);
 -        if(obj) {
 -            return new (obj) (item, field, parent || this);
 -        } else {
 -            console.group('Unknown field type ' + field.type);
 -            console.error('View node', item);
 -            console.info('View field', field);
 -            console.info('In view', this);
 -            console.groupEnd();
 -            return null;
 -        }
 -    },
 -
 -    add_common_inputs: function() {
 -        // add custom filters to this.inputs
 -        this.custom_filters = new instance.web.search.CustomFilters(this);
 -        // add Filters to this.inputs, need view.controls filled
 -        (new instance.web.search.Filters(this));
 -        (new instance.web.search.SaveFilter(this, this.custom_filters));
 -        // add Advanced to this.inputs
 -        (new instance.web.search.Advanced(this));
 +    renderChangedFacets: function (model, options) {
 +        this.renderFacets(undefined, model, options);
      },
 -
  });
  
  /**
Simple merge
Simple merge
@@@ -73,9 -54,9 +73,11 @@@ openerp.web_graph.Graph = openerp.web.W
              self.$('.graph_options_selection label').last().toggle(result);
          });
  
 +        this.$buttons.find('button').tooltip();
 +        
-         return this.model.call('fields_get', []).then(function (f) {
+         return this.model.call('fields_get', {
+                     context: this.graph_view.dataset.context
+                 }).then(function (f) {
              self.fields = f;
              self.fields.__count = {field:'__count', type: 'integer', string:_t('Count')};
              self.groupby_fields = self.get_groupby_fields();
Simple merge
Simple merge
@@@ -13,8 -13,9 +13,9 @@@
                          <button string="Cancel" type="object" name="cancel" class="oe_link"/>
                      </header>
                      <div>
 -                        <field name="website_id" invisible="True" on_change="on_change_website_id(website_id)"/>
 +                        <field name="website_id" on_change="on_change_website_id(website_id)"/>
                          <group string="Domain">
+                             <field name="website_name" />
                              <label for="google_analytics_key"/>
                              <div name="google_analytics_key">
                                  <div>
@@@ -156,6 -190,33 +186,23 @@@ class BlogPost(osv.Model)
              self.pool['mail.message'].write(cr, SUPERUSER_ID, msg_ids, {'path': new_attribute}, context=context)
          return content
  
 -    def create_history(self, cr, uid, ids, vals, context=None):
 -        for i in ids:
 -            history = self.pool.get('blog.post.history')
 -            if vals.get('content'):
 -                res = {
 -                    'content': vals.get('content', ''),
 -                    'post_id': i,
 -                }
 -                history.create(cr, uid, res)
 -
+     def _check_for_publication(self, cr, uid, ids, vals, context=None):
+         if vals.get('website_published'):
+             base_url = self.pool['ir.config_parameter'].get_param(cr, uid, 'web.base.url')
+             for post in self.browse(cr, uid, ids, context=context):
+                 post.blog_id.message_post(
+                     body='<p>%(post_publication)s <a href="%(base_url)s/blog/%(blog_slug)s/post/%(post_slug)s">%(post_link)s</a></p>' % {
+                         'post_publication': _('A new post %s has been published on the %s blog.') % (post.name, post.blog_id.name),
+                         'post_link': _('Click here to access the post.'),
+                         'base_url': base_url,
+                         'blog_slug': slug(post.blog_id),
+                         'post_slug': slug(post),
+                     },
+                     subtype='website_blog.mt_blog_blog_published',
+                     context=context)
+             return True
+         return False
      def create(self, cr, uid, vals, context=None):
          if context is None:
              context = {}
              vals['content'] = self._postproces_content(cr, uid, None, vals['content'], context=context)
          create_context = dict(context, mail_create_nolog=True)
          post_id = super(BlogPost, self).create(cr, uid, vals, context=create_context)
 -        self.create_history(cr, uid, [post_id], vals, context)
+         self._check_for_publication(cr, uid, [post_id], vals, context=context)
          return post_id
  
      def write(self, cr, uid, ids, vals, context=None):
          if 'content' in vals:
              vals['content'] = self._postproces_content(cr, uid, None, vals['content'], context=context)
          result = super(BlogPost, self).write(cr, uid, ids, vals, context)
 -        self.create_history(cr, uid, ids, vals, context)
+         self._check_for_publication(cr, uid, ids, vals, context=context)
          return result
 -
 -class BlogPostHistory(osv.Model):
 -    _name = "blog.post.history"
 -    _description = "Blog Post History"
 -    _order = 'id DESC'
 -    _rec_name = "create_date"
 -
 -    _columns = {
 -        'post_id': fields.many2one('blog.post', 'Blog Post'),
 -        'summary': fields.char('Summary', select=True),
 -        'content': fields.text("Content"),
 -        'create_date': fields.datetime("Date"),
 -        'create_uid': fields.many2one('res.users', "Modified By"),
 -    }
 -
 -    def getDiff(self, cr, uid, v1, v2, context=None):
 -        history_pool = self.pool.get('blog.post.history')
 -        text1 = history_pool.read(cr, uid, [v1], ['content'])[0]['content']
 -        text2 = history_pool.read(cr, uid, [v2], ['content'])[0]['content']
 -        line1 = line2 = ''
 -        if text1:
 -            line1 = text1.splitlines(1)
 -        if text2:
 -            line2 = text2.splitlines(1)
 -        if (not line1 and not line2) or (line1 == line2):
 -            raise osv.except_osv(_('Warning!'), _('There are no changes in revisions.'))
 -        diff = difflib.HtmlDiff()
 -        return diff.make_table(line1, line2, "Revision-%s" % (v1), "Revision-%s" % (v2), context=True)
@@@ -1,6 -1,8 +1,7 @@@
  id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink\r
  blog_blog_all,blog.blog,model_blog_blog,,1,0,0,0\r
+ blog_blog,blog.blog,model_blog_blog,base.group_document_user,1,1,1,1\r
  blog_post_all,blog.post,model_blog_post,,1,0,0,0\r
  blog_post,blog.post,model_blog_post,base.group_document_user,1,1,1,1\r
 -blog_post_history,blog.post.history,model_blog_post_history,base.group_document_user,1,0,1,0\r
  blog_tag,blog.tag,model_blog_tag,,1,0,0,0\r
  blog_tag_edition,blog.tag,model_blog_tag,base.group_document_user,1,1,1,1\r
          </record>
          <menuitem id="menu_blog" parent="menu_wiki" name="Blogs" action="action_blog_blog" sequence="20"/>
  
 -        <!-- History Tree view -->
 -        <record model="ir.ui.view" id="view_blog_history_tree">
 -            <field name="name">blog.post.history.tree</field>
 -            <field name="model">blog.post.history</field>
 -            <field name="arch" type="xml">
 -                <tree string="Document History">
 -                    <field name="create_date"/>
 -                    <field name="create_uid"/>
 -                    <field name="post_id"/>
 -                </tree>
 -            </field>
 -        </record>
 -        <!-- History Form view  -->
 -        <record model="ir.ui.view" id="view_blog_history_form">
 -            <field name="name">blog.post.history.form</field>
 -            <field name="model">blog.post.history</field>
 -            <field name="arch" type="xml">
 -                <form string="Blog Post History">
 -                    <label for="post_id" class="oe_edit_only"/>
 -                    <h1><field name="post_id" select="1" /></h1>
 -                    <label for="create_date" class="oe_edit_only"/>
 -                    <field name="create_date" readonly="1"/>
 -                </form>
 -            </field>
 -        </record>
 -        <!-- History Action  -->
 -        <record model="ir.actions.act_window" id="action_history">
 -            <field name="name">Page history</field>
 -            <field name="res_model">blog.post.history</field>
 -            <field name="view_type">form</field>
 -            <field name="view_mode">tree,form</field>
 -        </record>
 -        <menuitem id="menu_page_history" parent="menu_wiki" name="Pages history" action="action_history" sequence="30" groups="base.group_no_one"/>
 -        <act_window
 -            id="action_related_page_history"
 -            context="{'search_default_post_id': [active_id], 'default_post_id': active_id}"
 -            domain="[('post_id','=',active_id)]"
 -            name="Page History"
 -            res_model="blog.post.history"
 -            src_model="blog.post"/>
 -
+         <record model="ir.ui.view" id="blog_tag_tree">
+             <field name="name">blog_tag_tree</field>
+             <field name="model">blog.tag</field>
+             <field name="arch" type="xml">
+                 <tree string="Tag List" create="false">
+                     <field name="name"/>
+                     <field name="post_ids"/>
+                 </tree>
+             </field>
+         </record>
+         <record model="ir.ui.view" id="blog_tag_form">
+             <field name="name">blog_tag_form</field>
+             <field name="model">blog.tag</field>
+             <field name="arch" type="xml">
+                 <form string="Tag Form">
+                     <sheet>
+                         <group>
+                             <field name="name"/>
+                         </group>
+                         <label for="post_ids" string="Used in: "/>
+                         <field name="post_ids"/>
+                     </sheet>
+                 </form>
+             </field>
+         </record>
+         <record model="ir.actions.act_window" id="action_tags">
+             <field name="name">Blog Tags</field>
+             <field name="res_model">blog.tag</field>
+             <field name="view_type">form</field>
+             <field name="view_mode">tree,form,graph</field>
+             <field name="view_id" ref="blog_tag_tree"/>
+         </record>
+         <menuitem id="menu_blog_tag" parent="menu_wiki" name="Blog Tags" action="action_tags" sequence="40" />
      </data>
  </openerp>
@@@ -411,46 -498,59 +411,56 @@@ class WebsiteForum(http.Controller)
  
      @http.route(['/forum/<model("forum.forum"):forum>/user/<int:user_id>'], type='http', auth="public", website=True)
      def open_user(self, forum, user_id=0, **post):
 -        cr, uid, context = request.cr, request.uid, request.context
 -        User = request.registry['res.users']
 -        Post = request.registry['forum.post']
 -        Vote = request.registry['forum.post.vote']
 -        Activity = request.registry['mail.message']
 -        Followers = request.registry['mail.followers']
 -        Data = request.registry["ir.model.data"]
 +        User = request.env['res.users']
 +        Post = request.env['forum.post']
 +        Vote = request.env['forum.post.vote']
 +        Activity = request.env['mail.message']
 +        Followers = request.env['mail.followers']
 +        Data = request.env["ir.model.data"]
  
 -        user = User.browse(cr, SUPERUSER_ID, user_id, context=context)
 -        current_user = User.browse(cr, SUPERUSER_ID, uid, context=context)
 +        user = User.sudo().search([('id', '=', user_id)])
++        current_user = request.env.user.sudo()
 +        if not user or user.karma < 1:
+         # Users with high karma can see users with karma <= 0 for
+         # moderation purposes, IFF they have posted something (see below)
 -        if (not user.exists() or
++        if (not user or
+                (user.karma < 1 and current_user.karma < forum.karma_unlink_all)):
              return werkzeug.utils.redirect("/forum/%s" % slug(forum))
          values = self._prepare_forum_values(forum=forum, **post)
-         if user_id != request.session.uid and not user.website_published:
-             return request.website.render("website_forum.private_profile", values)
          # questions and answers by user
-         user_questions, user_answers = [], []
 -        user_question_ids = Post.search(cr, uid, [
 -                ('parent_id', '=', False),
 -                ('forum_id', '=', forum.id), ('create_uid', '=', user.id),
 -            ], order='create_date desc', context=context)
 +        user_question_ids = Post.search([
 +            ('parent_id', '=', False),
 +            ('forum_id', '=', forum.id), ('create_uid', '=', user.id)],
 +            order='create_date desc')
          count_user_questions = len(user_question_ids)
+         if (user_id != request.session.uid and not
+                 (user.website_published or
+                     (count_user_questions and current_user.karma > forum.karma_unlink_all))):
+             return request.website.render("website_forum.private_profile", values)
          # displaying only the 20 most recent questions
 -        user_questions = Post.browse(cr, uid, user_question_ids[:20], context=context)
 +        user_questions = user_question_ids[:20]
  
 -        user_answer_ids = Post.search(cr, uid, [
 -                ('parent_id', '!=', False),
 -                ('forum_id', '=', forum.id), ('create_uid', '=', user.id),
 -            ], order='create_date desc', context=context)
 +        user_answer_ids = Post.search([
 +            ('parent_id', '!=', False),
 +            ('forum_id', '=', forum.id), ('create_uid', '=', user.id)],
 +            order='create_date desc')
          count_user_answers = len(user_answer_ids)
          # displaying only the 20  most recent answers
 -        user_answers = Post.browse(cr, uid, user_answer_ids[:20], context=context)
 +        user_answers = user_answer_ids[:20]
  
          # showing questions which user following
 -        obj_ids = Followers.search(cr, SUPERUSER_ID, [('res_model', '=', 'forum.post'), ('partner_id', '=', user.partner_id.id)], context=context)
 -        post_ids = [follower.res_id for follower in Followers.browse(cr, SUPERUSER_ID, obj_ids, context=context)]
 -        que_ids = Post.search(cr, uid, [('id', 'in', post_ids), ('forum_id', '=', forum.id), ('parent_id', '=', False)], context=context)
 -        followed = Post.browse(cr, uid, que_ids, context=context)
 +        post_ids = [follower.res_id for follower in Followers.sudo().search([('res_model', '=', 'forum.post'), ('partner_id', '=', user.partner_id.id)])]
 +        followed = Post.search([('id', 'in', post_ids), ('forum_id', '=', forum.id), ('parent_id', '=', False)])
  
 -        #showing Favourite questions of user.
 -        fav_que_ids = Post.search(cr, uid, [('favourite_ids', '=', user.id), ('forum_id', '=', forum.id), ('parent_id', '=', False)], context=context)
 -        favourite = Post.browse(cr, uid, fav_que_ids, context=context)
 +        # showing Favourite questions of user.
 +        favourite = Post.search([('favourite_ids', '=', user.id), ('forum_id', '=', forum.id), ('parent_id', '=', False)])
  
 -        #votes which given on users questions and answers.
 -        data = Vote.read_group(cr, uid, [('forum_id', '=', forum.id), ('recipient_id', '=', user.id)], ["vote"], groupby=["vote"], context=context)
 +        # votes which given on users questions and answers.
 +        data = Vote.read_group([('forum_id', '=', forum.id), ('recipient_id', '=', user.id)], ["vote"], groupby=["vote"])
          up_votes, down_votes = 0, 0
          for rec in data:
              if rec['vote'] == '1':
@@@ -42,91 -78,74 +42,93 @@@ class Forum(models.Model)
              return f.read()
          return False
  
 -    _defaults = {
 -        'description': 'This community is for professionals and enthusiasts of our products and services.',
 -        'faq': _get_default_faq,
 -        'karma_gen_question_new': 0,  # set to null for anti spam protection
 -        'karma_gen_question_upvote': 5,
 -        'karma_gen_question_downvote': -2,
 -        'karma_gen_answer_upvote': 10,
 -        'karma_gen_answer_downvote': -2,
 -        'karma_gen_answer_accept': 2,
 -        'karma_gen_answer_accepted': 15,
 -        'karma_gen_answer_flagged': -100,
 -        'karma_ask': 3,  # set to not null for anti spam protection
 -        'karma_answer': 3,  # set to not null for anti spam protection
 -        'karma_edit_own': 1,
 -        'karma_edit_all': 300,
 -        'karma_close_own': 100,
 -        'karma_close_all': 500,
 -        'karma_unlink_own': 500,
 -        'karma_unlink_all': 1000,
 -        'karma_upvote': 5,
 -        'karma_downvote': 50,
 -        'karma_answer_accept_own': 20,
 -        'karma_answer_accept_all': 500,
 -        'karma_editor_link_files': 20,
 -        'karma_editor_clickable_link': 20,
 -        'karma_comment_own': 3,
 -        'karma_comment_all': 5,
 -        'karma_comment_convert_own': 50,
 -        'karma_comment_convert_all': 500,
 -        'karma_comment_unlink_own': 50,
 -        'karma_comment_unlink_all': 500,
 -        'karma_retag': 75,
 -        'karma_flag': 500,
 -    }
 -
 -    def create(self, cr, uid, values, context=None):
 -        if context is None:
 -            context = {}
 -        create_context = dict(context, mail_create_nolog=True)
 -        return super(Forum, self).create(cr, uid, values, context=create_context)
 -
 -    def _tag_to_write_vals(self, cr, uid, ids, tags='', context=None):
 -        User = self.pool['res.users']
 -        Tag = self.pool['forum.tag']
 -        result = {}
 -        for forum in self.browse(cr, uid, ids, context=context):
 -            post_tags = []
 -            existing_keep = []
 -            for tag in filter(None, tags.split(',')):
 -                if tag.startswith('_'):  # it's a new tag
 -                    # check that not already created meanwhile or maybe excluded by the limit on the search
 -                    tag_ids = Tag.search(cr, uid, [('name', '=', tag[1:])], context=context)
 -                    if tag_ids:
 -                        existing_keep.append(int(tag_ids[0]))
 -                    else:
 -                        # check if user have Karma needed to create need tag
 -                        user = User.browse(cr, uid, uid, context=context)
 -                        if user.exists() and user.karma >= forum.karma_retag:
 -                            post_tags.append((0, 0, {'name': tag[1:], 'forum_id': forum.id}))
 +    # description and use
 +    name = fields.Char('Forum Name', required=True, translate=True)
 +    faq = fields.Html('Guidelines', default=_get_default_faq, translate=True)
 +    description = fields.Html(
 +        'Description',
 +        default='<p> This community is for professionals and enthusiasts of our products and services.'
 +                'Share and discuss the best content and new marketing ideas,'
 +                'build your professional profile and become a better marketer together.</p>')
 +    default_order = fields.Selection([
 +        ('create_date desc', 'Newest'),
 +        ('write_date desc', 'Last Updated'),
 +        ('vote_count desc', 'Most Voted'),
 +        ('relevancy desc', 'Relevancy'),
 +        ('child_count desc', 'Answered')],
 +        string='Default Order', required=True, default='write_date desc')
 +    relevancy_post_vote = fields.Float('First Relevancy Parameter', default=0.8)
 +    relevancy_time_decay = fields.Float('Second Relevancy Parameter', default=1.8)
 +    default_post_type = fields.Selection([
 +        ('question', 'Question'),
 +        ('discussion', 'Discussion'),
 +        ('link', 'Link')],
 +        string='Default Post', required=True, default='question')
 +    allow_question = fields.Boolean('Questions', help="Users can answer only once per question. Contributors can edit answers and mark the right ones.", default=True)
 +    allow_discussion = fields.Boolean('Discussions', default=True)
 +    allow_link = fields.Boolean('Links', help="When clicking on the post, it redirects to an external link", default=True)
 +    # karma generation
 +    karma_gen_question_new = fields.Integer(string='Asking a question', default=2)
 +    karma_gen_question_upvote = fields.Integer(string='Question upvoted', default=5)
 +    karma_gen_question_downvote = fields.Integer(string='Question downvoted', default=-2)
 +    karma_gen_answer_upvote = fields.Integer(string='Answer upvoted', default=10)
 +    karma_gen_answer_downvote = fields.Integer(string='Answer downvoted', default=-2)
 +    karma_gen_answer_accept = fields.Integer(string='Accepting an answer', default=2)
 +    karma_gen_answer_accepted = fields.Integer(string='Answer accepted', default=15)
 +    karma_gen_answer_flagged = fields.Integer(string='Answer flagged', default=-100)
 +    # karma-based actions
 +    karma_ask = fields.Integer(string='Ask a new question', default=3)
 +    karma_answer = fields.Integer(string='Answer a question', default=3)
 +    karma_edit_own = fields.Integer(string='Edit its own posts', default=1)
 +    karma_edit_all = fields.Integer(string='Edit all posts', default=300)
 +    karma_close_own = fields.Integer(string='Close its own posts', default=100)
 +    karma_close_all = fields.Integer(string='Close all posts', default=500)
 +    karma_unlink_own = fields.Integer(string='Delete its own posts', default=500)
 +    karma_unlink_all = fields.Integer(string='Delete all posts', default=1000)
 +    karma_upvote = fields.Integer(string='Upvote', default=5)
 +    karma_downvote = fields.Integer(string='Downvote', default=50)
 +    karma_answer_accept_own = fields.Integer(string='Accept an answer on its own questions', default=20)
 +    karma_answer_accept_all = fields.Integer(string='Accept an answers to all questions', default=500)
 +    karma_editor_link_files = fields.Integer(string='Linking files (Editor)', default=20)
 +    karma_editor_clickable_link = fields.Integer(string='Add clickable links (Editor)', default=20)
 +    karma_comment_own = fields.Integer(string='Comment its own posts', default=1)
 +    karma_comment_all = fields.Integer(string='Comment all posts', default=1)
 +    karma_comment_convert_own = fields.Integer(string='Convert its own answers to comments and vice versa', default=50)
 +    karma_comment_convert_all = fields.Integer(string='Convert all answers to answers and vice versa', default=500)
 +    karma_comment_unlink_own = fields.Integer(string='Unlink its own comments', default=50)
 +    karma_comment_unlink_all = fields.Integer(string='Unlinnk all comments', default=500)
 +    karma_retag = fields.Integer(string='Change question tags', default=75)
 +    karma_flag = fields.Integer(string='Flag a post as offensive', default=500)
 +    karma_dofollow = fields.Integer(string='Disabled links', help='If the author has not enough karma, a nofollow attribute is added to links', default=500)
 +
 +    @api.model
 +    def create(self, values):
 +        return super(Forum, self.with_context(mail_create_nolog=True)).create(values)
 +
 +    @api.model
 +    def _tag_to_write_vals(self, tags=''):
 +        User = self.env['res.users']
 +        Tag = self.env['forum.tag']
 +        post_tags = []
++        existing_keep = []
 +        for tag in filter(None, tags.split(',')):
 +            if tag.startswith('_'):  # it's a new tag
 +                # check that not arleady created meanwhile or maybe excluded by the limit on the search
 +                tag_ids = Tag.search([('name', '=', tag[1:])])
 +                if tag_ids:
-                     post_tags.append((4, int(tag_ids[0])))
++                    existing_keep.append(int(tag_ids[0]))
                  else:
 -                    existing_keep.append(int(tag))
 -            post_tags.insert(0, [6, 0, existing_keep])
 -            result[forum.id] = post_tags
 -
 -        return result
 +                    # check if user have Karma needed to create need tag
 +                    user = User.sudo().browse(self._uid)
 +                    if user.exists() and user.karma >= self.karma_retag:
-                             post_tags.append((0, 0, {'name': tag[1:], 'forum_id': self.id}))
++                        post_tags.append((0, 0, {'name': tag[1:], 'forum_id': self.id}))
 +            else:
-                 post_tags.append((4, int(tag)))
++                existing_keep.append(int(tag))
++        post_tags.insert(0, [6, 0, existing_keep])
 +        return post_tags
  
  
 -class Post(osv.Model):
 +class Post(models.Model):
      _name = 'forum.post'
      _description = 'Forum Post'
      _inherit = ['mail.thread', 'website.seo.metadata']
                  }
              });
          }
-         function IsKarmaValid(eventNumber,minKarma){
-             "use strict";
-             if(parseInt($("#karma").val()) >= minKarma){
-                 CKEDITOR.tools.callFunction(eventNumber,this);
-                 return false;
-             } else {
-                 alert("Sorry you need more than " + minKarma + " Karma.");
-             }
-         }
 -        //END-TODO Remove in master
 -
 -        if ($('textarea.load_editor').length) {
 -            var editor = CKEDITOR.instances['content'];
 -            editor.on('instanceReady', CKEDITORLoadComplete);
 -        }
 -
++        
          function CKEDITORLoadComplete(){
              "use strict";
-             $('.cke_button__link').on('click', function() { IsKarmaValid(33,30); });
-             $('.cke_button__unlink').on('click', function() { IsKarmaValid(37,30); });
-             $('.cke_button__image').on('click', function() { IsKarmaValid(41,30); });
+             $('.cke_button__link').attr('onclick','website_forum_IsKarmaValid(33,30)');
+             $('.cke_button__unlink').attr('onclick','website_forum_IsKarmaValid(37,30)');
+             $('.cke_button__image').attr('onclick','website_forum_IsKarmaValid(41,30)');
          }
      });
  
      </form>
  </template>
  
 -<template id="assets_frontend" inherit_id="website.assets_frontend" name="website_forum assets">
 -    <xpath expr="." position="inside">
 -        <link rel='stylesheet' href="/web/static/lib/jquery.textext/jquery.textext.css"/>
 -        <link rel='stylesheet' href='/website_forum/static/src/css/website_forum.css'/>
 -        <script type="text/javascript" src="/web/static/lib/jquery.textext/jquery.textext.js"/>
+         <script type="text/javascript" src="/web/static/lib/select2/select2.js"></script>
+         <link rel="stylesheet" href="/web/static/lib/select2/select2.css"/>
+         <link rel="stylesheet" href="/website/static/lib/select2-bootstrap-css/select2-bootstrap.css"/>
+         <script type="text/javascript" src="/website_forum/static/src/js/website_forum.js"/>
 -    </xpath>
 -</template>
 -
 -<template id="assets_editor" inherit_id="website.assets_editor" name="Forum Editor" groups="base.group_user">
 -    <xpath expr="." position="inside">
 -        <script type="text/javascript" src="/website_forum/static/src/js/website.tour.forum.js"/>
 -        <script type="text/javascript" src="/website_forum/static/src/js/website_forum.editor.js"/>
 -    </xpath>
 -</template>
 -
  <!-- Page Index -->
  <template id="header" name="Forum Index">
      <t t-call="website.layout">
              </textarea>
              <div t-if="not is_answer">
                  <br/>
 -                <input type="hidden" name="tag_type" value="select2"/>
 -                <input type="hidden" name="question_tag" class="form-control col-md-9 js_select2" placeholder="Tags" value="see data init value" t-attf-data-init-value="#{tags}"/>
 +                <input type="hidden" name="karma_retag" t-attf-value="#{forum.karma_retag}" id="karma_retag"/>
-                 <input type="text" name="post_tag" class="form-control col-md-9 js_select2" placeholder="Tags" t-attf-value="#{tags}"/>
++                <input type="text" name="post_tag" class="form-control col-md-9 js_select2" placeholder="Tags" t-attf-data-init-value="#{tags}"/>
                  <br/>
              </div>
              <button class="btn btn-primary btn-lg">Save</button>
@@@ -38,9 -32,9 +38,9 @@@
                  </div>
              </div>
              <div id="email_designer" class="mb32" t-att-style="mode != 'email_designer' and 'display: none' or ''">
 -                <a class="mt16 btn btn-primary pull-right css_editable_mode_hidden" 
 +                <a class="mt16 btn btn-primary pull-right" id="save_and_continue"
                    t-attf-href="/web#return_label=Website&amp;model=#{model}&amp;id=#{res_id}&amp;view_type=form">
-                     Save and Continue
+                     Back to the mass mailing
                  </a>
                  <h1 class="page-header mt16">
                      Design Your Email
diff --cc doc/conf.py
Simple merge
@@@ -18,6 -18,6 +18,7 @@@ Referenc
      reference/qweb
      reference/javascript
  
+     reference/translations
      reference/reports
      reference/workflows
 +    reference/cdn
Simple merge
@@@ -193,10 -137,8 +193,10 @@@ class view(osv.osv)
              ('kanban', 'Kanban'),
              ('search','Search'),
              ('qweb', 'QWeb')], string='View Type'),
 -        'arch': fields.text('View Architecture', required=True),
 +        'arch': fields.function(_arch_get, fnct_inv=_arch_set, string='View Architecture', type="text", nodrop=True),
 +        'arch_db': fields.text('Arch Blob'),
 +        'arch_fs': fields.char('Arch Filename'),
-         'inherit_id': fields.many2one('ir.ui.view', 'Inherited View', ondelete='cascade', select=True),
+         'inherit_id': fields.many2one('ir.ui.view', 'Inherited View', ondelete='restrict', select=True),
          'inherit_children_ids': fields.one2many('ir.ui.view','inherit_id', 'Inherit Views'),
          'field_parent': fields.char('Child Field'),
          'model_data_id': fields.function(_get_model_data, type='many2one', relation='ir.model.data', string="Model Data",
                                  <group>
                                      <field name="ref"/>
                                      <field name="lang"/>
 -                                    <field name="date"/>
                                  </group>
                                  <group>
-                                     <field name="company_id" groups="base.group_multi_company" widget="selection"/>
 -                                    <field name="active"/>
++                                    <field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
 +                                    <field name="active"/>                                    
                                  </group>
                              </group>
 +                            <group name="container_row_2">
 +                                <group string="Sale" name="sale">
 +                                    <field name="customer"/>
 +                                    <field name="user_id" 
 +                                        context="{'default_groups_ref': ['base.group_partner_manager']}"/>
 +                                </group>
 +                                <group string="Purchase" name="purchase">
 +                                    <field name="supplier"/>
 +                                </group>
 +                            </group>
 +                            <group name="container_row_3">
 +                                <group name="container_left"/>
 +                                <group name="container_right"/>
 +                            </group>
                          </page>
                      </notebook>
                  </sheet>
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge