diff --git a/addons/calendar/models/calendar.py b/addons/calendar/models/calendar.py index 35571684c0d..6fcf24f2c35 100644 --- a/addons/calendar/models/calendar.py +++ b/addons/calendar/models/calendar.py @@ -434,7 +434,7 @@ class AlarmManager(models.AbstractModel): result = False if alarm.type == 'email': - result = meeting.attendee_ids._send_mail_to_attendees('calendar.calendar_template_meeting_reminder', force_send=True) + result = meeting.attendee_ids.filtered(lambda r: r.state != 'declined')._send_mail_to_attendees('calendar.calendar_template_meeting_reminder', force_send=True) return result def do_notif_reminder(self, alert): diff --git a/addons/event/models/event.py b/addons/event/models/event.py index 825006e9933..1157d941e8b 100644 --- a/addons/event/models/event.py +++ b/addons/event/models/event.py @@ -317,7 +317,7 @@ class EventEvent(models.Model): self.state = 'confirm' @api.one - def mail_attendees(self, template_id, force_send=False, filter_func=lambda self: True): + def mail_attendees(self, template_id, force_send=False, filter_func=lambda self: self.state != 'cancel'): for attendee in self.registration_ids.filtered(filter_func): self.env['mail.template'].browse(template_id).send_mail(attendee.id, force_send=force_send) diff --git a/addons/hr_holidays/models/hr.py b/addons/hr_holidays/models/hr.py index e74e60f314d..bf8fbddd5ea 100644 --- a/addons/hr_holidays/models/hr.py +++ b/addons/hr_holidays/models/hr.py @@ -218,7 +218,12 @@ class Employee(models.Model): def write(self, values): res = super(Employee, self).write(values) - if 'parent_id' in values: + if 'parent_id' in values or 'department_id' in values: holidays = self.env['hr.holidays'].search([('state', 'in', ['draft', 'confirm']), ('employee_id', 'in', self.ids)]) - holidays.write({'manager_id': values['parent_id']}) + hr_vals = {} + if values.get('parent_id') is not None: + hr_vals['manager_id'] = values['parent_id'] + if values.get('department_id') is not None: + hr_vals['department_id'] = values['department_id'] + holidays.write(hr_vals) return res diff --git a/addons/hr_holidays/models/hr_holidays.py b/addons/hr_holidays/models/hr_holidays.py index 3638f191e6c..505db8a2ea2 100644 --- a/addons/hr_holidays/models/hr_holidays.py +++ b/addons/hr_holidays/models/hr_holidays.py @@ -197,7 +197,7 @@ class Holidays(models.Model): "\nChoose 'Allocation Request' if you want to increase the number of leaves available for someone") parent_id = fields.Many2one('hr.holidays', string='Parent') linked_request_ids = fields.One2many('hr.holidays', 'parent_id', string='Linked Requests') - department_id = fields.Many2one('hr.department', related='employee_id.department_id', string='Department', readonly=True, store=True) + department_id = fields.Many2one('hr.department', string='Department', readonly=True) category_id = fields.Many2one('hr.employee.category', string='Employee Tag', readonly=True, states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, help='Category of Employee') holiday_type = fields.Selection([ @@ -216,6 +216,7 @@ class Holidays(models.Model): @api.onchange('employee_id') def _onchange_employee_id(self): self.manager_id = self.employee_id and self.employee_id.parent_id + self.department_id = self.employee_id and self.employee_id.department_id @api.multi @api.depends('number_of_days_temp', 'type') diff --git a/addons/l10n_be_hr_payroll/data/l10n_be_hr_payroll_data.xml b/addons/l10n_be_hr_payroll/data/l10n_be_hr_payroll_data.xml index 14ad3eaefa0..0f6bb86479f 100644 --- a/addons/l10n_be_hr_payroll/data/l10n_be_hr_payroll_data.xml +++ b/addons/l10n_be_hr_payroll/data/l10n_be_hr_payroll_data.xml @@ -545,7 +545,6 @@ result = -contract.car_atn ref('hr_salary_rule_representation_fees'), ref('hr_payroll_rules_suppl_net'), ref('hr_payroll_rules_retained_net'), - ref('hr_salary_rule_commission_on_target'), ref('hr_salary_rule_pp_reduction_employees'), ])]" name="rule_ids"/> @@ -571,7 +570,6 @@ result = -contract.car_atn ref('hr_salary_rule_representation_fees'), ref('hr_payroll_rules_suppl_net'), ref('hr_payroll_rules_retained_net'), - ref('hr_salary_rule_commission_on_target'), ref('hr_salary_rule_pp_reduction_workers') ])]" name="rule_ids"/> diff --git a/addons/l10n_be_hr_payroll/models/l10n_be_hr_payroll.py b/addons/l10n_be_hr_payroll/models/l10n_be_hr_payroll.py index c80ccc983a5..1b7a66cb475 100644 --- a/addons/l10n_be_hr_payroll/models/l10n_be_hr_payroll.py +++ b/addons/l10n_be_hr_payroll/models/l10n_be_hr_payroll.py @@ -116,8 +116,8 @@ class HrContract(models.Model): @api.depends('commission_on_target') def _compute_warrants_cost(self): for contract in self: - contract.warrants_cost = contract.commission_on_target * 1.326 / 1.05 * 12.0 - contract.warrant_value_employee = contract.warrants_cost * 0.54 + contract.warrants_cost = contract.commission_on_target * 1.05 * 12.0 + contract.warrant_value_employee = contract.commission_on_target * (1.00 - 0.535) * 12.0 @api.depends('wage', 'fuel_card', 'representation_fees', 'transport_employer_cost', 'internet', 'mobile', 'mobile_plus') diff --git a/addons/sales_team/models/crm_team.py b/addons/sales_team/models/crm_team.py index cf95b66a7e2..4542116c47c 100644 --- a/addons/sales_team/models/crm_team.py +++ b/addons/sales_team/models/crm_team.py @@ -232,7 +232,7 @@ class CrmTeam(models.Model): values[index][y_field] = data_item.get('y_value') elif self.dashboard_graph_group == 'week': - weeks_in_start_year = int(date(start_date.year, 12, 31).isocalendar()[1]) + weeks_in_start_year = int(date(start_date.year, 12, 28).isocalendar()[1]) # This date is always in the last week of ISO years for week in range(0, (end_date.isocalendar()[1] - start_date.isocalendar()[1]) % weeks_in_start_year + 1): short_name = get_week_name(start_date + relativedelta(days=7 * week), locale) values.append({x_field: short_name, y_field: 0}) diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index b0cf820005b..913edc89932 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -1382,7 +1382,7 @@ class ExportFormat(object): model, fields, ids, domain, import_compat = \ operator.itemgetter('model', 'fields', 'ids', 'domain', 'import_compat')(params) - Model = request.env[model].with_context(**params.get('context', {})) + Model = request.env[model].with_context(import_compat=import_compat, **params.get('context', {})) records = Model.browse(ids) or Model.search(domain, offset=0, limit=False, order=False) if not Model._is_an_ordinary_table(): diff --git a/addons/website/views/website_templates.xml b/addons/website/views/website_templates.xml index fc4561c9980..69afcbbbd94 100644 --- a/addons/website/views/website_templates.xml +++ b/addons/website/views/website_templates.xml @@ -116,13 +116,16 @@ + + - - - + + diff --git a/addons/website_event/models/event.py b/addons/website_event/models/event.py index cd551f0f451..c2b37b0fab2 100644 --- a/addons/website_event/models/event.py +++ b/addons/website_event/models/event.py @@ -23,8 +23,8 @@ class Event(models.Model): website_menu = fields.Boolean( 'Dedicated Menu', compute='_compute_website_menu', inverse='_set_website_menu', help="Creates menus Introduction, Location and Register on the page " - " of the event on the website.", store=True, copy=False) - menu_id = fields.Many2one('website.menu', 'Event Menu') + " of the event on the website.", store=True) + menu_id = fields.Many2one('website.menu', 'Event Menu', copy=False) def _compute_is_participating(self): # we don't allow public user to see participating label diff --git a/doc/cla/individual/willmakenoise.md b/doc/cla/individual/willmakenoise.md new file mode 100644 index 00000000000..3c8ac824146 --- /dev/null +++ b/doc/cla/individual/willmakenoise.md @@ -0,0 +1,10 @@ +Norway, 2018-02-08 + +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, +Marius Stedjan marius@stedjan.com https://github.com/willmakenoise diff --git a/doc/reference/cmdline.rst b/doc/reference/cmdline.rst index d35868a718f..2f1626f6dac 100644 --- a/doc/reference/cmdline.rst +++ b/doc/reference/cmdline.rst @@ -54,7 +54,7 @@ Running the server Maximum allowed virtual memory per worker. If the limit is exceeded, the worker is killed and recycled at the end of the current request. - Defaults to 640MB. + Defaults to 2048MB. .. option:: --limit-memory-hard @@ -62,7 +62,7 @@ Running the server immediately killed without waiting for the end of the current request processing. - Defaults to 768MB. + Defaults to 2560MB. .. option:: --limit-time-cpu diff --git a/odoo/addons/test_impex/tests/test_export.py b/odoo/addons/test_impex/tests/test_export.py index a360cfd0ef4..8009f97adc0 100644 --- a/odoo/addons/test_impex/tests/test_export.py +++ b/odoo/addons/test_impex/tests/test_export.py @@ -601,7 +601,54 @@ class test_m2m(CreatorCase): ['', u'export.many2many.other:13'], ]) - # essentially same as o2m, so boring + def test_multiple_records_subfield(self): + r = self.make(self.commands) + xid = self.env['ir.model.data'].create({ + 'name': 'whopwhopwhop', + 'module': '__t__', + 'model': r._name, + 'res_id': r.id, + }).complete_name + sids = [ + self.env['ir.model.data'].create({ + 'name': sub.str, + 'module': '__t__', + 'model': sub._name, + 'res_id': sub.id, + }).complete_name + for sub in r.value + ] + r.invalidate_cache() + + self.assertEqual( + r._export_rows([['value', 'id']]), + [['__t__.record000,__t__.record001,__t__.record010,__t__.record011,__t__.record100']] + ) + self.assertEqual( + r.with_context(import_compat=True)._export_rows([['value', 'id']]), + [['__t__.record000,__t__.record001,__t__.record010,__t__.record011,__t__.record100']] + ) + + self.assertEqual( + r.with_context(import_compat=False)._export_rows([['id'], ['value', 'id'], ['value', 'value']]), + [ + [xid, u'__t__.record000', u'4'], + [u'', u'__t__.record001', u'42'], + [u'', u'__t__.record010', u'36'], + [u'', u'__t__.record011', u'4'], + [u'', u'__t__.record100', u'13'] + ] + ) + self.assertEqual( + r.with_context(import_compat=False)._export_rows([['id'], ['value', 'value'], ['value', 'id']]), + [ + [xid, u'4', u'__t__.record000'], + [u'', u'42', u'__t__.record001'], + [u'', u'36', u'__t__.record010'], + [u'', u'4', u'__t__.record011'], + [u'', u'13', u'__t__.record100'] + ] + ) class test_function(CreatorCase): diff --git a/odoo/models.py b/odoo/models.py index a99779c0251..68efa22c314 100644 --- a/odoo/models.py +++ b/odoo/models.py @@ -650,6 +650,7 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})): :param fields: list of lists of fields to traverse :return: list of lists of corresponding values """ + import_compatible = self.env.context.get('import_compat', True) lines = [] def splittor(rs): @@ -696,8 +697,9 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})): else: primary_done.append(name) - # This is a special case, its strange behavior is intended! - if field.type == 'many2many' and len(path) > 1 and path[1] == 'id': + # in import_compat mode, m2m should always be exported as + # a comma-separated list of xids in a single cell + if import_compatible and field.type == 'many2many' and len(path) > 1 and path[1] == 'id': xml_ids = [r.__export_xml_id() for r in value] current[i] = ','.join(xml_ids) or False continue @@ -2853,47 +2855,48 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})): raise UserError(_('Unable to delete this document because it is used as a default property')) # Delete the records' properties. - self.env['ir.property'].search([('res_id', 'in', refs)]).unlink() + with self.env.norecompute(): + self.env['ir.property'].search([('res_id', 'in', refs)]).unlink() - self.check_access_rule('unlink') + self.check_access_rule('unlink') - cr = self._cr - Data = self.env['ir.model.data'].sudo().with_context({}) - Defaults = self.env['ir.default'].sudo() - Attachment = self.env['ir.attachment'] + cr = self._cr + Data = self.env['ir.model.data'].sudo().with_context({}) + Defaults = self.env['ir.default'].sudo() + Attachment = self.env['ir.attachment'] - for sub_ids in cr.split_for_in_conditions(self.ids): - query = "DELETE FROM %s WHERE id IN %%s" % self._table - cr.execute(query, (sub_ids,)) + for sub_ids in cr.split_for_in_conditions(self.ids): + query = "DELETE FROM %s WHERE id IN %%s" % self._table + cr.execute(query, (sub_ids,)) - # Removing the ir_model_data reference if the record being deleted - # is a record created by xml/csv file, as these are not connected - # with real database foreign keys, and would be dangling references. - # - # Note: the following steps are performed as superuser to avoid - # access rights restrictions, and with no context to avoid possible - # side-effects during admin calls. - data = Data.search([('model', '=', self._name), ('res_id', 'in', sub_ids)]) - if data: - data.unlink() + # Removing the ir_model_data reference if the record being deleted + # is a record created by xml/csv file, as these are not connected + # with real database foreign keys, and would be dangling references. + # + # Note: the following steps are performed as superuser to avoid + # access rights restrictions, and with no context to avoid possible + # side-effects during admin calls. + data = Data.search([('model', '=', self._name), ('res_id', 'in', sub_ids)]) + if data: + data.unlink() - # For the same reason, remove the defaults having some of the - # records as value - Defaults.discard_records(self.browse(sub_ids)) + # For the same reason, remove the defaults having some of the + # records as value + Defaults.discard_records(self.browse(sub_ids)) - # For the same reason, remove the relevant records in ir_attachment - # (the search is performed with sql as the search method of - # ir_attachment is overridden to hide attachments of deleted - # records) - query = 'SELECT id FROM ir_attachment WHERE res_model=%s AND res_id IN %s' - cr.execute(query, (self._name, sub_ids)) - attachments = Attachment.browse([row[0] for row in cr.fetchall()]) - if attachments: - attachments.unlink() + # For the same reason, remove the relevant records in ir_attachment + # (the search is performed with sql as the search method of + # ir_attachment is overridden to hide attachments of deleted + # records) + query = 'SELECT id FROM ir_attachment WHERE res_model=%s AND res_id IN %s' + cr.execute(query, (self._name, sub_ids)) + attachments = Attachment.browse([row[0] for row in cr.fetchall()]) + if attachments: + attachments.unlink() - # invalidate the *whole* cache, since the orm does not handle all - # changes made in the database, like cascading delete! - self.invalidate_cache() + # invalidate the *whole* cache, since the orm does not handle all + # changes made in the database, like cascading delete! + self.invalidate_cache() # recompute new-style fields if self.env.recompute and self._context.get('recompute', True): diff --git a/odoo/service/wsgi_server.py b/odoo/service/wsgi_server.py index af5ae574187..45f9de07b52 100644 --- a/odoo/service/wsgi_server.py +++ b/odoo/service/wsgi_server.py @@ -41,7 +41,7 @@ RPC_FAULT_CODE_ACCESS_ERROR = 4 def xmlrpc_handle_exception_int(e): if isinstance(e, odoo.exceptions.UserError): - fault = xmlrpclib.Fault(RPC_FAULT_CODE_WARNING, odoo.tools.ustr(e.value)) + fault = xmlrpclib.Fault(RPC_FAULT_CODE_WARNING, odoo.tools.ustr(e.name)) elif isinstance(e, odoo.exceptions.RedirectWarning): fault = xmlrpclib.Fault(RPC_FAULT_CODE_WARNING, str(e)) elif isinstance(e, odoo.exceptions.MissingError):