summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--addons/account/models/account_payment.py2
-rw-r--r--addons/account/wizard/account_report_common.py2
-rw-r--r--addons/account_asset/models/account_invoice.py2
-rw-r--r--addons/base_gengo/wizard/base_gengo_translations.py2
-rw-r--r--addons/calendar/models/calendar.py22
-rw-r--r--addons/event/models/event.py5
-rw-r--r--addons/event_sale/models/event.py19
-rw-r--r--addons/hr_attendance/static/src/js/kiosk_confirm.js2
-rw-r--r--addons/hr_expense/models/hr_expense.py11
-rw-r--r--addons/hr_expense/report/report_expense_sheet.xml26
-rw-r--r--addons/hr_payroll/models/hr_payslip.py4
-rw-r--r--addons/l10n_be_hr_payroll_fleet/models/fleet.py2
-rw-r--r--addons/mrp/views/ir_attachment_view.xml6
-rw-r--r--addons/point_of_sale/static/src/js/chrome.js2
-rw-r--r--addons/sale_mrp/models/sale_mrp.py4
-rw-r--r--addons/stock/models/stock_location.py3
-rw-r--r--addons/web/static/src/js/chrome/abstract_web_client.js2
-rw-r--r--addons/web/static/src/js/chrome/action_manager.js4
-rw-r--r--addons/web/static/src/js/fields/abstract_field.js2
-rw-r--r--addons/web/static/src/js/fields/basic_fields.js4
-rw-r--r--addons/web/static/src/js/views/calendar/calendar_controller.js17
-rw-r--r--addons/web/static/src/js/views/calendar/calendar_renderer.js12
-rw-r--r--addons/web/static/src/js/views/calendar/calendar_view.js3
-rw-r--r--addons/web/static/src/js/views/list/list_editable_renderer.js3
-rw-r--r--addons/web/static/src/less/fields_extra.less2
-rw-r--r--addons/web/static/tests/fields/basic_fields_tests.js16
-rw-r--r--addons/web/static/tests/fields/relational_fields_tests.js49
-rw-r--r--addons/website_blog/controllers/main.py2
-rw-r--r--addons/website_event/controllers/main.py2
-rw-r--r--addons/website_event_track/views/event_track_templates.xml4
-rw-r--r--addons/website_sale/controllers/backend.py2
-rw-r--r--addons/website_slides/controllers/main.py6
-rw-r--r--doc/cla/corporate/niboo.md21
-rw-r--r--doc/cla/individual/YasserKadddour.md11
-rw-r--r--odoo/addons/base/res/res_currency_data.xml2
-rw-r--r--odoo/addons/base/tests/test_expression.py12
-rw-r--r--odoo/addons/test_assetsbundle/tests/test_assetsbundle.py44
37 files changed, 255 insertions, 79 deletions
diff --git a/addons/account/models/account_payment.py b/addons/account/models/account_payment.py
index 202bb87b262..45d47666f62 100644
--- a/addons/account/models/account_payment.py
+++ b/addons/account/models/account_payment.py
@@ -475,7 +475,7 @@ class account_payment(models.Model):
if rec.payment_type == 'outbound':
sequence_code = 'account.payment.supplier.invoice'
rec.name = self.env['ir.sequence'].with_context(ir_sequence_date=rec.payment_date).next_by_code(sequence_code)
- if not rec.name and self.payment_type != 'transfer':
+ if not rec.name and rec.payment_type != 'transfer':
raise UserError(_("You have to define a sequence for %s in your company.") % (sequence_code,))
# Create the journal entry
diff --git a/addons/account/wizard/account_report_common.py b/addons/account/wizard/account_report_common.py
index 23f6953b2dc..f7cba985485 100644
--- a/addons/account/wizard/account_report_common.py
+++ b/addons/account/wizard/account_report_common.py
@@ -35,5 +35,5 @@ class AccountCommonReport(models.TransientModel):
data['model'] = self.env.context.get('active_model', 'ir.ui.menu')
data['form'] = self.read(['date_from', 'date_to', 'journal_ids', 'target_move'])[0]
used_context = self._build_contexts(data)
- data['form']['used_context'] = dict(used_context, lang=self.env.context.get('lang', 'en_US'))
+ data['form']['used_context'] = dict(used_context, lang=self.env.context.get('lang') or 'en_US')
return self._print_report(data)
diff --git a/addons/account_asset/models/account_invoice.py b/addons/account_asset/models/account_invoice.py
index 192c984b265..fc4789d0706 100644
--- a/addons/account_asset/models/account_invoice.py
+++ b/addons/account_asset/models/account_invoice.py
@@ -48,6 +48,8 @@ class AccountInvoiceLine(models.Model):
self.asset_end_date = False
cat = self.asset_category_id
if cat:
+ if cat.method_number == 0 or cat.method_period == 0:
+ raise UserError(_('The number of depreciations or the period length of your asset category cannot be null.'))
months = cat.method_number * cat.method_period
if self.invoice_id.type in ['out_invoice', 'out_refund']:
self.asset_mrr = self.price_subtotal_signed / months
diff --git a/addons/base_gengo/wizard/base_gengo_translations.py b/addons/base_gengo/wizard/base_gengo_translations.py
index d6e5ce9176a..0a585934af8 100644
--- a/addons/base_gengo/wizard/base_gengo_translations.py
+++ b/addons/base_gengo/wizard/base_gengo_translations.py
@@ -31,7 +31,7 @@ class BaseGengoTranslations(models.TransientModel):
res['authorized_credentials'], gengo = self.gengo_authentication()
if 'lang_id' in fields:
res['lang_id'] = self.env['res.lang'].search([
- ('code', '=', self.env.context.get('lang', 'en_US'))
+ ('code', '=', self.env.context.get('lang') or 'en_US')
], limit=1).id
return res
diff --git a/addons/calendar/models/calendar.py b/addons/calendar/models/calendar.py
index cdc1b16dfd3..e54a96ff13f 100644
--- a/addons/calendar/models/calendar.py
+++ b/addons/calendar/models/calendar.py
@@ -683,16 +683,30 @@ class Meeting(models.Model):
date_deadline = fields.Datetime.context_timestamp(self.with_context(tz=timezone), fields.Datetime.from_string(stop))
# convert into string the date and time, using user formats
- date_str = date.strftime(format_date)
- time_str = date.strftime(format_time)
+ date_str = date.strftime(format_date).decode('utf-8')
+ time_str = date.strftime(format_time).decode('utf-8')
if zallday:
display_time = _("AllDay , %s") % (date_str)
elif zduration < 24:
duration = date + timedelta(hours=zduration)
- display_time = _("%s at (%s To %s) (%s)") % (date_str, time_str, duration.strftime(format_time), timezone)
+ duration_time = duration.strftime(format_time).decode('utf-8')
+ display_time = _(u"%s at (%s To %s) (%s)") % (
+ date_str,
+ time_str,
+ duration_time,
+ timezone,
+ )
else:
- display_time = _("%s at %s To\n %s at %s (%s)") % (date_str, time_str, date_deadline.strftime(format_date), date_deadline.strftime(format_time), timezone)
+ dd_date = date_deadline.strftime(format_date).decode('utf-8')
+ dd_time = date_deadline.strftime(format_time).decode('utf-8')
+ display_time = _(u"%s at %s To\n %s at %s (%s)") % (
+ date_str,
+ time_str,
+ dd_date,
+ dd_time,
+ timezone,
+ )
return display_time
def _get_duration(self, start, stop):
diff --git a/addons/event/models/event.py b/addons/event/models/event.py
index 85406ba3ba9..60fa9cfb9f3 100644
--- a/addons/event/models/event.py
+++ b/addons/event/models/event.py
@@ -501,3 +501,8 @@ class EventRegistration(models.Model):
return _('next month')
else:
return _('on ') + format_tz(self.with_context({'use_babel': True}).env, self.event_begin_date, tz=self.event_id.date_tz or 'UTC')
+
+ @api.multi
+ def summary(self):
+ self.ensure_one()
+ return {'information': []}
diff --git a/addons/event_sale/models/event.py b/addons/event_sale/models/event.py
index d1c28b32d2a..9af02529ef8 100644
--- a/addons/event_sale/models/event.py
+++ b/addons/event_sale/models/event.py
@@ -212,3 +212,22 @@ class EventRegistration(models.Model):
'sale_order_line_id': line_id.id,
})
return att_data
+
+ @api.multi
+ def summary(self):
+ res = super(EventRegistration, self).summary()
+ if self.event_ticket_id.product_id.image_medium:
+ res['image'] = '/web/image/product.product/%s/image_medium' % self.event_ticket_id.product_id.id
+ information = res.setdefault('information', {})
+ information.append((_('Name'), self.name))
+ information.append((_('Ticket'), self.event_ticket_id.name or _('None')))
+ order = self.sale_order_id.sudo()
+ if not order:
+ payment_status = _('Free')
+ elif not order.invoice_ids or any(invoice.state != 'paid' for invoice in order.invoice_ids):
+ payment_status = _('To pay')
+ res['alert'] = _('The registration must be paid')
+ else:
+ payment_status = _('Paid')
+ information.append((_('Payment'), payment_status))
+ return res
diff --git a/addons/hr_attendance/static/src/js/kiosk_confirm.js b/addons/hr_attendance/static/src/js/kiosk_confirm.js
index 9ee035dc493..2f55edd42c1 100644
--- a/addons/hr_attendance/static/src/js/kiosk_confirm.js
+++ b/addons/hr_attendance/static/src/js/kiosk_confirm.js
@@ -67,7 +67,7 @@ var KioskConfirm = Widget.extend({
start: function () {
var self = this;
- self.getSession().user_has_group('hr_attendance.group_hr_attendance_use_pin').then(function(has_group){
+ this.getSession().user_has_group('hr_attendance.group_hr_attendance_use_pin').then(function(has_group){
self.use_pin = has_group;
self.$el.html(QWeb.render("HrAttendanceKioskConfirm", {widget: self}));
self.start_clock();
diff --git a/addons/hr_expense/models/hr_expense.py b/addons/hr_expense/models/hr_expense.py
index 8d8e645d889..086dc125cca 100644
--- a/addons/hr_expense/models/hr_expense.py
+++ b/addons/hr_expense/models/hr_expense.py
@@ -508,10 +508,13 @@ class HrExpenseSheet(models.Model):
@api.one
@api.depends('expense_line_ids', 'expense_line_ids.total_amount', 'expense_line_ids.currency_id')
def _compute_amount(self):
- if len(self.expense_line_ids.mapped('currency_id')) < 2:
- self.total_amount = sum(self.expense_line_ids.mapped('total_amount'))
- else:
- self.total_amount = 0.0
+ total_amount = 0.0
+ for expense in self.expense_line_ids:
+ total_amount += expense.currency_id.with_context(
+ date=expense.date,
+ company_id=expense.company_id.id
+ ).compute(expense.total_amount, self.currency_id)
+ self.total_amount = total_amount
# FIXME: A 4 command is missing to explicitly declare the one2many relation
# between the sheet and the lines when using 'default_expense_line_ids':[ids]
diff --git a/addons/hr_expense/report/report_expense_sheet.xml b/addons/hr_expense/report/report_expense_sheet.xml
index 30ad5337f94..1934b7cb64b 100644
--- a/addons/hr_expense/report/report_expense_sheet.xml
+++ b/addons/hr_expense/report/report_expense_sheet.xml
@@ -77,21 +77,19 @@
</tbody>
</table>
- <t t-if="len(o.mapped('expense_line_ids').mapped('currency_id')) &lt; 2">
- <div class="row">
- <div class="col-xs-4 pull-right">
- <table class="table table-condensed">
- <tr class="border-black">
- <td><strong>Total</strong></td>
- <td class="text-right">
- <span t-field="o.total_amount"
- t-options='{"widget": "monetary", "display_currency": o.currency_id}'/>
- </td>
- </tr>
- </table>
- </div>
+ <div class="row">
+ <div class="col-xs-4 pull-right">
+ <table class="table table-condensed">
+ <tr class="border-black">
+ <td><strong>Total</strong></td>
+ <td class="text-right">
+ <span t-field="o.total_amount"
+ t-options='{"widget": "monetary", "display_currency": o.currency_id}'/>
+ </td>
+ </tr>
+ </table>
</div>
- </t>
+ </div>
<p>Certified honest and conform,<br/>(Date and signature).<br/><br/></p>
</div>
</t>
diff --git a/addons/hr_payroll/models/hr_payslip.py b/addons/hr_payroll/models/hr_payslip.py
index b1af24026ae..562604a591d 100644
--- a/addons/hr_payroll/models/hr_payslip.py
+++ b/addons/hr_payroll/models/hr_payslip.py
@@ -391,7 +391,7 @@ class HrPayslip(models.Model):
return res
ttyme = datetime.fromtimestamp(time.mktime(time.strptime(date_from, "%Y-%m-%d")))
employee = self.env['hr.employee'].browse(employee_id)
- locale = self.env.context.get('lang', 'en_US')
+ locale = self.env.context.get('lang') or 'en_US'
res['value'].update({
'name': _('Salary Slip of %s for %s') % (employee.name, tools.ustr(babel.dates.format_date(date=ttyme, format='MMMM-y', locale=locale))),
'company_id': employee.company_id.id,
@@ -442,7 +442,7 @@ class HrPayslip(models.Model):
contract_ids = []
ttyme = datetime.fromtimestamp(time.mktime(time.strptime(date_from, "%Y-%m-%d")))
- locale = self.env.context.get('lang', 'en_US')
+ locale = self.env.context.get('lang') or 'en_US'
self.name = _('Salary Slip of %s for %s') % (employee.name, tools.ustr(babel.dates.format_date(date=ttyme, format='MMMM-y', locale=locale)))
self.company_id = employee.company_id
diff --git a/addons/l10n_be_hr_payroll_fleet/models/fleet.py b/addons/l10n_be_hr_payroll_fleet/models/fleet.py
index e70c31ad7f2..b0ba4206ec4 100644
--- a/addons/l10n_be_hr_payroll_fleet/models/fleet.py
+++ b/addons/l10n_be_hr_payroll_fleet/models/fleet.py
@@ -69,7 +69,7 @@ class FleetVehicle(models.Model):
return babel.dates.format_date(
date=Datetime.from_string(self.acquisition_date),
format='MMMM y',
- locale=self._context.get('lang', 'en_US')
+ locale=self._context.get('lang') or 'en_US'
)
def _get_car_atn(self, acquisition_date, car_value, fuel_type, co2):
diff --git a/addons/mrp/views/ir_attachment_view.xml b/addons/mrp/views/ir_attachment_view.xml
index 7d3e5d47daa..cf6b00982c7 100644
--- a/addons/mrp/views/ir_attachment_view.xml
+++ b/addons/mrp/views/ir_attachment_view.xml
@@ -6,7 +6,7 @@
<field name="model">mrp.document</field>
<field name="arch" type="xml">
<kanban>
- <field name="id"/>
+ <field name="ir_attachment_id"/>
<field name="mimetype"/>
<field name="type"/>
<field name="datas_fname"/>
@@ -19,11 +19,11 @@
<div class="oe_attachment">
<div class="row">
<div class="col-xs-4 o_kanban_primary_left" t-if="record.type.raw_value == 'binary'">
- <a t-attf-href="/web/content/#{record.id.raw_value}?download=1">
+ <a t-attf-href="/web/content/#{record.ir_attachment_id.raw_value}?download=1">
<t t-set="webimage" t-value="new RegExp('image.*(gif|jpeg|jpg|png)').test(record.mimetype.value)"/>
<div t-if="!webimage" class="o_image" t-att-data-mimetype="record.mimetype.value"/>
<div t-if="webimage" class="o_attachment_image">
- <img t-attf-src="/web/image/#{record.id.raw_value}?download=1"/>
+ <img t-attf-src="/web/image/#{record.ir_attachment_id.raw_value}?download=1"/>
</div>
</a>
</div>
diff --git a/addons/point_of_sale/static/src/js/chrome.js b/addons/point_of_sale/static/src/js/chrome.js
index 3d95af17a61..92c1316fcb7 100644
--- a/addons/point_of_sale/static/src/js/chrome.js
+++ b/addons/point_of_sale/static/src/js/chrome.js
@@ -572,7 +572,7 @@ var Chrome = PosBaseWidget.extend({
this.started = new $.Deferred(); // resolves when DOM is online
this.ready = new $.Deferred(); // resolves when the whole GUI has been loaded
- this.pos = new models.PosModel(this.session,{chrome:this});
+ this.pos = new models.PosModel(this.getSession(), {chrome:this});
this.gui = new gui.Gui({pos: this.pos, chrome: this});
this.chrome = this; // So that chrome's childs have chrome set automatically
this.pos.gui = this.gui;
diff --git a/addons/sale_mrp/models/sale_mrp.py b/addons/sale_mrp/models/sale_mrp.py
index 20ddf2c3fc1..fe789333d03 100644
--- a/addons/sale_mrp/models/sale_mrp.py
+++ b/addons/sale_mrp/models/sale_mrp.py
@@ -36,14 +36,14 @@ class SaleOrderLine(models.Model):
if uom.id != components[product]['uom']:
from_uom = uom
to_uom = self.env['product.uom'].browse(components[product]['uom'])
- qty = from_uom._compute_quantity(qty, to_uom_id=to_uom)
+ qty = from_uom._compute_quantity(qty, to_uom)
components[product]['qty'] += qty
else:
# To be in the uom reference of the product
to_uom = self.env['product.product'].browse(product).uom_id
if uom.id != to_uom.id:
from_uom = uom
- qty = from_uom._compute_quantity(qty, to_uom_id=to_uom)
+ qty = from_uom._compute_quantity(qty, to_uom)
components[product] = {'qty': qty, 'uom': to_uom.id}
return components
diff --git a/addons/stock/models/stock_location.py b/addons/stock/models/stock_location.py
index 35329d1616a..db80965fbfe 100644
--- a/addons/stock/models/stock_location.py
+++ b/addons/stock/models/stock_location.py
@@ -3,6 +3,7 @@
from datetime import datetime
from dateutil import relativedelta
+from odoo.exceptions import UserError
from odoo import api, fields, models, _
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT
@@ -86,6 +87,8 @@ class Location(models.Model):
name = location.name
while location.location_id and location.usage != 'view':
location = location.location_id
+ if not name:
+ raise UserError(_('You have to set a name for this location.'))
name = location.name + "/" + name
ret_list.append((orig_location.id, name))
return ret_list
diff --git a/addons/web/static/src/js/chrome/abstract_web_client.js b/addons/web/static/src/js/chrome/abstract_web_client.js
index 8492adc33f6..9a629ec3393 100644
--- a/addons/web/static/src/js/chrome/abstract_web_client.js
+++ b/addons/web/static/src/js/chrome/abstract_web_client.js
@@ -245,7 +245,7 @@ var AbstractWebClient = Widget.extend(mixins.ServiceProvider, {
},
do_reload: function () {
var self = this;
- return this.session.session_reload().then(function () {
+ return session.session_reload().then(function () {
session.load_modules(true).then(
self.menu.proxy('do_reload'));
});
diff --git a/addons/web/static/src/js/chrome/action_manager.js b/addons/web/static/src/js/chrome/action_manager.js
index e18a3d60734..194b8e6c8a7 100644
--- a/addons/web/static/src/js/chrome/action_manager.js
+++ b/addons/web/static/src/js/chrome/action_manager.js
@@ -912,14 +912,14 @@ var ActionManager = Widget.extend({
action: JSON.stringify(action),
token: new Date().getTime()
};
- var url = self.session.url('/web/report', params);
+ var url = session.url('/web/report', params);
framework.unblockUI();
$('<a href="'+url+'" target="_blank"></a>')[0].click();
return;
}
var c = crash_manager;
return $.Deferred(function (d) {
- self.session.get_file({
+ session.get_file({
url: '/web/report',
data: {action: JSON.stringify(action)},
complete: framework.unblockUI,
diff --git a/addons/web/static/src/js/fields/abstract_field.js b/addons/web/static/src/js/fields/abstract_field.js
index 894ee57c813..e25c02f781e 100644
--- a/addons/web/static/src/js/fields/abstract_field.js
+++ b/addons/web/static/src/js/fields/abstract_field.js
@@ -457,7 +457,7 @@ var AbstractField = Widget.extend({
this.trigger_up('navigation_move', {direction: 'next_line'});
break;
case $.ui.keyCode.ESCAPE:
- this.trigger_up('navigation_move', {direction: 'cancel'});
+ this.trigger_up('navigation_move', {direction: 'cancel', originalEvent: ev});
break;
case $.ui.keyCode.UP:
ev.stopPropagation();
diff --git a/addons/web/static/src/js/fields/basic_fields.js b/addons/web/static/src/js/fields/basic_fields.js
index 1139b234edd..6392870dc90 100644
--- a/addons/web/static/src/js/fields/basic_fields.js
+++ b/addons/web/static/src/js/fields/basic_fields.js
@@ -994,6 +994,10 @@ var FieldPhone = FieldEmail.extend({
} else {
this.$el.removeClass('o_form_uri');
}
+ // This class should technically be there in case of a very very long
+ // phone number, but it breaks the o_row mechanism, which is more
+ // important right now.
+ this.$el.removeClass('o_text_overflow');
},
/**
diff --git a/addons/web/static/src/js/views/calendar/calendar_controller.js b/addons/web/static/src/js/views/calendar/calendar_controller.js
index db2394ddd1d..8d27acb43dd 100644
--- a/addons/web/static/src/js/views/calendar/calendar_controller.js
+++ b/addons/web/static/src/js/views/calendar/calendar_controller.js
@@ -28,6 +28,7 @@ var CalendarController = AbstractController.extend({
changeDate: '_onChangeDate',
changeFilter: '_onChangeFilter',
toggleFullWidth: '_onToggleFullWidth',
+ viewUpdated: '_onViewUpdated',
}),
/**
* @override
@@ -39,6 +40,7 @@ var CalendarController = AbstractController.extend({
init: function (parent, model, renderer, params) {
this._super.apply(this, arguments);
this.current_start = null;
+ this.displayName = params.displayName;
this.quickAddPop = params.quickAddPop;
this.disableQuickCreate = params.disableQuickCreate;
this.eventOpenPopup = params.eventOpenPopup;
@@ -317,6 +319,21 @@ var CalendarController = AbstractController.extend({
_onUpdateRecord: function (event) {
this._updateRecord(event.data);
},
+ /**
+ * The internal state of the calendar (mode, period displayed) has changed,
+ * so update the control panel buttons and breadcrumbs accordingly.
+ *
+ * @param {OdooEvent} event
+ */
+ _onViewUpdated: function (event) {
+ this.mode = event.data.mode;
+ if (this.$buttons) {
+ this.$buttons.find('.active').removeClass('active');
+ this.$buttons.find('.o_calendar_button_' + this.mode).addClass('active');
+ }
+ var subtitle = (this.mode === 'week' ? _t('Week ') : '') + event.data.title;
+ this.set({title: this.displayName + ' (' + subtitle + ')'});
+ },
});
return CalendarController;
diff --git a/addons/web/static/src/js/views/calendar/calendar_renderer.js b/addons/web/static/src/js/views/calendar/calendar_renderer.js
index 584813cba5a..f011a24a32e 100644
--- a/addons/web/static/src/js/views/calendar/calendar_renderer.js
+++ b/addons/web/static/src/js/views/calendar/calendar_renderer.js
@@ -354,9 +354,19 @@ return AbstractRenderer.extend({
element.find('.fc-content .fc-time').text(display_hour);
},
// Dirty hack to ensure a correct first render
- eventAfterAllRender: function (view) {
+ eventAfterAllRender: function () {
window.dispatchEvent(new Event('resize'));
},
+ viewRender: function (view) {
+ // compute mode from view.name which is either 'month', 'agendaWeek' or 'agendaDay'
+ var mode = view.name === 'month' ? 'month' : (view.name === 'agendaWeek' ? 'week' : 'day');
+ // compute title: in week mode, display the week number
+ var title = mode === 'week' ? view.intervalStart.week() : view.title;
+ self.trigger_up('viewUpdated', {
+ mode: mode,
+ title: title,
+ });
+ },
height: 'parent',
unselectAuto: false,
});
diff --git a/addons/web/static/src/js/views/calendar/calendar_view.js b/addons/web/static/src/js/views/calendar/calendar_view.js
index 47c16300508..7f58ca55632 100644
--- a/addons/web/static/src/js/views/calendar/calendar_view.js
+++ b/addons/web/static/src/js/views/calendar/calendar_view.js
@@ -109,7 +109,7 @@ var CalendarView = AbstractView.extend({
//if quick_add = False, we don't allow quick_add
//if quick_add = not specified in view, we use the default widgets.QuickCreate
- //if quick_add = is NOT False and IS specified in view, we this one for widgets.QuickCreate'
+ //if quick_add = is NOT False and IS specified in view, we this one for widgets.QuickCreate'
this.controllerParams.quickAddPop = (!('quick_add' in attrs) || utils.toBoolElse(attrs.quick_add+'', true));
this.controllerParams.disableQuickCreate = params.disable_quick_create || !this.controllerParams.quickAddPop;
// If this field is set ot true, we don't open the event in form view, but in a popup with the view_id passed by this parameter
@@ -118,6 +118,7 @@ var CalendarView = AbstractView.extend({
this.controllerParams.eventOpenPopup = utils.toBoolElse(attrs.event_open_popup || '', false);
this.controllerParams.mapping = mapping;
this.controllerParams.context = params.context || {};
+ this.controllerParams.displayName = params.action && params.action.name;
this.rendererParams.displayFields = displayFields;
this.rendererParams.eventTemplate = _.findWhere(arch.children, {'tag': 'templates'});
diff --git a/addons/web/static/src/js/views/list/list_editable_renderer.js b/addons/web/static/src/js/views/list/list_editable_renderer.js
index 06058069ab6..5bd24044c39 100644
--- a/addons/web/static/src/js/views/list/list_editable_renderer.js
+++ b/addons/web/static/src/js/views/list/list_editable_renderer.js
@@ -729,6 +729,9 @@ ListRenderer.include({
this._moveToNextLine();
break;
case 'cancel':
+ // stop the original event (typically an ESCAPE keydown), to
+ // prevent from closing the potential dialog containing this list
+ ev.data.originalEvent.stopPropagation();
this.trigger_up('discard_changes', {
recordID: ev.target.dataPointID,
});
diff --git a/addons/web/static/src/less/fields_extra.less b/addons/web/static/src/less/fields_extra.less
index 79d9628116d..541fef8ba47 100644
--- a/addons/web/static/src/less/fields_extra.less
+++ b/addons/web/static/src/less/fields_extra.less
@@ -6,7 +6,7 @@
// Required
.o_required_modifier {
&.o_input, .o_input {
- background-color: #D2D2FF;
+ background-color: #D2D2FF!important;
}
}
diff --git a/addons/web/static/tests/fields/basic_fields_tests.js b/addons/web/static/tests/fields/basic_fields_tests.js
index b040f5ebbcd..f01f2d8b589 100644
--- a/addons/web/static/tests/fields/basic_fields_tests.js
+++ b/addons/web/static/tests/fields/basic_fields_tests.js
@@ -2605,7 +2605,7 @@ QUnit.module('basic_fields', {
},
});
- var $phoneLink = form.$('a.o_form_uri.o_field_widget.o_text_overflow');
+ var $phoneLink = form.$('a.o_form_uri.o_field_widget');
assert.strictEqual($phoneLink.length, 1,
"should have a anchor with correct classes");
assert.strictEqual($phoneLink.text(), 'y\u00ADop',
@@ -2625,7 +2625,7 @@ QUnit.module('basic_fields', {
// save
form.$buttons.find('.o_form_button_save').click();
- $phoneLink = form.$('a.o_form_uri.o_field_widget.o_text_overflow');
+ $phoneLink = form.$('a.o_form_uri.o_field_widget');
assert.strictEqual($phoneLink.text(), 'n\u00ADew',
"new value should be displayed properly as text with the skype obfuscation");
assert.strictEqual($phoneLink.attr('href'), 'tel:new',
@@ -2655,7 +2655,7 @@ QUnit.module('basic_fields', {
assert.strictEqual(list.$('tbody td:not(.o_list_record_selector)').first().text(), 'y\u00ADop',
"value should be displayed properly as text with the skype obfuscation");
- var $phoneLink = list.$('a.o_form_uri.o_field_widget.o_text_overflow');
+ var $phoneLink = list.$('a.o_form_uri.o_field_widget');
assert.strictEqual($phoneLink.length, 5,
"should have anchors with correct classes");
assert.strictEqual($phoneLink.first().attr('href'), 'tel:yop',
@@ -2675,7 +2675,7 @@ QUnit.module('basic_fields', {
assert.ok(!$cell.parent().hasClass('o_selected_row'), 'should not be in edit mode anymore');
assert.strictEqual(list.$('tbody td:not(.o_list_record_selector)').first().text(), 'n\u00ADew',
"value should be properly updated");
- $phoneLink = list.$('a.o_form_uri.o_field_widget.o_text_overflow');
+ $phoneLink = list.$('a.o_form_uri.o_field_widget');
assert.strictEqual($phoneLink.length, 5,
"should still have anchors with correct classes");
assert.strictEqual($phoneLink.first().attr('href'), 'tel:new',
@@ -2715,7 +2715,7 @@ QUnit.module('basic_fields', {
},
});
- var $phone = form.$('span.o_field_widget.o_text_overflow:not(.o_form_uri)');
+ var $phone = form.$('span.o_field_widget:not(.o_form_uri)');
assert.strictEqual($phone.length, 1,
"should have a simple span rather than a link");
assert.strictEqual($phone.text(), 'yop',
@@ -2733,7 +2733,7 @@ QUnit.module('basic_fields', {
// save
form.$buttons.find('.o_form_button_save').click();
- assert.strictEqual(form.$('span.o_field_widget.o_text_overflow:not(.o_form_uri)').text(), 'new',
+ assert.strictEqual(form.$('span.o_field_widget:not(.o_form_uri)').text(), 'new',
"new value should be displayed properly as text without skype obfuscation");
form.destroy();
@@ -2768,7 +2768,7 @@ QUnit.module('basic_fields', {
assert.strictEqual(list.$('tbody td:not(.o_list_record_selector)').first().text(), 'yop',
"value should be displayed properly as text without skype obfuscation");
- assert.strictEqual(list.$('span.o_field_widget.o_text_overflow:not(.o_form_uri)').length, 5,
+ assert.strictEqual(list.$('span.o_field_widget:not(.o_form_uri)').length, 5,
"should have spans with correct classes");
// Edit a line and check the result
@@ -2785,7 +2785,7 @@ QUnit.module('basic_fields', {
assert.ok(!$cell.parent().hasClass('o_selected_row'), 'should not be in edit mode anymore');
assert.strictEqual(list.$('tbody td:not(.o_list_record_selector)').first().text(), 'new',
"value should be properly updated");
- assert.strictEqual(list.$('span.o_field_widget.o_text_overflow:not(.o_form_uri)').length, 5,
+ assert.strictEqual(list.$('span.o_field_widget:not(.o_form_uri)').length, 5,
"should still have spans with correct classes");
list.destroy();
diff --git a/addons/web/static/tests/fields/relational_fields_tests.js b/addons/web/static/tests/fields/relational_fields_tests.js
index 9b1dd8d8f61..34442ad0d50 100644
--- a/addons/web/static/tests/fields/relational_fields_tests.js
+++ b/addons/web/static/tests/fields/relational_fields_tests.js
@@ -3206,6 +3206,54 @@ QUnit.module('relational_fields', {
form.destroy();
});
+ QUnit.test('pressing escape in editable o2m list in dialog', function (assert) {
+ assert.expect(3);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<group>' +
+ '<field name="p">' +
+ '<tree>' +
+ '<field name="display_name"/>' +
+ '</tree>' +
+ '</field>' +
+ '</group>' +
+ '</form>',
+ res_id: 1,
+ archs: {
+ "partner,false,form": '<form>' +
+ '<field name="p">' +
+ '<tree editable="bottom">' +
+ '<field name="display_name"/>' +
+ '</tree>' +
+ '</field>' +
+ '</form>',
+ },
+ viewOptions: {
+ mode: 'edit',
+ },
+ });
+
+ form.$('.o_field_x2many_list_row_add a').click();
+ $('.modal .o_field_x2many_list_row_add a').click();
+
+ assert.strictEqual($('.modal .o_data_row.o_selected_row').length, 1,
+ "there should be a row in edition in the dialog");
+
+ // trigger keydown ESCAPE in the edited row
+ $('.modal .o_data_cell input').trigger({type: 'keydown', which: $.ui.keyCode.ESCAPE});
+
+ assert.strictEqual($('.modal').length, 1,
+ "dialog should still be open");
+ assert.strictEqual($('.modal .o_data_row').length, 0,
+ "the row should have been removed");
+
+ form.destroy();
+ });
+
QUnit.test('editable o2m with onchange and required field: delete an invalid line', function (assert) {
assert.expect(5);
@@ -5954,7 +6002,6 @@ QUnit.module('relational_fields', {
form.destroy();
});
-
QUnit.test('editing tabbed one2many (editable=bottom)', function (assert) {
assert.expect(12);
diff --git a/addons/website_blog/controllers/main.py b/addons/website_blog/controllers/main.py
index a7ac33d7997..6ebb67c8782 100644
--- a/addons/website_blog/controllers/main.py
+++ b/addons/website_blog/controllers/main.py
@@ -35,7 +35,7 @@ class WebsiteBlog(http.Controller):
group['date_begin'] = start
group['date_end'] = end
- locale = request.context.get('lang', 'en_US')
+ locale = request.context.get('lang') or 'en_US'
start = pytz.UTC.localize(fields.Datetime.from_string(start))
tzinfo = pytz.timezone(request.context.get('tz', 'utc') or 'utc')
diff --git a/addons/website_event/controllers/main.py b/addons/website_event/controllers/main.py
index 28ac0c17a8a..4f5a6516720 100644
--- a/addons/website_event/controllers/main.py
+++ b/addons/website_event/controllers/main.py
@@ -197,7 +197,7 @@ class WebsiteEventController(http.Controller):
def get_formated_date(self, event):
start_date = fields.Datetime.from_string(event.date_begin).date()
end_date = fields.Datetime.from_string(event.date_end).date()
- month = babel.dates.get_month_names('abbreviated', locale=event.env.context.get('lang', 'en_US'))[start_date.month]
+ month = babel.dates.get_month_names('abbreviated', locale=event.env.context.get('lang') or 'en_US')[start_date.month]
return ('%s %s%s') % (month, start_date.strftime("%e"), (end_date != start_date and ("-" + end_date.strftime("%e")) or ""))
@http.route('/event/get_country_event_list', type='json', auth='public', website=True)
diff --git a/addons/website_event_track/views/event_track_templates.xml b/addons/website_event_track/views/event_track_templates.xml
index caf9d7d68e2..d8d94f18026 100644
--- a/addons/website_event_track/views/event_track_templates.xml
+++ b/addons/website_event_track/views/event_track_templates.xml
@@ -220,11 +220,11 @@
</small>
</div>
<div class="col-md-12">
- <p t-field="track.description"/>
+ <div t-field="track.description"/>
</div>
<div class="col-md-12">
<h3>About The Author</h3>
- <p t-field="track.partner_biography"/>
+ <div t-field="track.partner_biography"/>
</div>
</div>
</div>
diff --git a/addons/website_sale/controllers/backend.py b/addons/website_sale/controllers/backend.py
index 98a5a06b342..303d5d3e3e8 100644
--- a/addons/website_sale/controllers/backend.py
+++ b/addons/website_sale/controllers/backend.py
@@ -141,7 +141,7 @@ class WebsiteSaleBackend(WebsiteBackend):
sales_graph = [{
'0': fields.Date.to_string(d) if not previous else fields.Date.to_string(d + timedelta(days=days_between)),
# Respect read_group format in models.py
- '1': daily_sales_dict.get(babel.dates.format_date(d, format='dd MMM yyyy', locale=request.env.context.get('lang', 'en_US')), 0)
+ '1': daily_sales_dict.get(babel.dates.format_date(d, format='dd MMM yyyy', locale=request.env.context.get('lang') or 'en_US'), 0)
} for d in date_list]
return sales_graph
diff --git a/addons/website_slides/controllers/main.py b/addons/website_slides/controllers/main.py
index f441d9abf98..9e934d08ad0 100644
--- a/addons/website_slides/controllers/main.py
+++ b/addons/website_slides/controllers/main.py
@@ -258,10 +258,8 @@ class WebsiteSlides(http.Controller):
@http.route(['/slides/add_slide'], type='json', auth='user', methods=['POST'], website=True)
def create_slide(self, *args, **post):
- payload = request.httprequest.content_length
- # payload is total request content size so it's not exact size of file.
- # already add client validation this is for double check if client alter.
- if (payload / 1024 / 1024 > 25):
+ file_size = len(post['datas']) * 3 / 4; # base64
+ if (file_size / 1024.0 / 1024.0) > 25:
return {'error': _('File is too big. File size cannot exceed 25MB')}
values = dict((fname, post[fname]) for fname in [
diff --git a/doc/cla/corporate/niboo.md b/doc/cla/corporate/niboo.md
new file mode 100644
index 00000000000..c1ec9af21c8
--- /dev/null
+++ b/doc/cla/corporate/niboo.md
@@ -0,0 +1,21 @@
+Belgium, 2017-09-25
+
+Niboo 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,
+
+Samuel Lefever sam@niboo.be https://github.com/samuellefever
+
+List of contributors:
+
+Samuel Lefever sam@niboo.be https://github.com/samuellefever
+Pierre Faniel pierre@niboo.be https://github.com/PierreFaniel
+Jerome Guerriat jerome@niboo.be https://github.com/jguerriat
+Jeremy Van Driessche jeremy@niboo.be https://github.com/jvandri
+Gael Rabier gael@niboo.be https://github.com/gaelrabier
+Tobias Zehntner tobias@niboo.be https://github.com/twobeers83
+Alexandre Dutry alexandre@nboo.be https://github.com/AlexandreDz
diff --git a/doc/cla/individual/YasserKadddour.md b/doc/cla/individual/YasserKadddour.md
new file mode 100644
index 00000000000..c1f6801bc13
--- /dev/null
+++ b/doc/cla/individual/YasserKadddour.md
@@ -0,0 +1,11 @@
+Algeria, 2017-09-22
+
+I hereby agree to the terms of the Odoo Individual Contributor License
+Agreement v1.0.
+
+I declare that I am authorized and able to make this agreement and sign this
+declaration.
+
+Signed,
+
+Yasser Kaddour yasser.kaddour@gmail.com https://github.com/YasserKaddour
diff --git a/odoo/addons/base/res/res_currency_data.xml b/odoo/addons/base/res/res_currency_data.xml
index ee793d3f25d..75efdb752a9 100644
--- a/odoo/addons/base/res/res_currency_data.xml
+++ b/odoo/addons/base/res/res_currency_data.xml
@@ -1006,7 +1006,7 @@
<record id="DZD" model="res.currency">
<field name="name">DZD</field>
- <field name="symbol">DZ</field>
+ <field name="symbol">DA</field>
<field name="rounding">0.01</field>
<field name="active" eval="False"/>
<field name="currency_unit_label">Dinar</field>
diff --git a/odoo/addons/base/tests/test_expression.py b/odoo/addons/base/tests/test_expression.py
index ded008afa07..e18e0b1240e 100644
--- a/odoo/addons/base/tests/test_expression.py
+++ b/odoo/addons/base/tests/test_expression.py
@@ -688,7 +688,7 @@ class TestAutoJoin(TransactionCase):
self.assertIn('res_partner_bank', sql_query[0],
"_auto_join off: ('bank_ids.sanitized_acc_number', 'like', '..') first query incorrect main table")
- expected = "%s::text like %s" % (unaccent('"res_partner_bank"."sanitized_acc_number"'), unaccent('%s'))
+ expected = "%s like %s" % (unaccent('"res_partner_bank"."sanitized_acc_number"::text'), unaccent('%s'))
self.assertIn(expected, sql_query[1],
"_auto_join off: ('bank_ids.sanitized_acc_number', 'like', '..') first query incorrect where condition")
@@ -728,7 +728,7 @@ class TestAutoJoin(TransactionCase):
self.assertIn('"res_partner_bank" as "res_partner__bank_ids"', sql_query[0],
"_auto_join on: ('bank_ids.sanitized_acc_number', 'like', '..') query incorrect join")
- expected = "%s::text like %s" % (unaccent('"res_partner__bank_ids"."sanitized_acc_number"'), unaccent('%s'))
+ expected = "%s like %s" % (unaccent('"res_partner__bank_ids"."sanitized_acc_number"::text'), unaccent('%s'))
self.assertIn(expected, sql_query[1],
"_auto_join on: ('bank_ids.sanitized_acc_number', 'like', '..') query incorrect where condition")
@@ -811,7 +811,7 @@ class TestAutoJoin(TransactionCase):
self.assertIn('"res_country"', sql_query[0],
"_auto_join on for state_id: ('state_id.country_id.code', 'like', '..') query 1 incorrect main table")
- expected = "%s::text like %s" % (unaccent('"res_country"."code"'), unaccent('%s'))
+ expected = "%s like %s" % (unaccent('"res_country"."code"::text'), unaccent('%s'))
self.assertIn(expected, sql_query[1],
"_auto_join on for state_id: ('state_id.country_id.code', 'like', '..') query 1 incorrect where condition")
@@ -845,7 +845,7 @@ class TestAutoJoin(TransactionCase):
self.assertIn('"res_country" as "res_country_state__country_id"', sql_query[0],
"_auto_join on for country_id: ('state_id.country_id.code', 'like', '..') query 1 incorrect join")
- expected = "%s::text like %s" % (unaccent('"res_country_state__country_id"."code"'), unaccent('%s'))
+ expected = "%s like %s" % (unaccent('"res_country_state__country_id"."code"::text'), unaccent('%s'))
self.assertIn(expected, sql_query[1],
"_auto_join on for country_id: ('state_id.country_id.code', 'like', '..') query 1 incorrect where condition")
@@ -879,7 +879,7 @@ class TestAutoJoin(TransactionCase):
self.assertIn('"res_country" as "res_partner__state_id__country_id"', sql_query[0],
"_auto_join on: ('state_id.country_id.code', 'like', '..') query incorrect join")
- expected = "%s::text like %s" % (unaccent('"res_partner__state_id__country_id"."code"'), unaccent('%s'))
+ expected = "%s like %s" % (unaccent('"res_partner__state_id__country_id"."code"::text'), unaccent('%s'))
self.assertIn(expected, sql_query[1],
"_auto_join on: ('state_id.country_id.code', 'like', '..') query incorrect where condition")
@@ -909,7 +909,7 @@ class TestAutoJoin(TransactionCase):
# Test produced queries that domains effectively present
sql_query = self.query_list[0].get_sql()
- expected = "%s::text like %s" % (unaccent('"res_partner__child_ids__bank_ids"."sanitized_acc_number"'), unaccent('%s'))
+ expected = "%s like %s" % (unaccent('"res_partner__child_ids__bank_ids"."sanitized_acc_number"::text'), unaccent('%s'))
self.assertIn(expected, sql_query[1],
"_auto_join on one2many with domains incorrect result")
# TDE TODO: check first domain has a correct table name
diff --git a/odoo/addons/test_assetsbundle/tests/test_assetsbundle.py b/odoo/addons/test_assetsbundle/tests/test_assetsbundle.py
index c8ed48a33b1..86c878b35e2 100644
--- a/odoo/addons/test_assetsbundle/tests/test_assetsbundle.py
+++ b/odoo/addons/test_assetsbundle/tests/test_assetsbundle.py
@@ -2,6 +2,8 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from collections import Counter
+import datetime
+import errno
from os import utime
import time
@@ -12,6 +14,21 @@ from odoo.tests import HttpCase
from odoo.tests.common import TransactionCase
+def _touch(filepath, asset, t=None):
+ try:
+ utime(filepath, (t, t) if t else None)
+ except OSError as e:
+ if e.errno in [errno.EPERM, errno.EACCES, errno.EROFS]:
+ # Permission denied when touching the asset file, possibly read-only filesystem.
+ # We alter the asset last modified time to simulate a change in the file
+ now = datetime.datetime.now()
+ asset.last_modified = now
+ for sheet in asset.stylesheets:
+ sheet.last_modified = now
+ else:
+ raise
+
+
class TestJavascriptAssetsBundle(TransactionCase):
def setUp(self):
super(TestJavascriptAssetsBundle, self).setUp()
@@ -82,9 +99,9 @@ class TestJavascriptAssetsBundle(TransactionCase):
version0 = bundle0.version
path = get_resource_path('test_assetsbundle', 'static', 'src', 'js', 'test_jsfile1.js')
- utime(path, None) # touch
-
bundle1 = self._get_asset(self.jsbundle_xmlid)
+ _touch(path, bundle1)
+
bundle1.js()
last_modified1 = bundle1.last_modified
version1 = bundle1.version
@@ -210,9 +227,9 @@ class TestJavascriptAssetsBundle(TransactionCase):
version0 = bundle0.version
path = get_resource_path('test_assetsbundle', 'static', 'src', 'css', 'test_cssfile1.css')
- utime(path, None) # touch
-
bundle1 = self._get_asset(self.cssbundle_xmlid, env=self.env(context={'max_css_rules': 1}))
+ _touch(path, bundle1)
+
bundle1.css()
last_modified1 = bundle1.last_modified
version1 = bundle1.version
@@ -362,10 +379,12 @@ class TestAssetsBundleWithIRAMock(TransactionCase):
self.env['ir.attachment']._patch_method('unlink', unlink)
self.addCleanup(self.env['ir.attachment']._revert_method, 'unlink')
- def _bundle(self, should_create, should_unlink):
- self.counter.clear()
+ def _get_asset(self):
files, remains = self.env['ir.qweb']._get_asset_content(self.lessbundle_xmlid, {})
- asset = AssetsBundle(self.lessbundle_xmlid, files, remains, env=self.env)
+ return AssetsBundle(self.lessbundle_xmlid, files, remains, env=self.env)
+
+ def _bundle(self, asset, should_create, should_unlink):
+ self.counter.clear()
asset.to_html(debug='assets')
self.assertEquals(self.counter['create'], int(should_create))
self.assertEquals(self.counter['unlink'], int(should_unlink))
@@ -375,16 +394,17 @@ class TestAssetsBundleWithIRAMock(TransactionCase):
are correctly invalidated.
"""
# Compile for the first time
- self._bundle(True, False)
+ self._bundle(self._get_asset(), True, False)
# Compile a second time, without changes
- self._bundle(False, False)
+ self._bundle(self._get_asset(), False, False)
# Touch the file and compile a third time
path = get_resource_path('test_assetsbundle', 'static', 'src', 'less', 'test_lessfile1.less')
t = time.time() + 5
- utime(path, (t, t)) # touch
- self._bundle(True, True)
+ asset = self._get_asset()
+ _touch(path, asset, t=t)
+ self._bundle(asset, True, True)
# Because we are in the same transaction since the beginning of the test, the first asset
# created and the second one have the same write_date, but the file's last modified date
@@ -394,4 +414,4 @@ class TestAssetsBundleWithIRAMock(TransactionCase):
self.cr.execute("update ir_attachment set write_date=clock_timestamp() + interval '10 seconds' where id = (select max(id) from ir_attachment)")
# Compile a fourth time, without changes
- self._bundle(False, False)
+ self._bundle(self._get_asset(), False, False)