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):