[MERGE] forward port branch saas-15 up to 482370d014
This commit is contained in:
@@ -76,7 +76,8 @@ bus.Bus = Widget.extend({
|
||||
this.last_partners_presence_check = now;
|
||||
}
|
||||
var data = {channels: self.channels, last: self.last, options: options};
|
||||
session.rpc('/longpolling/poll', data, {shadow : true}).then(function(result) {
|
||||
// The backend has a maximum cycle time of 50 seconds so give +10 seconds
|
||||
session.rpc('/longpolling/poll', data, {shadow : true, timeout: 60000}).then(function(result) {
|
||||
self.on_notification(result);
|
||||
if(!self.stop){
|
||||
self.poll();
|
||||
|
||||
@@ -415,7 +415,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):
|
||||
|
||||
@@ -319,7 +319,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)
|
||||
|
||||
|
||||
@@ -225,7 +225,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})
|
||||
|
||||
@@ -1352,7 +1352,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():
|
||||
|
||||
@@ -104,12 +104,15 @@
|
||||
</t>
|
||||
<t t-set="x_icon" t-value="'/web/image/website/%s/favicon/' % website.id"/>
|
||||
<t t-set="head_website">
|
||||
<meta name="description" t-att-content="main_object and 'website_meta_description' in main_object
|
||||
and main_object.website_meta_description or website_meta_description"/>
|
||||
<meta name="keywords" t-att-content="main_object and 'website_meta_keywords' in main_object
|
||||
and main_object.website_meta_keywords or website_meta_keywords"/>
|
||||
<meta name="generator" content="Odoo"/>
|
||||
|
||||
<t t-set="meta_description" t-value="main_object and 'website_meta_description' in main_object
|
||||
and main_object.website_meta_description or website_meta_description"/>
|
||||
<t t-set="meta_keywords" t-value="main_object and 'website_meta_keywords' in main_object
|
||||
and main_object.website_meta_keywords or website_meta_keywords"/>
|
||||
<meta t-if="meta_description" name="description" t-att-content="meta_description"/>
|
||||
<meta t-if="meta_keywords" name="keywords" t-att-content="meta_keywords"/>
|
||||
|
||||
<!-- OpenGraph tags for Facebook sharing -->
|
||||
<meta property="og:title" t-att-content="additional_title" />
|
||||
<meta property="og:site_name" t-att-content="res_company.name" />
|
||||
|
||||
@@ -32,8 +32,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
|
||||
|
||||
@@ -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
|
||||
@@ -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 <limit>
|
||||
|
||||
@@ -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 <limit>
|
||||
|
||||
|
||||
@@ -600,7 +600,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):
|
||||
|
||||
+43
-40
@@ -646,6 +646,7 @@ class BaseModel(object):
|
||||
: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 = []
|
||||
for record in self:
|
||||
# main line of record, initially empty
|
||||
@@ -679,8 +680,9 @@ class BaseModel(object):
|
||||
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
|
||||
@@ -2852,51 +2854,52 @@ class BaseModel(object):
|
||||
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({})
|
||||
Values = self.env['ir.values']
|
||||
Attachment = self.env['ir.attachment']
|
||||
cr = self._cr
|
||||
Data = self.env['ir.model.data'].sudo().with_context({})
|
||||
Values = self.env['ir.values']
|
||||
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 relevant records in ir_values
|
||||
refs = ['%s,%s' % (self._name, i) for i in sub_ids]
|
||||
values = Values.search(['|', ('value', 'in', refs),
|
||||
'&', ('model', '=', self._name),
|
||||
('res_id', 'in', sub_ids)])
|
||||
if values:
|
||||
values.unlink()
|
||||
# For the same reason, remove the relevant records in ir_values
|
||||
refs = ['%s,%s' % (self._name, i) for i in sub_ids]
|
||||
values = Values.search(['|', ('value', 'in', refs),
|
||||
'&', ('model', '=', self._name),
|
||||
('res_id', 'in', sub_ids)])
|
||||
if values:
|
||||
values.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()
|
||||
# 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):
|
||||
|
||||
@@ -63,7 +63,7 @@ def xmlrpc_return(start_response, service, method, params, string_faultcode=Fals
|
||||
|
||||
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))
|
||||
response = xmlrpclib.dumps(fault, allow_none=False, encoding=None)
|
||||
elif isinstance(e, odoo.exceptions.RedirectWarning):
|
||||
fault = xmlrpclib.Fault(RPC_FAULT_CODE_WARNING, str(e))
|
||||
|
||||
Reference in New Issue
Block a user