diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py index 19121cdd05a47df293eea10d7fd0065f5ec2d01a..44f9c0661ca4fd6d074e1ed6d460758eb3d28e1d 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -451,30 +451,16 @@ class AccountInvoice(models.Model): payment_term_id = False fiscal_position = False bank_id = False - p = self.partner_id company_id = self.company_id.id + p = self.partner_id if not company_id else self.partner_id.with_context(force_company=company_id) type = self.type if p: - partner_id = p.id rec_account = p.property_account_receivable_id pay_account = p.property_account_payable_id - if company_id: - if p.property_account_receivable_id.company_id and \ - p.property_account_receivable_id.company_id.id != company_id and \ - p.property_account_payable_id.company_id and \ - p.property_account_payable_id.company_id.id != company_id: - prop = self.env['ir.property'] - rec_dom = [('name', '=', 'property_account_receivable_id'), ('company_id', '=', company_id)] - pay_dom = [('name', '=', 'property_account_payable_id'), ('company_id', '=', company_id)] - res_dom = [('res_id', '=', 'res.partner,%s' % partner_id)] - rec_prop = prop.search(rec_dom + res_dom) or prop.search(rec_dom) - pay_prop = prop.search(pay_dom + res_dom) or prop.search(pay_dom) - rec_account = rec_prop.get_by_record() - pay_account = pay_prop.get_by_record() - if not rec_account and not pay_account: - action = self.env.ref('account.action_account_config') - msg = _('Cannot find a chart of accounts for this company, You should configure it. \nPlease go to Account Configuration.') - raise RedirectWarning(msg, action.id, _('Go to the configuration panel')) + if not rec_account and not pay_account: + action = self.env.ref('account.action_account_config') + msg = _('Cannot find a chart of accounts for this company, You should configure it. \nPlease go to Account Configuration.') + raise RedirectWarning(msg, action.id, _('Go to the configuration panel')) if type in ('out_invoice', 'out_refund'): account_id = rec_account.id @@ -539,7 +525,7 @@ class AccountInvoice(models.Model): @api.multi def get_formview_id(self): """ Update form view id of action to open the invoice """ - if self.type == 'in_invoice': + if self.type in ('in_invoice', 'in_refund'): return self.env.ref('account.invoice_supplier_form').id else: return self.env.ref('account.invoice_form').id diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index 9fffdc3023678031c9aef6a76e4dec5782329fd9..ec4070178d55ffca7e13d467c5bbee98d0696c98 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -830,7 +830,14 @@ class AccountMoveLine(models.Model): #if writeoff_acc_id specified, then create write-off move with value the remaining amount from move in self if writeoff_acc_id and writeoff_journal_id and remaining_moves: - writeoff_to_reconcile = remaining_moves._create_writeoff({'account_id': writeoff_acc_id.id, 'journal_id': writeoff_journal_id.id}) + all_aml_share_same_currency = all([x.currency_id == self[0].currency_id for x in self]) + writeoff_vals = { + 'account_id': writeoff_acc_id.id, + 'journal_id': writeoff_journal_id.id + } + if not all_aml_share_same_currency: + writeoff_vals['amount_currency'] = False + writeoff_to_reconcile = remaining_moves._create_writeoff(writeoff_vals) #add writeoff line to reconcile algo and finish the reconciliation remaining_moves = (remaining_moves + writeoff_to_reconcile).auto_reconcile_lines() diff --git a/addons/calendar/calendar_data.xml b/addons/calendar/calendar_data.xml index 772f6772971ffb2eb6f3c975f48187479e529677..d02c1028745d83c12296d8fd0eebe9129fd2d831 100644 --- a/addons/calendar/calendar_data.xml +++ b/addons/calendar/calendar_data.xml @@ -118,12 +118,12 @@ <table style="margin-top: 20px;"><tr> <td> <div style="border-top-left-radius:3px;border-top-right-radius:3px;font-size:12px;border-collapse:separate;text-align:center;font-weight:bold;color:#ffffff;width:130px;min-height: 18px;background:#a24689;padding-top: 4px;"> - ${object.event_id.get_interval(object.event_id.start, 'dayname', tz=object.partner_id.tz)} + ${object.event_id.get_interval(object.event_id.start, 'dayname', tz=object.partner_id.tz if not object.event_id.allday else None)} </div> <div style="font-size:48px;min-height:auto;font-weight:bold;text-align:center;color: #5F5F5F;background-color: #F8F8F8;width: 130px;border:1px solid #a24689;"> - ${object.event_id.get_interval(object.event_id.start,'day', tz=object.partner_id.tz)} + ${object.event_id.get_interval(object.event_id.start,'day', tz=object.partner_id.tz if not object.event_id.allday else None)} </div> - <div style='font-size:12px;text-align:center;font-weight:bold;color:#ffffff;background-color:#a24689'>${object.event_id.get_interval(object.event_id.start, 'month', tz=object.partner_id.tz)}</div> + <div style='font-size:12px;text-align:center;font-weight:bold;color:#ffffff;background-color:#a24689'>${object.event_id.get_interval(object.event_id.start, 'month', tz=object.partner_id.tz if not object.event_id.allday else None)}</div> <div style="border-collapse:separate;color: #5F5F5F;text-align:center;width: 130px;font-size:12px;border-bottom-right-radius:3px;font-weight:bold;border:1px solid #a24689;border-bottom-left-radius:3px;">${not object.event_id.allday and object.event_id.get_interval(object.event_id.start, 'time', tz=object.partner_id.tz) or ''}</div> </td> <td width="20px;"/> @@ -210,12 +210,12 @@ <table style="margin-top: 20px;"><tr> <td> <div style="border-top-left-radius:3px;border-top-right-radius:3px;font-size:12px;border-collapse:separate;text-align:center;font-weight:bold;color:#ffffff;width:130px;min-height: 18px;background:#a24689;padding-top: 4px;"> - ${object.event_id.get_interval(object.event_id.start, 'dayname', tz=object.partner_id.tz)} + ${object.event_id.get_interval(object.event_id.start, 'dayname', tz=object.partner_id.tz if not object.event_id.allday else None)} </div> <div style="font-size:48px;min-height:auto;font-weight:bold;text-align:center;color: #5F5F5F;background-color: #F8F8F8;width: 130px;border:1px solid #a24689;"> - ${object.event_id.get_interval(object.event_id.start,'day', tz=object.partner_id.tz)} + ${object.event_id.get_interval(object.event_id.start,'day', tz=object.partner_id.tz if not object.event_id.allday else None)} </div> - <div style='font-size:12px;text-align:center;font-weight:bold;color:#ffffff;background-color:#a24689'>${object.event_id.get_interval(object.event_id.start, 'month', tz=object.partner_id.tz)}</div> + <div style='font-size:12px;text-align:center;font-weight:bold;color:#ffffff;background-color:#a24689'>${object.event_id.get_interval(object.event_id.start, 'month', tz=object.partner_id.tz if not object.event_id.allday else None)}</div> <div style="border-collapse:separate;color: #5F5F5F;text-align:center;width: 130px;font-size:12px;border-bottom-right-radius:3px;font-weight:bold;border:1px solid #a24689;border-bottom-left-radius:3px;">${not object.event_id.allday and object.event_id.get_interval(object.event_id.start, 'time', tz=object.partner_id.tz) or ''}</div> </td> <td width="20px;"/> @@ -302,12 +302,12 @@ <table style="margin-top: 20px;"><tr> <td> <div style="border-top-left-radius:3px;border-top-right-radius:3px;font-size:12px;border-collapse:separate;text-align:center;font-weight:bold;color:#ffffff;width:130px;min-height: 18px;background:#a24689;padding-top: 4px;"> - ${object.event_id.get_interval(object.event_id.start, 'dayname', tz=object.partner_id.tz)} + ${object.event_id.get_interval(object.event_id.start, 'dayname', tz=object.partner_id.tz if not object.event_id.allday else None)} </div> <div style="font-size:48px;min-height:auto;font-weight:bold;text-align:center;color: #5F5F5F;background-color: #F8F8F8;width: 130px;border:1px solid #a24689;"> - ${object.event_id.get_interval(object.event_id.start,'day', tz=object.partner_id.tz)} + ${object.event_id.get_interval(object.event_id.start,'day', tz=object.partner_id.tz if not object.event_id.allday else None)} </div> - <div style='font-size:12px;text-align:center;font-weight:bold;color:#ffffff;background-color:#a24689'>${object.event_id.get_interval(object.event_id.start, 'month', tz=object.partner_id.tz)}</div> + <div style='font-size:12px;text-align:center;font-weight:bold;color:#ffffff;background-color:#a24689'>${object.event_id.get_interval(object.event_id.start, 'month', tz=object.partner_id.tz if not object.event_id.allday else None)}</div> <div style="border-collapse:separate;color: #5F5F5F;text-align:center;width: 130px;font-size:12px;border-bottom-right-radius:3px;font-weight:bold;border:1px solid #a24689;border-bottom-left-radius:3px;">${not object.event_id.allday and object.event_id.get_interval(object.event_id.start, 'time', tz=object.partner_id.tz) or ''}</div> </td> <td width="20px;"/> diff --git a/addons/calendar/calendar_view.xml b/addons/calendar/calendar_view.xml index 2cf77cd216252653931856c8083f3b3ff9149c03..8fbd66a8a93c5ade2f0f71fbfcb30c149d6868b7 100644 --- a/addons/calendar/calendar_view.xml +++ b/addons/calendar/calendar_view.xml @@ -101,18 +101,19 @@ <group> <field name="start" attrs="{'invisible': True}"/> <field name="stop" attrs="{'invisible': True}"/> + <field name="id" attrs="{'invisible': True}"/> - <field name="start_date" string="Starting at" on_change="onchange_dates('start', start_date, stop_date, allday, True)" attrs="{'invisible': [('allday','=',False)]}"/> - <field name="stop_date" string="Ending at" on_change="onchange_dates('stop', start_date, stop_date, allday, True)" attrs="{'invisible': [('allday','=',False)]}"/> + <field name="start_date" string="Starting at" on_change="onchange_dates('start', start_date, stop_date, allday, True)" attrs="{'invisible': [('allday','=',False)], 'readonly': [('id', '!=', False), ('recurrency','=',True)]}"/> + <field name="stop_date" string="Ending at" on_change="onchange_dates('stop', start_date, stop_date, allday, True)" attrs="{'invisible': [('allday','=',False)], 'readonly': [('id', '!=', False), ('recurrency','=',True)]}"/> - <field name="start_datetime" string="Starting at" on_change="onchange_duration(start_datetime, duration)" attrs="{'invisible': [('allday','=',True)]}"/> + <field name="start_datetime" string="Starting at" on_change="onchange_duration(start_datetime, duration)" attrs="{'invisible': [('allday','=',True)], 'readonly': [('id', '!=', False), ('recurrency','=',True)]}"/> <field name="stop_datetime" invisible="1"/> <label for="duration" attrs="{'invisible': [('allday','=',True)]}"/> <div attrs="{'invisible': [('allday','=',True)]}"> - <field name="duration" widget="float_time" string="Duration" on_change="onchange_duration(start_datetime, duration)" class="oe_inline"/> + <field name="duration" widget="float_time" string="Duration" on_change="onchange_duration(start_datetime, duration)" class="oe_inline" attrs="{'readonly': [('id', '!=', False), ('recurrency','=',True)]}"/> <span> hours</span> </div> - <field name="allday" on_change="onchange_allday(start, stop, start_date, stop_date, start_datetime, stop_datetime, allday)"/> + <field name="allday" on_change="onchange_allday(start, stop, start_date, stop_date, start_datetime, stop_datetime, allday)" attrs="{'readonly': [('id', '!=', False), ('recurrency','=',True)]}"/> </group> <group> <field name="categ_ids" widget="many2many_tags" options=" diff --git a/addons/crm/base_partner_merge.py b/addons/crm/base_partner_merge.py index 8baf69a5a7a77d7b455bed26fb9944b382783958..95ad18f044f1518606ffbf12a95eefbb581b3dbf 100644 --- a/addons/crm/base_partner_merge.py +++ b/addons/crm/base_partner_merge.py @@ -306,7 +306,7 @@ class MergePartnerAutomatic(osv.TransientModel): for partner_id in partner_ids: child_ids = child_ids.union(set(proxy.search(cr, uid, [('id', 'child_of', [partner_id])])) - set([partner_id])) if set(partner_ids).intersection(child_ids): - raise osv.except_osv(_('Error'), _("You cannot merge a contact with one of his parent.")) + raise UserError(_("You cannot merge a contact with one of his parent.")) if openerp.SUPERUSER_ID != uid and len(set(partner.email for partner in proxy.browse(cr, uid, partner_ids, context=context))) > 1: raise UserError(_("All contacts must have the same email. Only the Administrator can merge contacts with different emails.")) diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py index 4dee329d92ffb41b8bb55a963a2718df8182fbc4..4f81a01b8515586097e9437cb75146df45cb7f06 100644 --- a/addons/crm/crm_lead.py +++ b/addons/crm/crm_lead.py @@ -458,11 +458,11 @@ class crm_lead(FormatAddress, osv.osv): value = dict(key).get(lead[field_name], lead[field_name]) elif field.type == 'many2one': if lead[field_name]: - value = lead[field_name].name_get()[0][1] + value = lead[field_name].sudo().name_get()[0][1] elif field.type == 'many2many': if lead[field_name]: for val in lead[field_name]: - field_value = val.name_get()[0][1] + field_value = val.sudo().name_get()[0][1] value += field_value + "," else: value = lead[field_name] diff --git a/addons/crm_partner_assign/security/ir.model.access.csv b/addons/crm_partner_assign/security/ir.model.access.csv index e0b29587ff47b01f1c9cacbc0d88e1fc216af765..3f39d776ddc216ac171218252a0249b04d7f135d 100644 --- a/addons/crm_partner_assign/security/ir.model.access.csv +++ b/addons/crm_partner_assign/security/ir.model.access.csv @@ -3,6 +3,8 @@ access_ crm_lead_report_assign,crm.lead.report.assign,model_crm_lead_report_assi access_ crm_lead_report_assign_all,crm.lead.report.assign.all,model_crm_lead_report_assign,base.group_user,1,0,0,0 access_crm_partner_report,crm.partner.report.assign.all,model_crm_partner_report_assign,base.group_sale_salesman,1,0,0,0 access_res_partner_grade,res.partner.grade,model_res_partner_grade,base.group_sale_salesman,1,1,1,0 +access_res_partner_grade_employee,res.partner.grade,model_res_partner_grade,base.group_user,1,0,0,0 +access_res_partner_grade_portal,res.partner.grade,model_res_partner_grade,base.group_portal,1,0,0,0 access_res_partner_grade_public,res.partner.grade,model_res_partner_grade,base.group_public,1,0,0,0 access_res_partner_grade_manager,res.partner.grade.manager,model_res_partner_grade,base.group_sale_manager,1,1,1,1 access_res_partner_activation_user,res.partner.activation.user,model_res_partner_activation,base.group_user,1,0,0,0 diff --git a/addons/gamification/models/goal.py b/addons/gamification/models/goal.py index 1607a6005d1da61500b0bea3454201c6845c2e19..a219d7ddc014adccfb1e3a05e0631d18d7db7d97 100644 --- a/addons/gamification/models/goal.py +++ b/addons/gamification/models/goal.py @@ -129,18 +129,36 @@ class gamification_goal_definition(osv.Model): raise UserError(_("The domain for the definition %s seems incorrect, please check it.\n\n%s") % (definition.name, msg)) return True + def _check_model_validity(self, cr, uid, ids, context=None): + """ make sure the selected field and model are usable""" + for definition in self.browse(cr, uid, ids, context=context): + try: + if not definition.model_id or not definition.field_id: + continue + + model = self.pool[definition.model_id.model] + field = model._fields[definition.field_id.name] + if not field.store: + raise UserError( + _("The model configuration for the definition %s seems incorrect, please check it.\n\n%s not stored") % (definition.name, definition.field_id.name)) + except KeyError, e: + raise UserError( + _("The model configuration for the definition %s seems incorrect, please check it.\n\n%s not found") % (definition.name, e.message)) + def create(self, cr, uid, vals, context=None): res_id = super(gamification_goal_definition, self).create(cr, uid, vals, context=context) if vals.get('computation_mode') in ('count', 'sum'): self._check_domain_validity(cr, uid, [res_id], context=context) - + if vals.get('field_id'): + self._check_model_validity(cr, uid, [res_id], context=context) return res_id def write(self, cr, uid, ids, vals, context=None): res = super(gamification_goal_definition, self).write(cr, uid, ids, vals, context=context) if vals.get('computation_mode', 'count') in ('count', 'sum') and (vals.get('domain') or vals.get('model_id')): self._check_domain_validity(cr, uid, ids, context=context) - + if vals.get('field_id') or vals.get('model_id') or vals.get('batch_mode'): + self._check_model_validity(cr, uid, ids, context=context) return res def on_change_model_id(self, cr, uid, ids, model_id, context=None): @@ -148,7 +166,8 @@ class gamification_goal_definition(osv.Model): if not model_id: return {'domain': {'field_id': expression.FALSE_DOMAIN, 'field_date_id': expression.FALSE_DOMAIN}} model = self.pool['ir.model'].browse(cr, uid, model_id, context=context) - model_fields_domain = ['|', ('model_id', '=', model_id), ('model_id', 'in', model.inherited_model_ids.ids)] + model_fields_domain = [('store', '=', True), + '|', ('model_id', '=', model_id), ('model_id', 'in', model.inherited_model_ids.ids)] model_date_fields_domain = expression.AND([[('ttype', 'in', ('date', 'datetime'))], model_fields_domain]) return {'domain': {'field_id': model_fields_domain, 'field_date_id': model_date_fields_domain}} diff --git a/addons/hr_holidays/hr_holidays.py b/addons/hr_holidays/hr_holidays.py index 32700ef2730749a9b1e68ba06ba24babccf419a4..f0724a74ac953d5cc24ec6e208196b7628d2bd60 100644 --- a/addons/hr_holidays/hr_holidays.py +++ b/addons/hr_holidays/hr_holidays.py @@ -597,14 +597,14 @@ class hr_employee(osv.Model): # Find for holidays status status_ids = type_obj.search(cr, uid, [('limit', '=', False)], context=context) if len(status_ids) != 1 : - raise osv.except_osv(_('Warning!'),_("The feature behind the field 'Remaining Legal Leaves' can only be used when there is only one leave type with the option 'Allow to Override Limit' unchecked. (%s Found). Otherwise, the update is ambiguous as we cannot decide on which leave type the update has to be done. \nYou may prefer to use the classic menus 'Leave Requests' and 'Allocation Requests' located in 'Human Resources \ Leaves' to manage the leave days of the employees if the configuration does not allow to use this field.") % (len(status_ids))) + raise UserError(_("The feature behind the field 'Remaining Legal Leaves' can only be used when there is only one leave type with the option 'Allow to Override Limit' unchecked. (%s Found). Otherwise, the update is ambiguous as we cannot decide on which leave type the update has to be done. \nYou may prefer to use the classic menus 'Leave Requests' and 'Allocation Requests' located in 'Human Resources \ Leaves' to manage the leave days of the employees if the configuration does not allow to use this field.") % (len(status_ids))) status_id = status_ids and status_ids[0] or False if not status_id: return False if diff > 0: leave_id = holiday_obj.create(cr, uid, {'name': _('Allocation for %s') % employee.name, 'employee_id': employee.id, 'holiday_status_id': status_id, 'type': 'add', 'holiday_type': 'employee', 'number_of_days_temp': diff}, context=context) elif diff < 0: - raise osv.except_osv(_('Warning!'), _('You cannot reduce validated allocation requests')) + raise UserError(_('You cannot reduce validated allocation requests')) else: return False for sig in ('confirm', 'validate', 'second_validate'): diff --git a/addons/hr_payroll/report/report_payslip_details.py b/addons/hr_payroll/report/report_payslip_details.py index f5858f8e7ef11ec04b0d4b777a8f407042ef1ac7..5cc1af7ba65cde1013a205986610c13e091ee40e 100644 --- a/addons/hr_payroll/report/report_payslip_details.py +++ b/addons/hr_payroll/report/report_payslip_details.py @@ -18,13 +18,16 @@ class payslip_details_report(report_sxw.rml_parse): payslip_line = self.pool.get('hr.payslip.line') rule_cate_obj = self.pool.get('hr.salary.rule.category') - def get_recursive_parent(rule_categories): - if not rule_categories: - return [] - if rule_categories[0].parent_id: - rule_categories = rule_categories[0].parent_id | rule_categories - get_recursive_parent(rule_categories) - return rule_categories + def get_recursive_parent(current_rule_category, rule_categories = None): + if rule_categories: + rule_categories = current_rule_category | rule_categories + else: + rule_categories = current_rule_category + + if current_rule_category.parent_id: + return get_recursive_parent(current_rule_category.parent_id, rule_categories) + else: + return rule_categories res = [] result = {} diff --git a/addons/im_livechat/i18n/im_livechat.pot b/addons/im_livechat/i18n/im_livechat.pot index 724c5cb037d1ab8516b3cfcb0810e1fae366dd50..bdcb126875c90966b7fde28fb4054c1c06010c40 100644 --- a/addons/im_livechat/i18n/im_livechat.pot +++ b/addons/im_livechat/i18n/im_livechat.pot @@ -4,10 +4,10 @@ # msgid "" msgstr "" -"Project-Id-Version: Odoo Server 9.0\n" +"Project-Id-Version: Odoo Server 9.0c\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-10-09 09:20+0000\n" -"PO-Revision-Date: 2015-10-09 09:20+0000\n" +"POT-Creation-Date: 2016-05-26 12:06+0000\n" +"PO-Revision-Date: 2016-05-26 12:06+0000\n" "Last-Translator: <>\n" "Language-Team: \n" "MIME-Version: 1.0\n" @@ -15,19 +15,6 @@ msgstr "" "Content-Transfer-Encoding: \n" "Plural-Forms: \n" -#. module: im_livechat -#. openerp-web -#: code:addons/im_livechat/static/src/js/im_livechat.js:222 -#, python-format -msgid " for the following reason: %s" -msgstr "" - -#. module: im_livechat -#: model:ir.ui.view,arch_db:im_livechat.loader -msgid "\", {modules:modules, use_cors: false});\n" -" });" -msgstr "" - #. module: im_livechat #: model:ir.model.fields,field_description:im_livechat.field_im_livechat_report_operator_nbr_channel msgid "# of channel" @@ -44,20 +31,6 @@ msgstr "" msgid "% Happy" msgstr "" -#. module: im_livechat -#: model:ir.ui.view,arch_db:im_livechat.support_page -msgid "<!DOCTYPE html>" -msgstr "" - -#. module: im_livechat -#: model:ir.ui.view,arch_db:im_livechat.loader -msgid ");\n" -" button.appendTo($('body'));\n" -" window.livechat_button = button;\n" -" });\n" -" });" -msgstr "" - #. module: im_livechat #: model:ir.model.fields,help:im_livechat.field_im_livechat_channel_rule_action msgid "* Select 'Display the button' to simply display the chat button on the pages.\n" @@ -87,11 +60,17 @@ msgstr "" #. module: im_livechat #. openerp-web -#: code:addons/im_livechat/static/src/js/im_livechat.js:26 +#: code:addons/im_livechat/static/src/js/im_livechat.js:27 #, python-format msgid "Ask something ..." msgstr "" +#. module: im_livechat +#: model:ir.ui.view,arch_db:im_livechat.mail_channel_view_form +#: model:ir.ui.view,arch_db:im_livechat.mail_channel_view_tree +msgid "Attendees" +msgstr "" + #. module: im_livechat #: selection:im_livechat.channel.rule,action:0 msgid "Auto popup" @@ -176,6 +155,7 @@ msgstr "" #. module: im_livechat #: model:ir.model.fields,field_description:im_livechat.field_im_livechat_report_channel_technical_name +#: model:ir.ui.view,arch_db:im_livechat.im_livechat_report_channel_view_search msgid "Code" msgstr "" @@ -187,7 +167,7 @@ msgstr "" #. module: im_livechat #: model:ir.ui.view,arch_db:im_livechat.im_livechat_channel_view_form -msgid "Copy and paste this code into your website, within the &lt;head&gt; tag:" +msgid "Copy and paste this code into your website, within the <head> tag:" msgstr "" #. module: im_livechat @@ -290,26 +270,16 @@ msgstr "" #. module: im_livechat #. openerp-web -#: code:addons/im_livechat/static/src/xml/im_livechat.xml:16 +#: code:addons/im_livechat/static/src/xml/im_livechat.xml:19 #, python-format msgid "Explain your note" msgstr "" -#. module: im_livechat -#: model:ir.ui.view,arch_db:im_livechat.mail_channel_view_form -msgid "Feedback" -msgstr "" - #. module: im_livechat #: model:ir.ui.view,arch_db:im_livechat.im_livechat_channel_view_form msgid "For website built with Odoo CMS, please install the website_livechat module. Then go to Settings > Website Settings and select the Website Live Chat Channel you want to add on your website." msgstr "" -#. module: im_livechat -#: model:ir.ui.view,arch_db:im_livechat.mail_channel_view_form -msgid "General" -msgstr "" - #. module: im_livechat #: model:ir.model.fields,help:im_livechat.field_im_livechat_channel_rule_sequence msgid "Given the order to find a matching rule. If 2 rules are matching for the given url/country, the one with the lowest sequence will be chosen." @@ -361,9 +331,9 @@ msgstr "" #. module: im_livechat #. openerp-web -#: code:addons/im_livechat/static/src/js/im_livechat.js:220 +#: code:addons/im_livechat/static/src/xml/im_livechat.xml:15 #, python-format -msgid "I rated you with :rating_%d" +msgid "I don't want to rate this conversation" msgstr "" #. module: im_livechat @@ -579,6 +549,11 @@ msgstr "" msgid "Options" msgstr "" +#. module: im_livechat +#: model:ir.model.fields,help:im_livechat.field_im_livechat_channel_rating_percentage_satisfaction +msgid "Percentage of happy ratings over the past 7 days" +msgstr "" + #. module: im_livechat #: model:ir.ui.view,arch_db:im_livechat.im_livechat_channel_view_kanban msgid "Quit" @@ -590,6 +565,13 @@ msgstr "" msgid "Rating" msgstr "" +#. module: im_livechat +#. openerp-web +#: code:addons/im_livechat/static/src/js/im_livechat.js:308 +#, python-format +msgid "Rating: :rating_%d" +msgstr "" + #. module: im_livechat #: model:ir.model.fields,help:im_livechat.field_im_livechat_channel_rule_regex_url msgid "Regular expression identifying the web page on which the rules will be applied." @@ -623,8 +605,9 @@ msgid "Search report" msgstr "" #. module: im_livechat -#: model:ir.ui.view,arch_db:im_livechat.im_livechat_report_channel_view_search -msgid "Session" +#: model:ir.ui.view,arch_db:im_livechat.mail_channel_view_form +#: model:ir.ui.view,arch_db:im_livechat.mail_channel_view_tree +msgid "Session Date" msgstr "" #. module: im_livechat @@ -777,11 +760,6 @@ msgid "You can create channels for each website on which you want\n" " visitors to talk in real time with your operators." msgstr "" -#. module: im_livechat -#: model:ir.ui.view,arch_db:im_livechat.loader -msgid "document.addEventListener(\"DOMContentLoaded\", function(event) {" -msgstr "" - #. module: im_livechat #: model:ir.ui.view,arch_db:im_livechat.im_livechat_channel_view_form msgid "e.g. Hello, how may I help you?" @@ -792,30 +770,6 @@ msgstr "" msgid "e.g. YourWebsite.com" msgstr "" -#. module: im_livechat -#. openerp-web -#: code:addons/im_livechat/static/src/xml/im_livechat_backend.xml:6 -#, python-format -msgid "livechat" -msgstr "" - -#. module: im_livechat -#: model:ir.ui.view,arch_db:im_livechat.loader -msgid "odoo.define('im_livechat.livesupport', function (require) {\n" -" var im_livechat = require('im_livechat.im_livechat');\n" -" var button = new im_livechat.LivechatButton(\n" -" $('body'),\n" -" \"" -msgstr "" - -#. module: im_livechat -#: model:ir.ui.view,arch_db:im_livechat.loader -msgid "odoo.define('web.session', function (require) {\n" -" var Session = require('web.Session');\n" -" var modules = odoo._modules;\n" -" return new Session(undefined, \"" -msgstr "" - #. module: im_livechat #: model:ir.ui.view,arch_db:im_livechat.im_livechat_channel_view_form msgid "or copy this url and send it by email to your customers or suppliers:" @@ -826,11 +780,3 @@ msgstr "" msgid "seconds" msgstr "" -#. module: im_livechat -#. openerp-web -#: code:addons/im_livechat/static/src/xml/im_livechat_backend.xml:9 -#: code:addons/im_livechat/static/src/xml/im_livechat_backend.xml:12 -#, python-format -msgid "true" -msgstr "" - diff --git a/addons/im_livechat/views/im_livechat_channel_templates.xml b/addons/im_livechat/views/im_livechat_channel_templates.xml index 8b771e1bc61ebb6df89b355151ee90af138a2c44..bbb4c9663b67604c5ade1d2591f8d186c321ff95 100644 --- a/addons/im_livechat/views/im_livechat_channel_templates.xml +++ b/addons/im_livechat/views/im_livechat_channel_templates.xml @@ -136,11 +136,11 @@ <!-- the js code to initialize the LiveSupport object --> <template id="loader" name="Livechat : Javascript appending the livechat button"> + <t t-translation="off"> <t t-if="not info"> <t t-set="info" t-value="request.env['im_livechat.channel'].get_livechat_info(channel)"/> </t> - document.addEventListener("DOMContentLoaded", function(event) { <t t-if="web_session_required"> odoo.define('web.session', function (require) { @@ -163,6 +163,7 @@ </t> }); }); + </t> </template> diff --git a/addons/mail/static/src/js/thread.js b/addons/mail/static/src/js/thread.js index 446333666ccecc6d227511ad75e9a3a9419e6667..ea593d21dfa102ab4c9a459bc83d374dcf11102b 100644 --- a/addons/mail/static/src/js/thread.js +++ b/addons/mail/static/src/js/thread.js @@ -110,29 +110,87 @@ var Thread = Widget.extend({ })); _.each(msgs, function(msg) { - self.$('[data-message-id="' + msg.id + '"] .o_mail_timestamp') - .data('date', msg.date); + var $msg = self.$('.o_thread_message[data-message-id="'+ msg.id +'"]'); + $msg.find('.o_mail_timestamp').data('date', msg.date); + + self.insert_read_more($msg); }); - this.$('[data-o-mail-quote="1"]').each(function () { - var $content = $(this); - var $read_more = $('<a class="o_mail_read_more" href="#"></a>').text(read_more); + if (!this.update_timestamps_interval) { + this.update_timestamps_interval = setInterval(function() { + self.update_timestamps(); + }, 1000*60); + } + }, + + /** + * Modifies $element to add the 'read more/read less' functionality + * All element nodes with "data-o-mail-quote" attribute are concerned. + * All text nodes after a ""#stopSpelling" element are concerned. + * Those text nodes need to be wrapped in a span (toggle functionality). + * All consecutive elements are joined in one 'read more/read less'. + */ + insert_read_more: function ($element) { + var self = this; + + var groups = []; + var read_more_nodes; + + // nodeType 1: element_node + // nodeType 3: text_node + var $children = $element.contents() + .filter(function() { + return this.nodeType === 1 || this.nodeType === 3 && this.nodeValue.trim(); + }); + + _.each($children, function(child) { + var $child = $(child); + + // Hide Text nodes if "stopSpelling" + if (child.nodeType === 3 && $child.prevAll("[id*='stopSpelling']").length > 0) { + // Convert Text nodes to Element nodes + var $child = $('<span>', { + text: child.textContent, + "data-o-mail-quote": "1", + }); + child.parentNode.replaceChild($child[0], child); + } + + // Create array for each "read more" with nodes to toggle + if ($child.attr('data-o-mail-quote') || ($child.get(0).nodeName === 'BR' && $child.prev("[data-o-mail-quote='1']").length > 0)) { + if (!read_more_nodes) { + read_more_nodes = []; + groups.push(read_more_nodes); + } + $child.hide(); + read_more_nodes.push($child); + } else { + read_more_nodes = undefined; + self.insert_read_more($child); + } + }); + + _.each(groups, function(group) { + // Insert link just before the first node + var $read_more = $('<a>', { + class: "o_mail_read_more", + href: "#", + text: read_more, + }).insertBefore(group[0]); + + // Toggle All next nodes var is_read_more = true; $read_more.click(function(e) { e.preventDefault(); is_read_more = !is_read_more; - $content.toggle(!is_read_more); + _.each(group, function ($child) { + $child.hide(); + $child.toggle(!is_read_more); + }); $read_more.text(is_read_more ? read_more : read_less); }); - $read_more.insertBefore($content); }); - if (!this.update_timestamps_interval) { - this.update_timestamps_interval = setInterval(function() { - self.update_timestamps(); - }, 1000*60); - } }, - update_timestamps: function () { this.$('.o_mail_timestamp').each(function() { var date = $(this).data('date'); diff --git a/addons/mail/static/src/less/thread.less b/addons/mail/static/src/less/thread.less index a70d9954a705dc566abaa3f12e8154dce621d698..5da7d2b496387965459e5bbe6b76911fdc317f1b 100644 --- a/addons/mail/static/src/less/thread.less +++ b/addons/mail/static/src/less/thread.less @@ -161,9 +161,6 @@ .o-webclient-padding(); } - [data-o-mail-quote="1"] { - display: none; - } .o_thread_message .o_thread_message_core .o_mail_read_more { display: block; } diff --git a/addons/point_of_sale/static/src/js/screens.js b/addons/point_of_sale/static/src/js/screens.js index 1ca3fb5dfdfa5cd0d8d831948faca4041a49d8cf..ee28d1a393e514c6b505daafd2b38e863ebfd0ee 100644 --- a/addons/point_of_sale/static/src/js/screens.js +++ b/addons/point_of_sale/static/src/js/screens.js @@ -131,6 +131,8 @@ var ScreenWidget = PosBaseWidget.extend({ this.pos.barcode_reader.set_action_callback({ 'cashier': _.bind(self.barcode_cashier_action, self), 'product': _.bind(self.barcode_product_action, self), + 'weight': _.bind(self.barcode_product_action, self), + 'price': _.bind(self.barcode_product_action, self), 'client' : _.bind(self.barcode_client_action, self), 'discount': _.bind(self.barcode_discount_action, self), 'error' : _.bind(self.barcode_error_action, self), diff --git a/addons/product_visible_discount/models/sale_order.py b/addons/product_visible_discount/models/sale_order.py index 75aab6a4f4fe3779abd6667df5e495b32bcde9ca..82e633b947bff620afd0b43a8b79fd0454425424 100644 --- a/addons/product_visible_discount/models/sale_order.py +++ b/addons/product_visible_discount/models/sale_order.py @@ -24,14 +24,15 @@ class SaleOrderLine(models.Model): field_name = 'standard_price' currency_id = pricelist_item.pricelist_id.currency_id + product_currency = (product.company_id and product.company_id.currency_id) or self.env.user.company_id.currency_id if not currency_id: - currency_id = product.company_id.currency_id + currency_id = product_currency cur_factor = 1.0 else: - if currency_id.id == product.company_id.currency_id.id: + if currency_id.id == product_currency.id: cur_factor = 1.0 else: - cur_factor = currency_id._get_conversion_rate(product.company_id.currency_id, currency_id) + cur_factor = currency_id._get_conversion_rate(product_currency, currency_id) product_uom = self.env.context.get('uom') or product.uom_id.id if uom and uom.id != product_uom: diff --git a/addons/report/models/report.py b/addons/report/models/report.py index 7f25a308df909c0556856a922c58a8e7b19300f2..2af640df6a660e0be4b2a7325ea1afb3b1a302fa 100644 --- a/addons/report/models/report.py +++ b/addons/report/models/report.py @@ -50,7 +50,7 @@ except (OSError, IOError): else: _logger.info('Will use the Wkhtmltopdf binary at %s' % _get_wkhtmltopdf_bin()) out, err = process.communicate() - version = re.search('([0-9.]+)', out).group(0) + version = re.search('([0-9.]+)', out or "0").group(0) if LooseVersion(version) < LooseVersion('0.12.0'): _logger.info('Upgrade Wkhtmltopdf to (at least) 0.12.0') wkhtmltopdf_state = 'upgrade' diff --git a/addons/sale/sale.py b/addons/sale/sale.py index 528605fc0c0fc69f8c93d76eb2a7a0fd49dba079..ba3c3dbc70ef28c0745b5c6432ef08e731303324 100644 --- a/addons/sale/sale.py +++ b/addons/sale/sale.py @@ -601,17 +601,9 @@ class SaleOrderLine(models.Model): def _compute_tax_id(self): for line in self: fpos = line.order_id.fiscal_position_id or line.order_id.partner_id.property_account_position_id - if fpos: - # The superuser is used by website_sale in order to create a sale order. We need to make - # sure we only select the taxes related to the company of the partner. This should only - # apply if the partner is linked to a company. - if self.env.uid == SUPERUSER_ID and line.order_id.company_id: - taxes = fpos.map_tax(line.product_id.taxes_id).filtered(lambda r: r.company_id == line.order_id.company_id) - else: - taxes = fpos.map_tax(line.product_id.taxes_id) - line.tax_id = taxes - else: - line.tax_id = line.product_id.taxes_id if line.product_id.taxes_id else False + # If company_id is set, always filter taxes by the company + taxes = line.product_id.taxes_id.filtered(lambda r: not line.company_id or r.company_id == line.company_id) + line.tax_id = fpos.map_tax(taxes) if fpos else taxes @api.multi def _prepare_order_line_procurement(self, group_id=False): diff --git a/addons/stock_account/stock_account.py b/addons/stock_account/stock_account.py index ab4b2bf915aee968b1d4780583ded46286dd834f..e98161804d4b3a5ab04267c7adbfe5a948c52c23 100644 --- a/addons/stock_account/stock_account.py +++ b/addons/stock_account/stock_account.py @@ -261,7 +261,7 @@ class stock_quant(osv.osv): :param context: context dictionary that can explicitly mention the company to consider via the 'force_company' key :returns: journal_id, source account, destination account, valuation account - :raise: osv.except_osv() is any mandatory account or journal is not defined. + :raise: openerp.exceptions.UserError if any mandatory account or journal is not defined. """ product_obj = self.pool.get('product.template') accounts = product_obj.browse(cr, uid, move.product_id.product_tmpl_id.id, context).get_product_accounts() @@ -438,12 +438,14 @@ class stock_move(osv.osv): else: tmpl_dict[product_id] = 0 product_avail = qty_available + # if the incoming move is for a purchase order with foreign currency, need to call this to get the same value that the quant will use. + price_unit = self.pool.get('stock.move').get_price_unit(cr, uid, move, context=context) if product_avail <= 0: - new_std_price = move.price_unit + new_std_price = price_unit else: # Get the standard price amount_unit = product.standard_price - new_std_price = ((amount_unit * product_avail) + (move.price_unit * move.product_qty)) / (product_avail + move.product_qty) + new_std_price = ((amount_unit * product_avail) + (price_unit * move.product_qty)) / (product_avail + move.product_qty) tmpl_dict[product_id] += move.product_qty # Write the standard price, as SUPERUSER_ID because a warehouse manager may not have the right to write on products ctx = dict(context or {}, force_company=move.company_id.id) diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index 51afa847b141993d8ff19cb9a9a313465580b971..ef53f8fb12c93452b7bc396fecf1305808e55c8e 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -693,7 +693,7 @@ <span class="o_count"/> <button class="btn btn-default" type="button" t-att-accesskey="widget.node.attrs.accesskey"/> - <input t-if="widget.debug" type="text" class="o_form_input o_debug_input"/> + <input t-if="widget.debug" t-att-readonly="widget.get('effective_readonly')" type="text" class="o_form_input o_debug_input"/> </div> </t> <t t-name="web.datepicker"> diff --git a/addons/web_editor/static/src/js/snippets.options.js b/addons/web_editor/static/src/js/snippets.options.js index 811d0e9c1752f0a3da07f3b0303d8ab7bd2d0448..3f933f4c191512b5117ac8cd558ef080ec3d5b55 100644 --- a/addons/web_editor/static/src/js/snippets.options.js +++ b/addons/web_editor/static/src/js/snippets.options.js @@ -344,8 +344,11 @@ odoo.define('web_editor.snippets.options', function (require) { self.$target.removeClass(self.classes).addClass('bg-' + $(this).data("color")); }) .mouseleave(function () { - self.$target.removeClass(self.classes) - .addClass('bg-' + $colors.filter(".selected").data("color")); + self.$target.removeClass(self.classes); + var $selected = $colors.filter(".selected"); + if ($selected.length) { + self.$target.addClass('bg-' + $selected.data("color")); + } }) .click(function () { $colors.removeClass("selected"); @@ -355,6 +358,7 @@ odoo.define('web_editor.snippets.options', function (require) { this.$el.find('.note-color-reset').on('click', function () { self.$target.removeClass(self.classes); + $colors.removeClass("selected"); }); } }); diff --git a/addons/web_editor/static/src/less/web_editor.ui.less b/addons/web_editor/static/src/less/web_editor.ui.less index df96dac896d6175e0c41d508b57d771a0a94125c..57caa2dda0e8a5b6193ac438213a04bc6e70ddb5 100644 --- a/addons/web_editor/static/src/less/web_editor.ui.less +++ b/addons/web_editor/static/src/less/web_editor.ui.less @@ -52,6 +52,20 @@ } } +// Translations +.oe_translate_examples li { + margin: 10px; + padding: 4px; +} +html[lang] > body.editor_enable{ + [data-oe-translation-state] { + background: rgba(255, 255, 90, 0.5) !important; + } + [data-oe-translation-state="translated"] { + background: rgba(120, 215, 110, 0.5) !important; + } +} + // SNIPPET PANEL #oe_snippets { .o-flex-display(); diff --git a/addons/website/models/ir_qweb.py b/addons/website/models/ir_qweb.py index ea0c6e334388ea432f55cea290bff392035e00f9..fcfb93b59c07da130d34053e6e178618a7804d71 100644 --- a/addons/website/models/ir_qweb.py +++ b/addons/website/models/ir_qweb.py @@ -31,18 +31,18 @@ class QWeb(orm.AbstractModel): if not context.get('rendering_bundle'): if name == self.URL_ATTRS.get(element.tag) and qwebcontext.get('url_for'): value = qwebcontext.get('url_for')(value) - elif request and request.website and request.website.cdn_activated and (name == self.URL_ATTRS.get(element.tag) or name == self.CDN_TRIGGERS.get(element.tag)): + elif request and getattr(request, 'website', None) and request.website.cdn_activated and (name == self.URL_ATTRS.get(element.tag) or name == self.CDN_TRIGGERS.get(element.tag)): value = request.website.get_cdn_url(value) return super(QWeb, self).render_attribute(element, name, value, qwebcontext) def render_text(self, text, element, qwebcontext): - compress = request and not request.debug and request.website and request.website.compress_html + compress = request and not request.debug and getattr(request, 'website', None) and request.website.compress_html if compress and element.tag not in self.PRESERVE_WHITESPACE: text = self.re_remove_spaces.sub(' ', text) return super(QWeb, self).render_text(text, element, qwebcontext) def render_tail(self, tail, element, qwebcontext): - compress = request and not request.debug and request.website and request.website.compress_html + compress = request and not request.debug and getattr(request, 'website', None) and request.website.compress_html if compress and element.getparent().tag not in self.PRESERVE_WHITESPACE: # No need to recurse because those tags children are not html5 parser friendly tail = self.re_remove_spaces.sub(' ', tail.rstrip()) diff --git a/addons/website_forum/static/src/js/website_forum.js b/addons/website_forum/static/src/js/website_forum.js index 22536d70d94078628a4689fbfb81f6d200af12ae..c2b45cdb065403128db424662adab143fb72d846 100644 --- a/addons/website_forum/static/src/js/website_forum.js +++ b/addons/website_forum/static/src/js/website_forum.js @@ -20,7 +20,7 @@ if(!$('.website_forum').length) { ev.preventDefault(); var $warning = $('<div class="alert alert-danger alert-dismissable oe_forum_alert" id="karma_alert">'+ '<button type="button" class="close notification_close" data-dismiss="alert" aria-hidden="true">×</button>'+ - karma + _t(' karma is required to perform this action. You can earn karma by having your answers upvoted by the community.') + '</div>'); + karma + ' ' + _t(' karma is required to perform this action. You can earn karma by having your answers upvoted by the community.') + '</div>'); var vote_alert = $(ev.currentTarget).parent().find("#vote_alert"); if (vote_alert.length == 0) { $(ev.currentTarget).parent().append($warning); diff --git a/addons/website_mail/views/website_mail_templates.xml b/addons/website_mail/views/website_mail_templates.xml index d0df8e6b5a1e059ae10ef3d273cac55fe59140af..b65a19b327118c98831c41c2c81f2412cce95dea 100644 --- a/addons/website_mail/views/website_mail_templates.xml +++ b/addons/website_mail/views/website_mail_templates.xml @@ -84,6 +84,14 @@ <t t-set="object" t-value="message"/> <t t-call="website.publish_short"/> <div t-field="message.body"/> + <div class="o_mg_link_content"> + <div class="col-md-2 col-sm-3 text-center" t-foreach='message.attachment_ids' t-as='attachment'> + <a t-attf-href="/web/content/#{attachment.id}?download=true" target="_blank"> + <div class='oe_attachment_embedded o_image' t-att-title="attachment.name" t-att-data-mimetype="attachment.mimetype" t-attf-data-src="/web/image/#{attachment.id}/100x80"/> + <div class='oe_attachment_name'><t t-raw='attachment.name' /></div> + </a> + </div> + </div> </div> </div> </li> diff --git a/addons/website_project_issue/views/project_issue_templates.xml b/addons/website_project_issue/views/project_issue_templates.xml index 67729ac99e3e2d4c902759e752debadff726729b..a19fa256366c0a7d2278c21f151828beda18c1eb 100644 --- a/addons/website_project_issue/views/project_issue_templates.xml +++ b/addons/website_project_issue/views/project_issue_templates.xml @@ -53,6 +53,7 @@ <div class="col-md-12"> <h4> Issue <span t-field="issue.id"/> - <span t-field="issue.name"/> + <a class="btn btn-info" t-att-href="'/web#return_label=Website&model=project.issue&id=%s&view_type=form' % (issue.id)" groups="project.group_project_user">Edit Issue</a> <span t-field="issue.stage_id.name" class="pull-right label label-info" title="Current stage of this issue"/> </h4> </div> diff --git a/doc/_extensions/odoo/static/style.css b/doc/_extensions/odoo/static/style.css index 65ad613a94efb37e7b462f7944f6c8b73d345e10..1d48dea23b0aa28f44f979dde1fe83a709fe57c3 100644 --- a/doc/_extensions/odoo/static/style.css +++ b/doc/_extensions/odoo/static/style.css @@ -9473,18 +9473,18 @@ h6, line-height: 1.4; } @media (min-width: 1200px) { - .has_code_col h1, - .has_code_col h2, - .has_code_col h3, - .has_code_col h4, - .has_code_col h5, - .has_code_col h6, - .has_code_col .h1, - .has_code_col .h2, - .has_code_col .h3, - .has_code_col .h4, - .has_code_col .h5, - .has_code_col .h6 { + .has_code_col .doc-aside h1, + .has_code_col .doc-aside h2, + .has_code_col .doc-aside h3, + .has_code_col .doc-aside h4, + .has_code_col .doc-aside h5, + .has_code_col .doc-aside h6, + .has_code_col .doc-aside .h1, + .has_code_col .doc-aside .h2, + .has_code_col .doc-aside .h3, + .has_code_col .doc-aside .h4, + .has_code_col .doc-aside .h5, + .has_code_col .doc-aside .h6 { color: white; } } @@ -11928,7 +11928,7 @@ main.index.animating .card { font-size: 65%; } @media (min-width: 1200px) { - .has_code_col .card.top .container h1 { + .has_code_col .doc-aside .card.top .container h1 { color: white; } } @@ -11969,7 +11969,7 @@ main.index.animating .card { } } @media screen and (min-width: 768px) and (min-width: 1200px) { - .has_code_col .card.top .container h1 { + .has_code_col .doc-aside .card.top .container h1 { color: white; } } @@ -12010,7 +12010,7 @@ main.index.animating .card { font-size: 65%; } @media (min-width: 1200px) { - .has_code_col .toctree-wrapper > ul > li.toctree-l1 > span { + .has_code_col .doc-aside .toctree-wrapper > ul > li.toctree-l1 > span { color: white; } } diff --git a/doc/cla/corporate/rocksolidsolutions.md b/doc/cla/corporate/rocksolidsolutions.md new file mode 100644 index 0000000000000000000000000000000000000000..133c19de35c77a651fd74ea38679b97c433cf2b6 --- /dev/null +++ b/doc/cla/corporate/rocksolidsolutions.md @@ -0,0 +1,15 @@ +United States, 2016-05-25 + +Rock Solid Solutions, LLC <http://www.rocksolidsolutions.org> 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, + +Daniel Kauffman, Manager rocksolidsolutions@users.noreply.github.com <https://github.com/rocksolidsolutions> + +List of contributors: + +Daniel Kauffman rocksolidsolutions@users.noreply.github.com <https://github.com/rocksolidsolutions> diff --git a/openerp/service/server.py b/openerp/service/server.py index 4d2c185348be3853f57ba2a094b653b4d5842892..84888f84a6139eedc7aff5787731394757a05bf9 100644 --- a/openerp/service/server.py +++ b/openerp/service/server.py @@ -383,7 +383,8 @@ class PreforkServer(CommonServer): """ def __init__(self, app): # config - self.address = (config['xmlrpc_interface'] or '0.0.0.0', config['xmlrpc_port']) + self.address = config['xmlrpc'] and \ + (config['xmlrpc_interface'] or '0.0.0.0', config['xmlrpc_port']) self.population = config['workers'] self.timeout = config['limit_time_real'] self.limit_request = config['limit_request'] @@ -564,12 +565,13 @@ class PreforkServer(CommonServer): signal.signal(signal.SIGQUIT, dumpstacks) signal.signal(signal.SIGUSR1, log_ormcache_stats) - # listen to socket - self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - self.socket.setblocking(0) - self.socket.bind(self.address) - self.socket.listen(8 * self.population) + if self.address: + # listen to socket + self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self.socket.setblocking(0) + self.socket.bind(self.address) + self.socket.listen(8 * self.population) def stop(self, graceful=True): if self.long_polling_pid is not None: @@ -593,7 +595,8 @@ class PreforkServer(CommonServer): _logger.info("Stopping forcefully") for pid in self.workers.keys(): self.worker_kill(pid, signal.SIGTERM) - self.socket.close() + if self.socket: + self.socket.close() def run(self, preload, stop): self.start() @@ -696,11 +699,13 @@ class Worker(object): _logger.info("Worker %s (%s) alive", self.__class__.__name__, self.pid) # Reseed the random number generator random.seed() - # Prevent fd inherientence close_on_exec - flags = fcntl.fcntl(self.multi.socket, fcntl.F_GETFD) | fcntl.FD_CLOEXEC - fcntl.fcntl(self.multi.socket, fcntl.F_SETFD, flags) - # reset blocking status - self.multi.socket.setblocking(0) + if self.multi.socket: + # Prevent fd inheritance: close_on_exec + flags = fcntl.fcntl(self.multi.socket, fcntl.F_GETFD) | fcntl.FD_CLOEXEC + fcntl.fcntl(self.multi.socket, fcntl.F_SETFD, flags) + # reset blocking status + self.multi.socket.setblocking(0) + signal.signal(signal.SIGINT, self.signal_handler) signal.signal(signal.SIGTERM, signal.SIG_DFL) signal.signal(signal.SIGCHLD, signal.SIG_DFL) @@ -819,7 +824,8 @@ class WorkerCron(Worker): def start(self): os.nice(10) # mommy always told me to be nice with others... Worker.start(self) - self.multi.socket.close() + if self.multi.socket: + self.multi.socket.close() #---------------------------------------------------------- # start/stop public api