diff options
| author | Christophe Simonis <chs@odoo.com> | 2017-09-28 13:24:14 +0200 |
|---|---|---|
| committer | Christophe Simonis <chs@odoo.com> | 2017-09-28 13:24:14 +0200 |
| commit | 99bf9e4563d8c17be1635fc09d336b693b5ba1a1 (patch) | |
| tree | eb04654ef5ef384319aa4292bac122b18445689f | |
| parent | 6b410fc954ccd85e7aea2c9fa86c6f2097474883 (diff) | |
| parent | 89df681b458df5b2fd21b738409a90dd53e33c9a (diff) | |
[MERGE] forward port branch saas-16 up to 89df681b458
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')) < 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) |
