diff --git a/addons/payment_sips/__openerp__.py b/addons/payment_sips/__openerp__.py
index 89c36a757e9..9fc4fb26324 100644
--- a/addons/payment_sips/__openerp__.py
+++ b/addons/payment_sips/__openerp__.py
@@ -18,7 +18,7 @@
#
##############################################################################
{
- 'name': 'Worldline SIPS Payment Acquiring for online payments',
+ 'name': 'Worldline SIPS',
'version': '1.0',
'author': 'Eezee-It',
'category': 'Hidden',
diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js
index 3f892bb9b53..7eea6cb2725 100644
--- a/addons/point_of_sale/static/src/js/models.js
+++ b/addons/point_of_sale/static/src/js/models.js
@@ -6,6 +6,7 @@ var PosDB = require('point_of_sale.DB');
var devices = require('point_of_sale.devices');
var core = require('web.core');
var Model = require('web.DataModel');
+var formats = require('web.formats');
var session = require('web.session');
var time = require('web.time');
var utils = require('web.utils');
@@ -1070,8 +1071,7 @@ exports.Orderline = Backbone.Model.extend({
}
this.product = options.product;
this.price = options.product.price;
- this.quantity = 1;
- this.quantityStr = '1';
+ this.set_quantity(1);
this.discount = 0;
this.discountStr = '0';
this.type = 'unit';
@@ -1134,7 +1134,8 @@ exports.Orderline = Backbone.Model.extend({
if(unit){
if (unit.rounding) {
this.quantity = round_pr(quant, unit.rounding);
- this.quantityStr = this.quantity.toFixed(Math.ceil(Math.log(1.0 / unit.rounding) / Math.log(10)));
+ var decimals = Math.ceil(Math.log(1.0 / unit.rounding) / Math.log(10));
+ this.quantityStr = formats.format_value(round_di(this.quantity, decimals), { type: 'float', digits: [69, decimals]});
} else {
this.quantity = round_pr(quant, 1);
this.quantityStr = this.quantity.toFixed(0);
diff --git a/addons/portal_sale/portal_sale_view.xml b/addons/portal_sale/portal_sale_view.xml
index 8600d9e4062..825d2349f5a 100644
--- a/addons/portal_sale/portal_sale_view.xml
+++ b/addons/portal_sale/portal_sale_view.xml
@@ -20,6 +20,9 @@
+
+
+
diff --git a/addons/product/pricelist.py b/addons/product/pricelist.py
index ffbdfaa23b2..ab8c84f6433 100644
--- a/addons/product/pricelist.py
+++ b/addons/product/pricelist.py
@@ -324,6 +324,18 @@ class product_pricelist_item(osv.osv):
class product_pricelist_item_new(models.Model):
_inherit = "product.pricelist.item"
+ _applied_on_field_map = {
+ '0_product_variant': 'product_id',
+ '1_product': 'product_tmpl_id',
+ '2_product_category': 'categ_id',
+ }
+
+ _compute_price_field_map = {
+ 'fixed': ['fixed_price'],
+ 'percentage': ['percent_price'],
+ 'formula': ['price_discount', 'price_surcharge', 'price_round', 'price_min_margin', 'price_max_margin'],
+ }
+
@api.one
@api.depends('categ_id', 'product_tmpl_id', 'product_id', 'compute_price', 'fixed_price', \
'pricelist_id', 'percent_price', 'price_discount', 'price_surcharge')
@@ -347,3 +359,16 @@ class product_pricelist_item_new(models.Model):
#functional fields used for usability purposes
name = Fields.Char(compute='_get_pricelist_item_name_price', string='Name', multi='item_name_price', help="Explicit rule name for this pricelist line.")
price = Fields.Char(compute='_get_pricelist_item_name_price', string='Price', multi='item_name_price', help="Explicit rule name for this pricelist line.")
+
+ @api.onchange('applied_on')
+ def _onchange_applied_on(self):
+ for applied_on, field in self._applied_on_field_map.iteritems():
+ if self.applied_on != applied_on:
+ setattr(self, field, False)
+
+ @api.onchange('compute_price')
+ def _onchange_compute_price(self):
+ for compute_price, field in self._compute_price_field_map.iteritems():
+ if self.compute_price != compute_price:
+ for f in field:
+ setattr(self, f, 0.0)
diff --git a/addons/product/product.py b/addons/product/product.py
index d07184ef5dd..465f83d342f 100644
--- a/addons/product/product.py
+++ b/addons/product/product.py
@@ -894,7 +894,12 @@ class product_product(osv.osv):
def _get_image_variant(self, cr, uid, ids, name, args, context=None):
result = dict.fromkeys(ids, False)
for obj in self.browse(cr, uid, ids, context=context):
- result[obj.id] = obj.image_variant or getattr(obj.product_tmpl_id, name)
+ if context.get('bin_size'):
+ result[obj.id] = obj.image_variant
+ else:
+ result[obj.id] = tools.image_get_resized_images(obj.image_variant, return_big=True, avoid_resize_medium=True)[name]
+ if not result[obj.id]:
+ result[obj.id] = getattr(obj.product_tmpl_id, name)
return result
def _set_image_variant(self, cr, uid, id, name, value, args, context=None):
diff --git a/addons/project/project.py b/addons/project/project.py
index aeee91eb948..e522703a451 100644
--- a/addons/project/project.py
+++ b/addons/project/project.py
@@ -388,7 +388,7 @@ class task(osv.osv):
project = self.pool.get('project.project').browse(cr, uid, project_id, context=context)
if project and project.partner_id:
return {'value': {'partner_id': project.partner_id.id}}
- return {'value': {'partner_id': False}}
+ return {}
def onchange_user_id(self, cr, uid, ids, user_id, context=None):
vals = {}
diff --git a/addons/project/project_data.xml b/addons/project/project_data.xml
index 729b5141d01..03285974081 100644
--- a/addons/project/project_data.xml
+++ b/addons/project/project_data.xml
@@ -296,7 +296,7 @@
You can set a deadline on a task
-
+
diff --git a/addons/purchase/purchase.py b/addons/purchase/purchase.py
index da13310e77b..bc91dfbb54b 100644
--- a/addons/purchase/purchase.py
+++ b/addons/purchase/purchase.py
@@ -149,6 +149,8 @@ class PurchaseOrder(models.Model):
picking_type_id = fields.Many2one('stock.picking.type', 'Deliver To', states=READONLY_STATES, required=True, default=_default_picking_type,\
help="This will determine picking type of incoming shipment")
+ default_location_dest_id_usage = fields.Selection(related='picking_type_id.default_location_dest_id.usage', string='Destination Location Type',\
+ help="Technical field used to display the Drop Ship Address")
group_id = fields.Many2one('procurement.group', string="Procurement Group")
@api.model
@@ -207,6 +209,11 @@ class PurchaseOrder(models.Model):
self.currency_id = self.partner_id.property_purchase_currency_id.id or self.env.user.company_id.currency_id.id
return {}
+ @api.onchange('picking_type_id')
+ def _onchange_picking_type_id(self):
+ if self.picking_type_id.default_location_dest_id.usage != 'customer':
+ self.dest_address_id = False
+
@api.multi
def action_rfq_send(self):
'''
@@ -853,6 +860,7 @@ class ProcurementOrder(models.Model):
'price_unit': price_unit,
'procurement_ids': [(4, procurement.id)]
})
+ break
if not po_line:
vals = procurement._prepare_purchase_order_line(po, supplier)
self.env['purchase.order.line'].create(vals)
diff --git a/addons/purchase/purchase_view.xml b/addons/purchase/purchase_view.xml
index 8e61ebb56df..5bbcb13720c 100644
--- a/addons/purchase/purchase_view.xml
+++ b/addons/purchase/purchase_view.xml
@@ -251,6 +251,8 @@
+
+
diff --git a/addons/sale/res_config_view.xml b/addons/sale/res_config_view.xml
index 36c6732c279..0fdffb377c4 100644
--- a/addons/sale/res_config_view.xml
+++ b/addons/sale/res_config_view.xml
@@ -34,11 +34,11 @@
-
+
-
+
diff --git a/addons/sale/sale.py b/addons/sale/sale.py
index 80d7aa94160..c603516d686 100644
--- a/addons/sale/sale.py
+++ b/addons/sale/sale.py
@@ -485,7 +485,9 @@ class SaleOrderLine(models.Model):
def _get_invoice_qty(self):
"""
Compute the quantity invoiced. If case of a refund, the quantity invoiced is decreased. Note
- that this is the case only if the refund is generated from the SO.
+ that this is the case only if the refund is generated from the SO and that is intentional: if
+ a refund made would automatically decrease the invoiced quantity, then there is a risk of reinvoicing
+ it automatically, which may not be wanted at all. That's why the refund has to be created from the SO
"""
for line in self:
qty_invoiced = 0.0
diff --git a/addons/sale/sale_view.xml b/addons/sale/sale_view.xml
index 1f3c71b01dc..91fcb915bc1 100644
--- a/addons/sale/sale_view.xml
+++ b/addons/sale/sale_view.xml
@@ -167,7 +167,7 @@
-
+
@@ -361,7 +361,7 @@
-
+
diff --git a/addons/sale/wizard/sale_make_invoice_advance.xml b/addons/sale/wizard/sale_make_invoice_advance.xml
index 1425990d52c..397e14ad3ee 100644
--- a/addons/sale/wizard/sale_make_invoice_advance.xml
+++ b/addons/sale/wizard/sale_make_invoice_advance.xml
@@ -47,6 +47,7 @@
formformnew
+
diff --git a/addons/sale_layout/models/sale_layout.py b/addons/sale_layout/models/sale_layout.py
index 7d282c15ce2..a0867007fa8 100644
--- a/addons/sale_layout/models/sale_layout.py
+++ b/addons/sale_layout/models/sale_layout.py
@@ -67,10 +67,6 @@ class AccountInvoiceLine(osv.Model):
sale_layout_cat_id = openerp.fields.Many2one('sale_layout.category', string='Section')
categ_sequence = openerp.fields.Integer(related='sale_layout_cat_id.sequence',
string='Layout Sequence', store=True)
- _defaults = {
- 'categ_sequence': 0
- }
-
class SaleOrder(osv.Model):
_inherit = 'sale.order'
@@ -100,10 +96,6 @@ class SaleOrderLine(osv.Model):
# Store is intentionally set in order to keep the "historic" order.
}
- _defaults = {
- 'categ_sequence': 0
- }
-
_order = 'order_id, categ_sequence, sale_layout_cat_id, sequence, id'
def _prepare_order_line_invoice_line(self, cr, uid, line, account_id=False, context=None):
diff --git a/addons/sale_stock/sale_stock.py b/addons/sale_stock/sale_stock.py
index 72bf6b0843d..10cd705b253 100644
--- a/addons/sale_stock/sale_stock.py
+++ b/addons/sale_stock/sale_stock.py
@@ -134,7 +134,7 @@ class SaleOrderLine(models.Model):
@api.onchange('product_uom_qty')
def _onchange_product_uom_qty(self):
- if self.state == 'sale' and self.product_id.type != 'service' and self.product_uom_qty < self._origin.product_uom_qty:
+ if self.state == 'sale' and self.product_id.type in ['product', 'consu'] and self.product_uom_qty < self._origin.product_uom_qty:
warning_mess = {
'title': _('Ordered quantity decreased!'),
'message' : _('You are decreasing the ordered quantity! Do not forget to manually update the delivery order if needed.'),
@@ -159,10 +159,14 @@ class SaleOrderLine(models.Model):
@api.multi
def _get_delivered_qty(self):
+ """Computes the delivered quantity on sale order lines, based on done stock moves related to its procurements
+ """
self.ensure_one()
super(SaleOrderLine, self)._get_delivered_qty()
qty = 0.0
for move in self.procurement_ids.mapped('move_ids').filtered(lambda r: r.state == 'done' and not r.scrapped):
+ #Note that we don't decrease quantity for customer returns on purpose: these are exeptions that must be treated manually. Indeed,
+ #modifying automatically the delivered quantity may trigger an automatic reinvoicing (refund) of the SO, which is definitively not wanted
if move.location_dest_id.usage == "customer":
qty += self.env['product.uom']._compute_qty_obj(move.product_uom, move.product_uom_qty, self.product_uom)
return qty
diff --git a/addons/stock/stock.py b/addons/stock/stock.py
index 17db57dc25c..d74db0b5af4 100644
--- a/addons/stock/stock.py
+++ b/addons/stock/stock.py
@@ -1920,7 +1920,7 @@ class stock_move(osv.osv):
auto_join=True, help="Location where the system will stock the finished products."),
'partner_id': fields.many2one('res.partner', 'Destination Address ', states={'done': [('readonly', True)]}, help="Optional address where goods are to be delivered, specifically used for allotment"),
-
+ 'picking_partner_id': fields.related('picking_id', 'partner_id', type='many2one', relation='res.partner', string='Transfer Destination Address'),
'move_dest_id': fields.many2one('stock.move', 'Destination Move', help="Optional: next stock move when chaining them", select=True, copy=False),
'move_orig_ids': fields.one2many('stock.move', 'move_dest_id', 'Original Move', help="Optional: previous stock move when chaining them", select=True),
diff --git a/addons/stock/stock_view.xml b/addons/stock/stock_view.xml
index 36b5301613e..4ac81da397f 100644
--- a/addons/stock/stock_view.xml
+++ b/addons/stock/stock_view.xml
@@ -1338,6 +1338,7 @@
+
diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py
index b2f35500ab4..3d68c05c2e0 100644
--- a/addons/web/controllers/main.py
+++ b/addons/web/controllers/main.py
@@ -1411,7 +1411,7 @@ class ExportFormat(object):
Model = request.session.model(model)
context = dict(request.context or {}, **params.get('context', {}))
- ids = ids or Model.search(domain, 0, False, False, context)
+ ids = ids or Model.search(domain, 0, False, False, context=context)
if not request.env[model]._is_an_ordinary_table():
fields = [field for field in fields if field['name'] != 'id']
diff --git a/addons/web/static/src/js/views/form_common.js b/addons/web/static/src/js/views/form_common.js
index aaa39b225f6..80342f42ee8 100644
--- a/addons/web/static/src/js/views/form_common.js
+++ b/addons/web/static/src/js/views/form_common.js
@@ -992,8 +992,8 @@ var SelectCreateDialog = ViewDialog.extend({
this.searchview.destroy();
}
var $header = $('').addClass('o_modal_header').appendTo(this.$el);
- var $buttons = $('').addClass('o_search_options').appendTo($header);
var $pager = $('').addClass('o_pager').appendTo($header);
+ var $buttons = $('').addClass('o_search_options').appendTo($header);
this.searchview = new SearchView(this, this.dataset, false, search_defaults, {$buttons: $buttons});
this.searchview.on('search_data', self, function(domains, contexts, groupbys) {
if (self.initial_ids) {
diff --git a/addons/web/static/src/js/views/list_view.js b/addons/web/static/src/js/views/list_view.js
index 86ae1790632..cb6daf093ce 100644
--- a/addons/web/static/src/js/views/list_view.js
+++ b/addons/web/static/src/js/views/list_view.js
@@ -1177,7 +1177,7 @@ ListView.List = Class.extend( /** @lends instance.web.ListView.List# */{
ids = value;
}
new Model(column.relation)
- .call('name_get', [ids, this.dataset.context]).done(function (names) {
+ .call('name_get', [ids, this.dataset.get_context()]).done(function (names) {
// FIXME: nth horrible hack in this poor listview
record.set(column.id + '__display',
_(names).pluck(1).join(', '));
diff --git a/addons/web/static/src/less/modal.less b/addons/web/static/src/less/modal.less
index 6635dbaf1f5..9ff0453c4dd 100644
--- a/addons/web/static/src/less/modal.less
+++ b/addons/web/static/src/less/modal.less
@@ -10,6 +10,7 @@
.o_modal_header {
.o-webclient-padding(@top: 10px, @bottom: 10px);
+ .clearfix();
.o_search_options {
display: inline-block;
diff --git a/addons/web_editor/static/src/js/backend.js b/addons/web_editor/static/src/js/backend.js
index e7c6d93853d..2c7c86466c1 100644
--- a/addons/web_editor/static/src/js/backend.js
+++ b/addons/web_editor/static/src/js/backend.js
@@ -122,8 +122,8 @@ var FieldTextHtmlSimple = widget.extend({
if (this.options['style-inline']) {
transcoder.class_to_style(this.$content);
transcoder.font_to_img(this.$content);
- this.internal_set_value(this.$content.html());
}
+ this.internal_set_value(this.$content.html());
},
destroy_content: function () {
$(".oe-view-manager-content").off("scroll");
diff --git a/addons/web_editor/static/src/js/summernote.js b/addons/web_editor/static/src/js/summernote.js
index c5a9b3ebbef..ff509800c50 100644
--- a/addons/web_editor/static/src/js/summernote.js
+++ b/addons/web_editor/static/src/js/summernote.js
@@ -2320,6 +2320,12 @@ function summernote_paste (event) {
var html = clipboardData.getData("text/html");
var $node = $('').html(html);
+ // if copying source did not provide html, default to plain text
+ if(!html) {
+ $node.text(clipboardData.getData("text/plain")).html(function(_, html){
+ return html.replace(/\r?\n/g,' ');
+ });
+ }
/*
remove undesirable tag
diff --git a/addons/web_settings_dashboard/static/src/xml/dashboard.xml b/addons/web_settings_dashboard/static/src/xml/dashboard.xml
index 50c50e4a8eb..5f9f9eee309 100644
--- a/addons/web_settings_dashboard/static/src/xml/dashboard.xml
+++ b/addons/web_settings_dashboard/static/src/xml/dashboard.xml
@@ -25,6 +25,7 @@
+ Installed App
diff --git a/addons/web_view_editor/static/src/js/view_editor.js b/addons/web_view_editor/static/src/js/view_editor.js
index 29d4b642920..350a02441f6 100644
--- a/addons/web_view_editor/static/src/js/view_editor.js
+++ b/addons/web_view_editor/static/src/js/view_editor.js
@@ -1032,7 +1032,7 @@ var ViewEditor = Widget.extend({
render_new_field :function( result ) {
var self = this;
var action = {
- context: {'default_model_id': result.id, 'manual': true, 'module' : result.model},
+ context: {'default_model_id': result.id, 'module' : result.model},
res_model: "ir.model.fields",
views: [[false, 'form']],
type: 'ir.actions.act_window',
diff --git a/addons/website/controllers/main.py b/addons/website/controllers/main.py
index c274a14ac8d..182d05c3c51 100644
--- a/addons/website/controllers/main.py
+++ b/addons/website/controllers/main.py
@@ -38,9 +38,9 @@ class Website(openerp.addons.web.controllers.main.Home):
else:
first_menu = main_menu.child_id and main_menu.child_id[0]
if first_menu:
- if not (first_menu.url.startswith(('/page/', '/?', '/#')) or (first_menu.url=='/')):
+ if first_menu.url and (not (first_menu.url.startswith(('/page/', '/?', '/#')) or (first_menu.url == '/'))):
return request.redirect(first_menu.url)
- if first_menu.url.startswith('/page/'):
+ if first_menu.url and first_menu.url.startswith('/page/'):
return request.registry['ir.http'].reroute(first_menu.url)
return self.page(page)
diff --git a/addons/website/views/website_templates.xml b/addons/website/views/website_templates.xml
index ea5b4618f4c..2fb0c1c89a9 100644
--- a/addons/website/views/website_templates.xml
+++ b/addons/website/views/website_templates.xml
@@ -6,7 +6,7 @@
-
+
diff --git a/addons/website_crm/data/website_crm_data.xml b/addons/website_crm/data/website_crm_data.xml
index e74672c71b1..5c454fc942c 100644
--- a/addons/website_crm/data/website_crm_data.xml
+++ b/addons/website_crm/data/website_crm_data.xml
@@ -1,12 +1,24 @@
-
+
TrueCreate a lead
-
+
+ crm.lead
+
+
+
+
Website Contact Formself
diff --git a/addons/website_form/controllers/main.py b/addons/website_form/controllers/main.py
index f7f5f0d07ef..e5cb84823d0 100644
--- a/addons/website_form/controllers/main.py
+++ b/addons/website_form/controllers/main.py
@@ -15,13 +15,11 @@ class WebsiteForm(http.Controller):
@http.route('/website_form/', type='http', auth="public", methods=['POST'], website=True)
def website_form(self, model_name, **kwargs):
model_record = request.env['ir.model'].search([('model', '=', model_name), ('website_form_access', '=', True)])
-
if not model_record:
return json.dumps(False)
try:
data = self.extract_data(model_record, ** kwargs)
-
# If we encounter an issue while extracting data
except ValidationError, e:
# I couldn't find a cleaner way to pass data to an exception
@@ -96,7 +94,7 @@ class WebsiteForm(http.Controller):
'custom': '', # Custom fields values
}
- authorized_fields = model.sudo().get_authorized_fields();
+ authorized_fields = model.sudo()._get_form_writable_fields()
error_fields = []
for field_name, field_value in kwargs.items():
@@ -183,7 +181,7 @@ class WebsiteForm(http.Controller):
def insert_attachment(self, model, id_record, files):
orphan_attachment_ids = []
record = model.env[model.model].browse(id_record)
- authorized_fields = model.sudo().get_authorized_fields()
+ authorized_fields = model.sudo()._get_form_writable_fields()
for file in files:
custom_field = file.field_name not in authorized_fields
attachment_value = {
diff --git a/addons/website_form/models/models.py b/addons/website_form/models/models.py
index 24d308bd64b..9a6799b6e5b 100644
--- a/addons/website_form/models/models.py
+++ b/addons/website_form/models/models.py
@@ -1,3 +1,5 @@
+import itertools
+
from openerp import tools
from openerp import models, fields, api
@@ -16,18 +18,36 @@ class website_form_model(models.Model):
website_form_default_field_id = fields.Many2one('ir.model.fields', 'Field for custom form data', domain="[('model', '=', model), ('ttype', '=', 'text')]", help="Specify the field which will contain meta and custom form fields datas.")
website_form_label = fields.Char("Label for form action", help="Form action label. Ex: crm.lead could be 'Send an e-mail' and project.issue could be 'Create an Issue'.")
+ def _all_inherited_model_ids(self):
+ return list(itertools.chain(
+ [self.id],
+ *(m._all_inherited_model_ids() for m in self.inherited_model_ids)
+ ))
- def all_inherited_model_ids(self):
- return [self.id] + [m.all_inherited_model_ids() for m in self.inherited_model_ids]
+ def _get_form_writable_fields(self):
+ """
+ Restriction of "authorized fields" (fields which can be used in the
+ form builders) to fields which have actually been opted into form
+ builders and are writable. By default no field is writable by the
+ form builder.
+ """
+ excluded = {
+ field.name
+ for field in self.env['ir.model.fields'].sudo().search([
+ ('model_id', 'in', self._all_inherited_model_ids()),
+ ('website_form_blacklisted', '=', True)
+ ])
+ }
+ return {
+ k: v for k, v in self.get_authorized_fields().iteritems()
+ if k not in excluded
+ }
@api.multi
def get_authorized_fields(self):
model = self.env[self.model]
fields_get = model.fields_get()
- for elem in self.env['ir.model.fields'].search([('model_id', 'in', self.all_inherited_model_ids())]):
- if elem.website_form_blacklisted:
- fields_get.pop(elem.name, None)
for key, val in model._inherits.iteritems():
fields_get.pop(val,None)
@@ -49,4 +69,44 @@ class website_form_model_fields(models.Model):
_name = 'ir.model.fields'
_inherit = 'ir.model.fields'
- website_form_blacklisted = fields.Boolean('Blacklisted in web forms', help='Blacklist this field for web forms')
+ def init(self, cr):
+ # set all existing unset website_form_blacklisted fields to ``true``
+ # (so that we can use it as a whitelist rather than a blacklist)
+ cr.execute('UPDATE ir_model_fields'
+ ' SET website_form_blacklisted=true'
+ ' WHERE website_form_blacklisted IS NULL')
+ # add an SQL-level default value on website_form_blacklisted to that
+ # pure-SQL ir.model.field creations (e.g. in _field_create) generate
+ # the right default value for a whitelist (aka fields should be
+ # blacklisted by default)
+ cr.execute('ALTER TABLE ir_model_fields '
+ ' ALTER COLUMN website_form_blacklisted SET DEFAULT true')
+
+ @api.model
+ def formbuilder_whitelist(self, model, fields):
+ """
+ :param str model: name of the model on which to whitelist fields
+ :param list(str) fields: list of fields to whitelist on the model
+ :return: nothing of import
+ """
+ # postgres does *not* like ``in [EMPTY TUPLE]`` queries
+ if not fields: return False
+
+ # only allow users who can change the website structure
+ if not self.env['res.users'].has_group('base.group_website_designer'):
+ return False
+
+ # the ORM only allows writing on custom fields and will trigger a
+ # registry reload once that's happened. We want to be able to
+ # whitelist non-custom fields and the registry reload absolutely
+ # isn't desirable, so go with a method and raw SQL
+ self.env.cr.execute(
+ "UPDATE ir_model_fields"
+ " SET website_form_blacklisted=false"
+ " WHERE model=%s AND name in %s", (model, tuple(fields)))
+ return True
+
+ website_form_blacklisted = fields.Boolean(
+ 'Blacklisted in web forms', default=True, select=True, # required=True,
+ help='Blacklist this field for web forms'
+ )
diff --git a/addons/website_forum/controllers/main.py b/addons/website_forum/controllers/main.py
index 4dc59d118a3..1f982328db6 100644
--- a/addons/website_forum/controllers/main.py
+++ b/addons/website_forum/controllers/main.py
@@ -384,7 +384,7 @@ class WebsiteForum(http.Controller):
def post_save(self, forum, post, **kwargs):
if 'post_name' in kwargs and not kwargs.get('post_name').strip():
return request.website.render('website.http_error', {'status_code': _('Bad Request'), 'status_message': _('Title should not be empty.')})
- post_tags = forum._tag_to_write_vals(kwargs.get('post_tag', ''))
+ post_tags = forum._tag_to_write_vals(kwargs.get('post_tags', ''))
vals = {
'tag_ids': post_tags,
'name': kwargs.get('post_name'),
diff --git a/addons/website_forum/i18n/website_forum.pot b/addons/website_forum/i18n/website_forum.pot
index 8196f8ae988..85a5822230e 100644
--- a/addons/website_forum/i18n/website_forum.pot
+++ b/addons/website_forum/i18n/website_forum.pot
@@ -4,10 +4,10 @@
#
msgid ""
msgstr ""
-"Project-Id-Version: Odoo Server 9.0\n"
+"Project-Id-Version: Odoo Server 9.0c\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2015-10-09 09:20+0000\n"
-"PO-Revision-Date: 2015-10-09 09:20+0000\n"
+"POT-Creation-Date: 2015-11-26 14:10+0000\n"
+"PO-Revision-Date: 2015-11-26 14:10+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
@@ -222,13 +222,19 @@ msgid "awarded users"
msgstr ""
#. module: website_forum
-#: code:addons/website_forum/models/forum.py:419
+#: code:addons/website_forum/models/forum.py:483
#, python-format
msgid "
diff --git a/addons/website_hr_recruitment/data/config_data.xml b/addons/website_hr_recruitment/data/config_data.xml
index ee934bb3ca4..24e4ed8b30f 100644
--- a/addons/website_hr_recruitment/data/config_data.xml
+++ b/addons/website_hr_recruitment/data/config_data.xml
@@ -16,10 +16,23 @@
open
+
+
TrueApply for a Job
+
+ hr.applicant
+
+
diff --git a/addons/website_issue/data/website_issue_data.xml b/addons/website_issue/data/website_issue_data.xml
index a588449d30d..83f65b516e1 100644
--- a/addons/website_issue/data/website_issue_data.xml
+++ b/addons/website_issue/data/website_issue_data.xml
@@ -1,15 +1,10 @@
-
+
TrueCreate an issue
-
diff --git a/addons/website_mass_mailing/static/src/js/website_mass_mailing.editor.js b/addons/website_mass_mailing/static/src/js/website_mass_mailing.editor.js
index e5ff84677c7..10f5771139f 100644
--- a/addons/website_mass_mailing/static/src/js/website_mass_mailing.editor.js
+++ b/addons/website_mass_mailing/static/src/js/website_mass_mailing.editor.js
@@ -68,9 +68,9 @@ options.registry.newsletter_popup = mass_mailing_common.extend({
});
web_editor.Class.include({
- edit: function () {
- this._super();
+ start: function () {
$('body').on('click','#edit_dialog',_.bind(this.edit_dialog, this.rte.editor));
+ return this._super();
},
save : function() {
var $target = $('#wrapwrap').find('#o_newsletter_popup');
diff --git a/addons/website_quote/static/src/js/website_quotation.js b/addons/website_quote/static/src/js/website_quotation.js
index 2539dc79365..f4e17c92bed 100644
--- a/addons/website_quote/static/src/js/website_quotation.js
+++ b/addons/website_quote/static/src/js/website_quotation.js
@@ -53,7 +53,7 @@ if(!$('.o_website_quote').length) {
'submit #accept': 'submitForm',
},
initSignature: function(ev){
- this.$("#signature").empty().jSignature({'decor-color' : '#D1D0CE'});
+ this.$("#signature").empty().jSignature({'decor-color' : '#D1D0CE', 'color': '#000', 'background-color': '#fff'});
this.empty_sign = this.$("#signature").jSignature("getData",'image');
},
clearSignature: function(ev){
diff --git a/addons/website_sale/static/src/js/website_sale.js b/addons/website_sale/static/src/js/website_sale.js
index 676ccba1e2f..c316f69d53c 100644
--- a/addons/website_sale/static/src/js/website_sale.js
+++ b/addons/website_sale/static/src/js/website_sale.js
@@ -146,9 +146,9 @@ $('.oe_website_sale').each(function () {
clickwatch(function(){
$dom_optional.each(function(){
+ $(this).find('.js_quantity').text(value);
product_ids.push($(this).find('span[data-product-id]').data('product-id'));
});
- if (isNaN(value)) value = 0;
$input.data('update_change', true);
ajax.jsonRpc("/shop/cart/update_json", 'call', {
diff --git a/addons/website_sale_stock/views/website_sale_stock.xml b/addons/website_sale_stock/views/website_sale_stock.xml
index b624c72bf72..32a1729123b 100644
--- a/addons/website_sale_stock/views/website_sale_stock.xml
+++ b/addons/website_sale_stock/views/website_sale_stock.xml
@@ -31,4 +31,9 @@
+
+
+ o.move_lines.sudo()
+
+
\ No newline at end of file
diff --git a/doc/cla/corporate/invitu.md b/doc/cla/corporate/invitu.md
new file mode 100644
index 00000000000..3b15373c298
--- /dev/null
+++ b/doc/cla/corporate/invitu.md
@@ -0,0 +1,15 @@
+French Polynesia (Tahiti), 2015-10-22
+
+Invitu agrees to the terms of the Odoo Corporate Contributor License
+Agreement v1.0.
+
+I declare that I am authorized and able to make this agreement and sign this
+declaration.
+
+Signed,
+
+Cyril Vinh-Tung contact@invitu.com https://github.com/invitu
+
+List of contributors:
+Cyril Vinh-Tung contact@invitu.com https://github.com/invitu
+
diff --git a/openerp/addons/base/i18n/base.pot b/openerp/addons/base/i18n/base.pot
index d18729cee4d..00f745ab7c2 100644
--- a/openerp/addons/base/i18n/base.pot
+++ b/openerp/addons/base/i18n/base.pot
@@ -16749,7 +16749,7 @@ msgstr ""
#. module: base
#: model:ir.module.module,shortdesc:base.module_payment_sips
-msgid "Worldline SIPS Payment Acquiring for online payments"
+msgid "Worldline SIPS"
msgstr ""
#. module: base
diff --git a/openerp/addons/base/ir/ir_model.py b/openerp/addons/base/ir/ir_model.py
index 306e9240c03..b2438d4ba2d 100644
--- a/openerp/addons/base/ir/ir_model.py
+++ b/openerp/addons/base/ir/ir_model.py
@@ -93,7 +93,7 @@ class ir_model(osv.osv):
_defaults = {
'model': 'x_',
- 'state': lambda self,cr,uid,ctx=None: (ctx and ctx.get('manual',False)) and 'manual' or 'base',
+ 'state': 'manual',
}
def _check_model_name(self, cr, uid, ids, context=None):
@@ -177,10 +177,8 @@ class ir_model(osv.osv):
def create(self, cr, user, vals, context=None):
if context is None:
context = {}
- if context and context.get('manual'):
- vals['state']='manual'
res = super(ir_model,self).create(cr, user, vals, context)
- if vals.get('state','base')=='manual':
+ if vals.get('state','manual')=='manual':
# setup models; this automatically adds model in registry
self.pool.setup_models(cr, partial=(not self.pool.ready))
# update database schema
@@ -264,7 +262,7 @@ class ir_model_fields(osv.osv):
'selection': "",
'domain': "[]",
'name': 'x_',
- 'state': lambda self,cr,uid,ctx=None: (ctx and ctx.get('manual',False)) and 'manual' or 'base',
+ 'state': 'manual',
'on_delete': 'set null',
'field_description': '',
'selectable': 1,
@@ -444,14 +442,12 @@ class ir_model_fields(osv.osv):
vals['model'] = model_data.model
if context is None:
context = {}
- if context and context.get('manual',False):
- vals['state'] = 'manual'
if vals.get('ttype', False) == 'selection':
if not vals.get('selection',False):
raise UserError(_('For selection fields, the Selection Options must be given!'))
self._check_selection(cr, user, vals['selection'], context=context)
res = super(ir_model_fields,self).create(cr, user, vals, context)
- if vals.get('state','base') == 'manual':
+ if vals.get('state','manual') == 'manual':
if not vals['name'].startswith('x_'):
raise UserError(_("Custom fields must have a name that starts with 'x_' !"))
@@ -479,8 +475,6 @@ class ir_model_fields(osv.osv):
def write(self, cr, user, ids, vals, context=None):
if context is None:
context = {}
- if context and context.get('manual',False):
- vals['state'] = 'manual'
#For the moment renaming a sparse field or changing the storing system is not allowed. This may be done later
if 'serialization_field_id' in vals or 'name' in vals:
@@ -523,7 +517,7 @@ class ir_model_fields(osv.osv):
raise UserError(_('Can only rename one field at a time!'))
if vals['name'] in obj._fields:
raise UserError(_('Cannot rename field to %s, because that field already exists!') % vals['name'])
- if vals.get('state', 'base') == 'manual' and not vals['name'].startswith('x_'):
+ if vals.get('state', 'manual') == 'manual' and not vals['name'].startswith('x_'):
raise UserError(_('New field name must still start with x_ , because it is a custom field!'))
if '\'' in vals['name'] or '"' in vals['name'] or ';' in vals['name']:
raise ValueError('Invalid character in column name')
diff --git a/openerp/addons/base/ir/ir_model_view.xml b/openerp/addons/base/ir/ir_model_view.xml
index 904ba7b4f5c..fce2edad237 100644
--- a/openerp/addons/base/ir/ir_model_view.xml
+++ b/openerp/addons/base/ir/ir_model_view.xml
@@ -41,7 +41,7 @@
-
+
@@ -203,7 +203,7 @@
Modelsir.modelform
- {'manual':True}
+ {}
@@ -351,7 +351,7 @@
Fieldsir.model.fieldsform
- {'manual':True}
+ {}
diff --git a/openerp/addons/base/ir/ir_translation.py b/openerp/addons/base/ir/ir_translation.py
index b395d327768..2ce4570329b 100644
--- a/openerp/addons/base/ir/ir_translation.py
+++ b/openerp/addons/base/ir/ir_translation.py
@@ -130,11 +130,13 @@ class ir_translation_import_cursor(object):
AND irt.type = ti.type
AND irt.module = ti.module
AND irt.name = ti.name
- AND (ti.type IN ('field', 'help') OR irt.src = ti.src)
- AND ( ti.type NOT IN ('model', 'view')
- OR (ti.type = 'model' AND ti.res_id = irt.res_id)
- OR (ti.type = 'view' AND (irt.res_id IS NULL OR ti.res_id = irt.res_id))
- )
+ AND (
+ (ti.type = 'model' AND ti.res_id = irt.res_id AND irt.src = ti.src)
+ OR (ti.type = 'view' AND (irt.res_id IS NULL OR ti.res_id = irt.res_id) AND irt.src = ti.src)
+ OR (ti.type = 'field')
+ OR (ti.type = 'help')
+ OR (ti.type NOT IN ('model', 'view', 'field', 'help') AND irt.src = ti.src)
+ )
"""
# Step 2: update existing (matching) translations
@@ -536,41 +538,46 @@ class ir_translation(osv.osv):
def insert_missing(self, field, records):
""" Insert missing translations for `field` on `records`. """
records = records.with_context(lang=None)
+ external_ids = records.get_external_id() # if no xml_id, empty string
if callable(field.translate):
# insert missing translations for each term in src
- query = """ INSERT INTO ir_translation (lang, type, name, res_id, src, value)
- SELECT l.code, 'model', %(name)s, %(res_id)s, %(src)s, %(src)s
+ query = """ INSERT INTO ir_translation (lang, type, name, res_id, src, value, module)
+ SELECT l.code, 'model', %(name)s, %(res_id)s, %(src)s, %(src)s, %(module)s
FROM res_lang l
WHERE l.active AND NOT EXISTS (
SELECT 1 FROM ir_translation
- WHERE lang=l.code AND type='model' AND name=%(name)s AND res_id=%(res_id)s AND src=%(src)s
+ WHERE lang=l.code AND type='model' AND name=%(name)s AND res_id=%(res_id)s AND src=%(src)s AND module=%(module)s
);
"""
for record in records:
+ module = external_ids[record.id].split('.')[0]
src = record[field.name] or None
for term in set(field.get_trans_terms(src)):
self._cr.execute(query, {
'name': "%s,%s" % (field.model_name, field.name),
'res_id': record.id,
'src': term,
+ 'module': module
})
else:
# insert missing translations for src
- query = """ INSERT INTO ir_translation (lang, type, name, res_id, src, value)
- SELECT l.code, 'model', %(name)s, %(res_id)s, %(src)s, %(src)s
+ query = """ INSERT INTO ir_translation (lang, type, name, res_id, src, value, module)
+ SELECT l.code, 'model', %(name)s, %(res_id)s, %(src)s, %(src)s, %(module)s
FROM res_lang l
WHERE l.active AND l.code != 'en_US' AND NOT EXISTS (
SELECT 1 FROM ir_translation
- WHERE lang=l.code AND type='model' AND name=%(name)s AND res_id=%(res_id)s
+ WHERE lang=l.code AND type='model' AND name=%(name)s AND res_id=%(res_id)s AND module=%(module)s
);
UPDATE ir_translation SET src=%(src)s
- WHERE type='model' AND name=%(name)s AND res_id=%(res_id)s;
+ WHERE type='model' AND name=%(name)s AND res_id=%(res_id)s AND module=%(module)s;
"""
for record in records:
+ module = external_ids[record.id].split('.')[0]
self._cr.execute(query, {
'name': "%s,%s" % (field.model_name, field.name),
'res_id': record.id,
'src': record[field.name] or None,
+ 'module': module
})
self.clear_caches()
diff --git a/openerp/addons/base/res/res_partner.py b/openerp/addons/base/res/res_partner.py
index b0567b0002a..7399bd83ea6 100644
--- a/openerp/addons/base/res/res_partner.py
+++ b/openerp/addons/base/res/res_partner.py
@@ -259,7 +259,7 @@ class res_partner(osv.Model, format_address):
'use_parent_address': fields.boolean('Use Company Address', help="Select this if you want to set company's address information for this contact"),
'company_id': fields.many2one('res.company', 'Company', select=1),
'color': fields.integer('Color Index'),
- 'user_ids': fields.one2many('res.users', 'partner_id', 'Users'),
+ 'user_ids': fields.one2many('res.users', 'partner_id', 'Users', auto_join=True),
'contact_address': fields.function(_address_display, type='char', string='Complete Address'),
# technical field used for managing commercial fields
diff --git a/openerp/addons/base/tests/test_translate.py b/openerp/addons/base/tests/test_translate.py
index 8aab490e0fa..d6944cd24dd 100644
--- a/openerp/addons/base/tests/test_translate.py
+++ b/openerp/addons/base/tests/test_translate.py
@@ -119,7 +119,7 @@ class TranslationToolsTestCase(unittest.TestCase):
terms = []
source = """
stuff before
-
Do not translate this
+
Do not translate this
stuff after
"""
result = xml_translate(terms.append, source)
diff --git a/openerp/cli/server.py b/openerp/cli/server.py
index 5794855cf02..98e8359d76b 100644
--- a/openerp/cli/server.py
+++ b/openerp/cli/server.py
@@ -61,9 +61,9 @@ def report_configuration():
user = config['db_user'] or os.environ.get('PGUSER', 'default')
_logger.info('database: %s@%s:%s', user, host, port)
-def rm_pid_file():
+def rm_pid_file(main_pid):
config = openerp.tools.config
- if not openerp.evented and config['pidfile']:
+ if config['pidfile'] and main_pid == os.getpid():
try:
os.unlink(config['pidfile'])
except OSError:
@@ -76,10 +76,10 @@ def setup_pid_file():
"""
config = openerp.tools.config
if not openerp.evented and config['pidfile']:
+ pid = os.getpid()
with open(config['pidfile'], 'w') as fd:
- pidtext = "%d" % (os.getpid())
- fd.write(pidtext)
- atexit.register(rm_pid_file)
+ fd.write(str(pid))
+ atexit.register(rm_pid_file, pid)
def export_translation():
config = openerp.tools.config
diff --git a/openerp/http.py b/openerp/http.py
index bf5367ff927..f0c8982a712 100644
--- a/openerp/http.py
+++ b/openerp/http.py
@@ -305,6 +305,7 @@ class WebRequest(object):
# case, the request cursor is unusable. Rollback transaction to create a new one.
if self._cr:
self._cr.rollback()
+ self.env.clear()
result = self.endpoint(*a, **kw)
if isinstance(result, Response) and result.is_qweb:
# Early rendering of lazy responses to benefit from @service_model.check protection
diff --git a/openerp/tools/translate.py b/openerp/tools/translate.py
index 7a5f6452fa2..2ee09b12982 100644
--- a/openerp/tools/translate.py
+++ b/openerp/tools/translate.py
@@ -215,7 +215,7 @@ class XMLTranslator(object):
if (
isinstance(node, SKIPPED_ELEMENT_TYPES) or
node.tag in SKIPPED_ELEMENTS or
- node.get("translation", "").strip() == "off" or
+ node.get("t-translation", "").strip() == "off" or
node.tag == "attribute" and node.get("name") not in TRANSLATED_ATTRS
):
# do not translate the contents of the node