-
-
-
-
-
-
-
diff --git a/addons/crm/static/description/index.html b/addons/crm/static/description/index.html
index df8b9a447f8..6f4ab397d69 100644
--- a/addons/crm/static/description/index.html
+++ b/addons/crm/static/description/index.html
@@ -43,7 +43,7 @@
Automate activities based on your sales scripts: calls, meetings, mails and quotations. Get all information you need, right on the opportunity: page viewed, mails received, etc.
-
+
diff --git a/addons/hr_timesheet_attendance/report/hr_timesheet_attendance_report.py b/addons/hr_timesheet_attendance/report/hr_timesheet_attendance_report.py
index 582926ab89a..a0765b0d5cf 100644
--- a/addons/hr_timesheet_attendance/report/hr_timesheet_attendance_report.py
+++ b/addons/hr_timesheet_attendance/report/hr_timesheet_attendance_report.py
@@ -42,6 +42,7 @@ class TimesheetAttendance(models.Model):
ts.unit_amount AS timesheet,
date_trunc('day', ts.date) AS date
FROM account_analytic_line AS ts
+ WHERE ts.project_id IS NOT NULL
) AS t
GROUP BY t.user_id, t.date
ORDER BY t.date
diff --git a/addons/hr_timesheet_attendance/views/hr_timesheet_sheet_views.xml b/addons/hr_timesheet_attendance/views/hr_timesheet_sheet_views.xml
index 7aba1d01c33..b465567946c 100644
--- a/addons/hr_timesheet_attendance/views/hr_timesheet_sheet_views.xml
+++ b/addons/hr_timesheet_attendance/views/hr_timesheet_sheet_views.xml
@@ -29,6 +29,7 @@
+ [('project_id','!=',False)]
diff --git a/addons/mass_mailing/static/src/js/mass_mailing_editor.js b/addons/mass_mailing/static/src/js/mass_mailing_editor.js
index 5d475808589..cc0b9d2dd08 100644
--- a/addons/mass_mailing/static/src/js/mass_mailing_editor.js
+++ b/addons/mass_mailing/static/src/js/mass_mailing_editor.js
@@ -402,4 +402,15 @@ odoo_top[callback+"_updown"] = function (value, fields_values, field_name) {
if ($editable_area.html().indexOf('on_change_model_and_list') !== -1) {
$editable_area.empty();
}
+// Adding compatibility for the outlook compliance of mailings.
+// Commit of such compatibility : a14f89c8663c9cafecb1cc26918055e023ecbe42
+options.registry.background.include({
+ start: function() {
+ this._super();
+ var $table_target = this.$target.find('table:first');
+ if ($table_target) {
+ this.$target = $table_target;
+ }
+ }
+});
});
diff --git a/addons/mrp/models/mrp_unbuild.py b/addons/mrp/models/mrp_unbuild.py
index 5fc2f77b97f..fa4d34557d5 100644
--- a/addons/mrp/models/mrp_unbuild.py
+++ b/addons/mrp/models/mrp_unbuild.py
@@ -121,14 +121,20 @@ class MrpUnbuild(models.Model):
for produce_move in produce_moves:
if produce_move.has_tracking != 'none':
original = original_quants.filtered(lambda quant: quant.product_id == produce_move.product_id)
- if original and not original[0].lot_id:
+ if not original:
raise UserError(_("You don't have in the stock the required lot/serial number for %s .") % (produce_move.product_id.name,))
- self.env['stock.move.lots'].create({
- 'move_id': produce_move.id,
- 'lot_id': original[0].lot_id.id,
- 'quantity_done': produce_move.product_uom_qty,
- 'quantity': produce_move.product_uom_qty
- })
+ quantity_todo = produce_move.product_qty
+ for quant in original:
+ if quantity_todo <= 0:
+ break
+ move_quantity = min(quantity_todo, quant.qty)
+ self.env['stock.move.lots'].create({
+ 'move_id': produce_move.id,
+ 'lot_id': quant.lot_id.id,
+ 'quantity_done': produce_move.product_id.uom_id._compute_quantity(move_quantity, produce_move.product_uom),
+ 'quantity': produce_move.product_id.uom_id._compute_quantity(move_quantity, produce_move.product_uom),
+ })
+ quantity_todo -= move_quantity
else:
produce_move.quantity_done = produce_move.product_uom_qty
produce_moves.move_validate()
diff --git a/addons/mrp/models/stock_move.py b/addons/mrp/models/stock_move.py
index fe74f19e643..486669b9e81 100644
--- a/addons/mrp/models/stock_move.py
+++ b/addons/mrp/models/stock_move.py
@@ -138,6 +138,14 @@ class StockMove(models.Model):
self.check_move_lots()
return res
+ def _propagate_cancel(self):
+ self.ensure_one()
+ if not self.move_dest_id.raw_material_production_id:
+ super(StockMove, self)._propagate_cancel()
+ elif self.move_dest_id.state == 'waiting':
+ # If waiting, the chain will be broken and we are not sure if we can still wait for it (=> could take from stock instead)
+ self.move_dest_id.write({'state': 'confirmed'})
+
@api.multi
def action_cancel(self):
if any(move.quantity_done for move in self):
@@ -373,6 +381,10 @@ class StockMove(models.Model):
self.sudo().unlink()
return processed_moves
+ def _propagate_split(self, new_move, qty):
+ if not self.move_dest_id.raw_material_production_id:
+ super(StockMove, self)._propagate_split(new_move, qty)
+
def _generate_move_phantom(self, bom_line, quantity):
if bom_line.product_id.type in ['product', 'consu']:
return self.copy(default={
diff --git a/addons/point_of_sale/models/pos_order.py b/addons/point_of_sale/models/pos_order.py
index 18a6b436938..c954091cee6 100644
--- a/addons/point_of_sale/models/pos_order.py
+++ b/addons/point_of_sale/models/pos_order.py
@@ -625,7 +625,7 @@ class PosOrder(models.Model):
return_picking = Picking.create(return_vals)
return_picking.message_post(body=message)
- for line in order.lines.filtered(lambda l: l.product_id.type in ['product', 'consu'] and not float_is_zero(l.qty, precision_digits=l.product_id.uom_id.rounding)):
+ for line in order.lines.filtered(lambda l: l.product_id.type in ['product', 'consu'] and not float_is_zero(l.qty, precision_rounding=l.product_id.uom_id.rounding)):
moves |= Move.create({
'name': line.name,
'product_uom': line.product_id.uom_id.id,
diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js
index 336e3b32848..27281342cde 100644
--- a/addons/point_of_sale/static/src/js/models.js
+++ b/addons/point_of_sale/static/src/js/models.js
@@ -202,7 +202,7 @@ exports.PosModel = Backbone.Model.extend({
},
},{
model: 'account.tax',
- fields: ['name','amount', 'price_include', 'include_base_amount', 'amount_type', 'children_tax_ids'],
+ fields: ['name','amount', 'price_include', 'include_base_amount', 'amount_type', 'children_tax_ids', 'sequence'],
domain: null,
loaded: function(self, taxes){
self.taxes = taxes;
@@ -1400,72 +1400,138 @@ exports.Orderline = Backbone.Model.extend({
return tax;
},
- _compute_all: function(tax, base_amount, quantity) {
+ /**
+ * Mirror JS method of:
+ * _compute_amount in addons/account/models/account.py
+ */
+ _compute_all: function(tax, base_amount, quantity, price_include) {
+ if(price_include === undefined)
+ price_include = tax.price_include;
if (tax.amount_type === 'fixed') {
var sign_base_amount = base_amount >= 0 ? 1 : -1;
return (Math.abs(tax.amount) * sign_base_amount) * quantity;
}
- if ((tax.amount_type === 'percent' && !tax.price_include) || (tax.amount_type === 'division' && tax.price_include)){
+ if ((tax.amount_type === 'percent' && !price_include) || (tax.amount_type === 'division' && tax.price_include)){
return base_amount * tax.amount / 100;
}
- if (tax.amount_type === 'percent' && tax.price_include){
+ if (tax.amount_type === 'percent' && price_include){
return base_amount - (base_amount / (1 + tax.amount / 100));
}
- if (tax.amount_type === 'division' && !tax.price_include) {
+ if (tax.amount_type === 'division' && !price_include) {
return base_amount / (1 - tax.amount / 100) - base_amount;
}
return false;
},
+ /**
+ * Mirror JS method of:
+ * compute_all in addons/account/models/account.py
+ *
+ * Read comments in the python side method for more details about each sub-methods.
+ */
compute_all: function(taxes, price_unit, quantity, currency_rounding, no_map_tax) {
var self = this;
- var list_taxes = [];
+
+ // 1) Flatten the taxes.
+
+ var _collect_taxes = function(taxes, all_taxes){
+ taxes.sort(function (tax1, tax2) {
+ return tax1.sequence - tax2.sequence;
+ });
+ _(taxes).each(function(tax){
+ if(tax.amount_type === 'group')
+ all_taxes = _collect_taxes(tax.children_tax_ids, all_taxes);
+ else
+ all_taxes.push(tax);
+ });
+ return all_taxes;
+ }
+ var collect_taxes = function(taxes){
+ return _collect_taxes(taxes, []);
+ }
+
+ taxes = collect_taxes(taxes);
+
+ // 2) Avoid dealing with taxes mixing price_include=False && include_base_amount=True
+ // with price_include=True
+
+ var base_excluded_flag = false; // price_include=False && include_base_amount=True
+ var included_flag = false; // price_include=True
+ _(taxes).each(function(tax){
+ if(tax.price_include)
+ included_flag = true;
+ else if(tax.include_base_amount)
+ base_excluded_flag = true
+ if(base_excluded_flag && included_flag)
+ throw new Error('Unable to mix any taxes being price included with taxes affecting the base amount but not included in price.');
+ });
+
+ // 3) Deal with the rounding methods
+
var currency_rounding_bak = currency_rounding;
if (this.pos.company.tax_calculation_rounding_method == "round_globally"){
currency_rounding = currency_rounding * 0.00001;
}
- var total_excluded = round_pr(price_unit * quantity, currency_rounding);
- var total_included = total_excluded;
- var base = total_excluded;
- _(taxes).each(function(tax) {
- if (!no_map_tax){
- tax = self._map_tax_fiscal_position(tax);
- }
- if (!tax){
- return;
- }
- if (tax.amount_type === 'group'){
- var ret = self.compute_all(tax.children_tax_ids, price_unit, quantity, currency_rounding);
- total_excluded = ret.total_excluded;
- base = ret.total_excluded;
- total_included = ret.total_included;
- list_taxes = list_taxes.concat(ret.taxes);
- }
- else {
- var tax_amount = self._compute_all(tax, base, quantity);
- tax_amount = round_pr(tax_amount, currency_rounding);
- if (tax_amount){
- if (tax.price_include) {
- total_excluded -= tax_amount;
- base -= tax_amount;
- }
- else {
- total_included += tax_amount;
- }
- if (tax.include_base_amount) {
- base += tax_amount;
- }
- var data = {
- id: tax.id,
- amount: tax_amount,
- name: tax.name,
- };
- list_taxes.push(data);
- }
+ var recompute_base = function(base_amount, fixed_amount, percent_amount){
+ if(fixed_amount === 0.0 && percent_amount === 0.0)
+ return base_amount;
+ return round_pr((base_amount - fixed_amount) / (1.0 + percent_amount / 100.0), currency_rounding);
+ }
+
+ // 4) Iterate the taxes in the reversed sequence order to retrieve the initial base of the computation.
+
+ var base = round_pr(price_unit * quantity, currency_rounding);
+ var sign = 1;
+ if(base < 0){
+ base = -base;
+ var sign = -1;
+ }
+
+ var incl_fixed_amount = 0.0;
+ var incl_percent_amount = 0.0;
+ _(taxes.reverse()).each(function(tax){
+ if(tax.include_base_amount){
+ base = recompute_base(base, incl_fixed_amount, incl_percent_amount);
+ incl_fixed_amount = 0.0;
+ incl_percent_amount = 0.0;
+ }
+ if(tax.price_include){
+ if(tax.amount_type === 'fixed')
+ incl_fixed_amount += tax.amount;
+ else if(tax.amount_type === 'percent')
+ incl_percent_amount += tax.amount;
}
});
+
+ base = recompute_base(base, incl_fixed_amount, incl_percent_amount);
+ var total_excluded = base;
+ var total_included = base;
+
+ // 5) Iterate the taxes in the sequence order to fill missing base/amount values.
+
+ var taxes_vals = [];
+ _(taxes.reverse()).each(function(tax){
+ var tax_amount = self._compute_all(tax, base, quantity, false);
+ tax_amount = round_pr(tax_amount, currency_rounding);
+
+ var tax_base = base;
+
+ if(tax.include_base_amount)
+ base += tax_amount;
+
+ total_included += tax_amount;
+
+ var tax_vals = {
+ id: tax.id,
+ amount: sign * tax_amount,
+ name: tax.name,
+ };
+
+ taxes_vals.push(tax_vals);
+ });
+
return {
- taxes: list_taxes,
+ taxes: taxes_vals,
total_excluded: round_pr(total_excluded, currency_rounding_bak),
total_included: round_pr(total_included, currency_rounding_bak)
};
diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py
index 130613806f3..79899961997 100644
--- a/addons/sale/models/sale.py
+++ b/addons/sale/models/sale.py
@@ -864,7 +864,7 @@ class SaleOrderLine(models.Model):
return price
else:
from_currency = self.order_id.company_id.currency_id
- return from_currency.compute(product.lst_price, self.order_id.pricelist_id.currency_id)
+ return from_currency.compute(product[pricelist_item.base], self.order_id.pricelist_id.currency_id)
@api.multi
@api.onchange('product_id')
diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py
index 0455c5b95e3..30c988954d7 100644
--- a/addons/stock/models/stock_move.py
+++ b/addons/stock/models/stock_move.py
@@ -658,6 +658,15 @@ class StockMove(models.Model):
if not no_prepare:
self.check_recompute_pack_op()
+ def _propagate_cancel(self):
+ self.ensure_one()
+ if self.move_dest_id:
+ if self.propagate:
+ self.move_dest_id.action_cancel()
+ elif self.move_dest_id.state == 'waiting':
+ # If waiting, the chain will be broken and we are not sure if we can still wait for it (=> could take from stock instead)
+ self.move_dest_id.write({'state': 'confirmed'})
+
@api.multi
def action_cancel(self):
""" Cancels the moves and if all moves are cancelled it cancels the picking. """
@@ -674,12 +683,7 @@ class StockMove(models.Model):
pass
# procurements.search([('move_dest_id', '=', move.id)]).cancel()
else:
- if move.move_dest_id:
- if move.propagate:
- move.move_dest_id.action_cancel()
- elif move.move_dest_id.state == 'waiting':
- # If waiting, the chain will be broken and we are not sure if we can still wait for it (=> could take from stock instead)
- move.move_dest_id.write({'state': 'confirmed'})
+ move._propagate_cancel()
if move.procurement_id:
procurements |= move.procurement_id
@@ -919,6 +923,11 @@ class StockMove(models.Model):
raise UserError(_('You can only delete draft moves.'))
return super(StockMove, self).unlink()
+ def _propagate_split(self, new_move, qty):
+ if self.move_dest_id and self.propagate and self.move_dest_id.state not in ('done', 'cancel'):
+ new_move_prop = self.move_dest_id.split(qty)
+ new_move.write({'move_dest_id': new_move_prop})
+
@api.multi
def split(self, qty, restrict_lot_id=False, restrict_partner_id=False):
""" Splits qty from move move into a new move
@@ -959,10 +968,7 @@ class StockMove(models.Model):
# TDE CLEANME: used only in write in this file, to clean
# ctx['do_not_propagate'] = True
self.with_context(do_not_propagate=True, rounding_method='HALF-UP').write({'product_uom_qty': self.product_uom_qty - uom_qty})
-
- if self.move_dest_id and self.propagate and self.move_dest_id.state not in ('done', 'cancel'):
- new_move_prop = self.move_dest_id.split(qty)
- new_move.write({'move_dest_id': new_move_prop})
+ self._propagate_split(new_move, qty)
# returning the first element of list returned by action_confirm is ok because we checked it wouldn't be exploded (and
# thus the result of action_confirm should always be a list of 1 element length)
new_move.action_confirm()
diff --git a/addons/stock/models/stock_warehouse.py b/addons/stock/models/stock_warehouse.py
index 9bb6044cbca..c7dd63fa804 100644
--- a/addons/stock/models/stock_warehouse.py
+++ b/addons/stock/models/stock_warehouse.py
@@ -8,7 +8,7 @@ from dateutil import relativedelta
from odoo import api, fields, models, _
from odoo.addons import decimal_precision as dp
from odoo.exceptions import UserError, ValidationError
-from odoo.tools import DEFAULT_SERVER_DATE_FORMAT
+from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT
import logging
@@ -806,7 +806,7 @@ class Orderpoint(models.Model):
# These days will be substracted when creating the PO
days += self.product_id._select_seller().delay or 0.0
date_planned = start_date + relativedelta.relativedelta(days=days)
- return date_planned.strftime(DEFAULT_SERVER_DATE_FORMAT)
+ return date_planned.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
@api.multi
def _prepare_procurement_values(self, product_qty, date=False, group=False):
diff --git a/addons/web_editor/static/lib/summernote/src/js/module/Clipboard.js b/addons/web_editor/static/lib/summernote/src/js/module/Clipboard.js
index 8b42726ff7a..955b49437a8 100644
--- a/addons/web_editor/static/lib/summernote/src/js/module/Clipboard.js
+++ b/addons/web_editor/static/lib/summernote/src/js/module/Clipboard.js
@@ -206,6 +206,18 @@ define([
r.select();
}
+ // If only pasting a element in an unique element, only paste
+ // the element text
+ var $p = $arch.children('p');
+ var onlyAP = ($p.length === 1 && $arch.children().length === 1);
+ if (onlyAP) {
+ var $p1 = $(r.sc).closest('p');
+ var $p2 = $(r.ec).closest('p');
+ if ($p1.length && $p2.length && $p1[0] === $p2[0]) {
+ $arch.html($p.text());
+ }
+ }
+
/*
insert content
*/
diff --git a/addons/web_editor/static/src/js/rte.js b/addons/web_editor/static/src/js/rte.js
index b0366583aea..c7dc268251d 100644
--- a/addons/web_editor/static/src/js/rte.js
+++ b/addons/web_editor/static/src/js/rte.js
@@ -429,7 +429,7 @@ var RTE = Widget.extend({
saveElement: function ($el, context) {
// remove multi edition
if ($el.data('oe-model')) {
- var key = $el.data('oe-model')+":"+$el.data('oe-id')+":"+$el.data('oe-field')+":"+$el.data('oe-type')+":"+$el.data('oe-expression');
+ var key = $el.data('oe-model')+":"+$el.data('oe-id')+":"+$el.data('oe-field')+":"+$el.data('oe-type')+":"+$el.data('oe-expression')+":"+$el.data('oe-xpath');
if (this.__saved[key]) return $.when();
this.__saved[key] = true;
}
diff --git a/addons/web_editor/static/src/js/rte.summernote.js b/addons/web_editor/static/src/js/rte.summernote.js
index 4ab1e7bed06..a3bf079aa30 100644
--- a/addons/web_editor/static/src/js/rte.summernote.js
+++ b/addons/web_editor/static/src/js/rte.summernote.js
@@ -454,7 +454,7 @@ $.summernote.pluginEvents.visible = function (event, editor, layoutInfo) {
if (($node.is('[data-oe-type="html"]') || $node.is('[data-oe-field="arch"]')) &&
$node.hasClass("o_editable") &&
!$node[0].children.length &&
- "h1 h2 h3 h4 h5 h6 p b bold i u code sup strong small pre th td".toUpperCase().indexOf($node[0].nodeName) === -1) {
+ "h1 h2 h3 h4 h5 h6 p b bold i u code sup strong small pre th td label".toUpperCase().indexOf($node[0].nodeName) === -1) {
var p = $('
')[0];
$node.append( p );
range.createFromNode(p.firstChild).select();
diff --git a/addons/website_forum/views/forum.xml b/addons/website_forum/views/forum.xml
index 76e269cd105..f236790b8ad 100644
--- a/addons/website_forum/views/forum.xml
+++ b/addons/website_forum/views/forum.xml
@@ -69,6 +69,7 @@
+
diff --git a/addons/website_slides/static/src/js/slides_upload.js b/addons/website_slides/static/src/js/slides_upload.js
index b8fbe164421..d451fd6eb64 100644
--- a/addons/website_slides/static/src/js/slides_upload.js
+++ b/addons/website_slides/static/src/js/slides_upload.js
@@ -150,6 +150,8 @@ var SlideDialog = Widget.extend({
_.each(data.items, function (obj) {
page_content = page_content + obj.str + " ";
});
+ // page_content may contain null characters
+ page_content = page_content.replace(/\0/g, "");
self.index_content = self.index_content + page_number + ". " + page_content + '\n';
if (maxPages === page_number) {
if (loaded) {
diff --git a/odoo/addons/base/static/src/css/description.css b/odoo/addons/base/static/src/css/description.css
index cbfa3d5e968..b1f436b872f 100644
--- a/odoo/addons/base/static/src/css/description.css
+++ b/odoo/addons/base/static/src/css/description.css
@@ -1,5 +1,4 @@
-@charset "utf-8";
-/*
+/**
* This CSS is for the html description of modules
* TODO clean
*/
@@ -78,11 +77,11 @@
/* ------ BASE GRID CONSTRUCTS ----- */
.oe_page {
margin: 0px auto 64px auto;
- max-width: 992px;
+ max-width: 100%;
}
.oe_row {
- width: 928px;
+ width: 100%;
margin-top: 16px;
margin-bottom: 16px;
margin-left: auto;
@@ -111,35 +110,35 @@
}
.oe_span12 {
- width: 928px;
+ width: 100%;
}
.oe_span10 {
- width: 773px;
+ width: 83.33333%;
}
.oe_span9 {
- width: 696px;
+ width: 75%;
}
.oe_span8 {
- width: 618px;
+ width: 66.66667%;
}
.oe_span6 {
- width: 464px;
+ width: 50%;
}
.oe_span4 {
- width: 309px;
+ width: 33.33333%;
}
.oe_span3 {
- width: 232px;
+ width: 25%;
}
.oe_span2 {
- width: 154px;
+ width: 16.66667%;
}
[class*='oe_span'].oe_fit {
@@ -162,28 +161,28 @@
width: auto;
}
.oe_row.oe_flex .oe_span12 {
- max-width: 928px;
+ max-width: 100%;
}
.oe_row.oe_flex .oe_span10 {
- max-width: 769px;
+ max-width: 83.33333%;
}
.oe_row.oe_flex .oe_span9 {
- max-width: 692px;
+ max-width: 75%;
}
.oe_row.oe_flex .oe_span8 {
- max-width: 614px;
+ max-width: 66.66667%;
}
.oe_row.oe_flex .oe_span6 {
- max-width: 460px;
+ max-width: 50%;
}
.oe_row.oe_flex .oe_span4 {
- max-width: 305px;
+ max-width: 33.33333%;
}
.oe_row.oe_flex .oe_span3 {
- max-width: 228px;
+ max-width: 25%;
}
.oe_row.oe_flex .oe_span2 {
- max-width: 150px;
+ max-width: 16.66667%;
}
.oe_mb0 {
diff --git a/odoo/addons/base/static/src/css/description.sass b/odoo/addons/base/static/src/css/description.sass
index 4fa2ed100d8..49be6054652 100644
--- a/odoo/addons/base/static/src/css/description.sass
+++ b/odoo/addons/base/static/src/css/description.sass
@@ -86,11 +86,11 @@ $v8_anchor_visited_color: rgb(91, 40, 79)
.oe_page
margin: 0px auto 64px auto
- max-width: 992px
+ max-width: 100%
.oe_row
- width: 928px
+ width: 100%
margin-top: 16px
margin-bottom: 16px
margin-left: auto
@@ -107,14 +107,14 @@ $v8_anchor_visited_color: rgb(91, 40, 79)
line-height: 0
height: 0
-$oe_span12_width: 928px
-$oe_span10_width: 773px
-$oe_span9_width: 696px
-$oe_span8_width: 618px
-$oe_span6_width: 464px
-$oe_span4_width: 309px
-$oe_span3_width: 232px
-$oe_span2_width: 154px
+$oe_span12_width: 100%
+$oe_span10_width: 83.33333333%
+$oe_span9_width: 75%
+$oe_span8_width: 66.66666667%
+$oe_span6_width: 50%
+$oe_span4_width: 33.33333333%
+$oe_span3_width: 25%
+$oe_span2_width: 16.66666667%
[class*='oe_span']
float: left
@@ -155,19 +155,19 @@ $oe_span2_width: 154px
.oe_span12
max-width: $oe_span12_width
.oe_span10
- max-width: ($oe_span10_width +-4px)
+ max-width: $oe_span10_width
.oe_span9
- max-width: ($oe_span9_width +-4px)
- .oe_span8
- max-width: ($oe_span8_width +-4px)
+ max-width: $oe_span9_width
+ .oe_span8
+ max-width: $oe_span8_width
.oe_span6
- max-width: ($oe_span6_width +-4px)
+ max-width: $oe_span6_width
.oe_span4
- max-width: ($oe_span4_width +-4px)
+ max-width: $oe_span4_width
.oe_span3
- max-width: ($oe_span3_width +-4px)
+ max-width: $oe_span3_width
.oe_span2
- max-width: ($oe_span2_width +-4px)
+ max-width: $oe_span2_width
.oe_mb0
margin-bottom: 0px !important
diff --git a/odoo/loglevels.py b/odoo/loglevels.py
index 400f08e1a8f..0972dc5d2b4 100644
--- a/odoo/loglevels.py
+++ b/odoo/loglevels.py
@@ -94,7 +94,7 @@ def ustr(value, hint_encoding='utf-8', errors='strict'):
def exception_to_unicode(e):
if (sys.version_info[:2] < (2,6)) and hasattr(e, 'message'):
return ustr(e.message)
- if hasattr(e, 'args'):
+ if getattr(e, 'args', ()):
return "\n".join((ustr(a) for a in e.args))
try:
return unicode(e)
diff --git a/odoo/tools/translate.py b/odoo/tools/translate.py
index fbef3443929..8d6703a5d17 100644
--- a/odoo/tools/translate.py
+++ b/odoo/tools/translate.py
@@ -949,7 +949,8 @@ def trans_generate(lang, modules, cr):
def get_module_from_path(path):
for (mp, rec) in path_list:
mp = os.path.join(mp, '')
- if rec and path.startswith(mp) and os.path.dirname(path) != mp:
+ dirname = os.path.join(os.path.dirname(path), '')
+ if rec and path.startswith(mp) and dirname != mp:
path = path[len(mp):]
return path.split(os.path.sep)[0]
return 'base' # files that are not in a module are considered as being in 'base' module