[MERGE] forward port branch saas-14 up to d171f282c3
This commit is contained in:
@@ -327,6 +327,9 @@ class Lead(models.Model):
|
||||
# Set date_open to today if it is an opp
|
||||
default = default or {}
|
||||
default['date_open'] = fields.Datetime.now() if self.type == 'opportunity' else False
|
||||
# Do not assign to an archived user
|
||||
if not self.user_id.active:
|
||||
default['user_id'] = False
|
||||
return super(Lead, self.with_context(context)).copy(default=default)
|
||||
|
||||
@api.model
|
||||
|
||||
@@ -530,6 +530,8 @@ class HrExpenseSheet(models.Model):
|
||||
|
||||
@api.multi
|
||||
def refuse_expenses(self, reason):
|
||||
if not self.user_has_groups('hr_expense.group_hr_expense_user'):
|
||||
raise UserError(_("Only HR Officers can refuse expenses"))
|
||||
self.write({'state': 'cancel'})
|
||||
for sheet in self:
|
||||
body = (_("Your Expense %s has been refused.<br/><ul class=o_timeline_tracking_value_list><li>Reason<span> : </span><span class=o_timeline_tracking_value>%s</span></li></ul>") % (sheet.name, reason))
|
||||
@@ -537,6 +539,8 @@ class HrExpenseSheet(models.Model):
|
||||
|
||||
@api.multi
|
||||
def approve_expense_sheets(self):
|
||||
if not self.user_has_groups('hr_expense.group_hr_expense_user'):
|
||||
raise UserError(_("Only HR Officers can approve expenses"))
|
||||
self.write({'state': 'approve', 'responsible_id': self.env.user.id})
|
||||
|
||||
@api.multi
|
||||
|
||||
@@ -165,7 +165,7 @@ class hr_timesheet_sheet_sheet_day(models.Model):
|
||||
ON r.user_id = u.id
|
||||
LEFT JOIN res_partner p
|
||||
ON u.partner_id = p.id
|
||||
WHERE check_out IS NOT NULL
|
||||
WHERE a.check_out IS NOT NULL
|
||||
group by (a.check_in AT TIME ZONE 'UTC' AT TIME ZONE coalesce(p.tz, 'UTC'))::date, s.id, timezone
|
||||
)) AS foo
|
||||
GROUP BY name, sheet_id, timezone
|
||||
|
||||
@@ -36,18 +36,16 @@ class MailChatController(BusController):
|
||||
# --------------------------
|
||||
@route('/mail/chat_post', type="json", auth="none")
|
||||
def mail_chat_post(self, uuid, message_content, **kwargs):
|
||||
request_uid = self._default_request_uid()
|
||||
# find the author from the user session, which can be None
|
||||
author_id = False # message_post accept 'False' author_id, but not 'None'
|
||||
if request.session.uid:
|
||||
author_id = request.env['res.users'].sudo().browse(request.session.uid).partner_id.id
|
||||
# post a message without adding followers to the channel. email_from=False avoid to get author from email data
|
||||
mail_channel = request.env["mail.channel"].sudo(request_uid).search([('uuid', '=', uuid)], limit=1)
|
||||
message = mail_channel.sudo(request_uid).with_context(mail_create_nosubscribe=True).message_post(author_id=author_id, email_from=False, body=message_content, message_type='comment', subtype='mail.mt_comment', content_subtype='plaintext', **kwargs)
|
||||
mail_channel = request.env["mail.channel"].sudo().search([('uuid', '=', uuid)], limit=1)
|
||||
message = mail_channel.sudo().with_context(mail_create_nosubscribe=True).message_post(author_id=author_id, email_from=False, body=message_content, message_type='comment', subtype='mail.mt_comment', content_subtype='plaintext')
|
||||
return message and message.id or False
|
||||
|
||||
@route(['/mail/chat_history'], type="json", auth="none")
|
||||
def mail_chat_history(self, uuid, last_id=False, limit=20):
|
||||
request_uid = self._default_request_uid()
|
||||
channel = request.env["mail.channel"].sudo(request_uid).search([('uuid', '=', uuid)], limit=1)
|
||||
return channel.sudo(request_uid).channel_fetch_message(last_id, limit)
|
||||
channel = request.env["mail.channel"].sudo().search([('uuid', '=', uuid)], limit=1)
|
||||
return channel.sudo().channel_fetch_message(last_id, limit)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
from datetime import date, datetime, timedelta
|
||||
|
||||
from odoo import api, fields, models
|
||||
from odoo import api, exceptions, fields, models, _
|
||||
|
||||
|
||||
class MailActivityType(models.Model):
|
||||
@@ -102,20 +102,65 @@ class MailActivity(models.Model):
|
||||
self.summary = self.activity_type_id.summary
|
||||
self.date_deadline = (datetime.now() + timedelta(days=self.activity_type_id.days))
|
||||
|
||||
@api.multi
|
||||
def _check_access(self, operation):
|
||||
""" Rule to access activities
|
||||
|
||||
* create: check write rights on related document;
|
||||
* write: rule OR write rights on document;
|
||||
* unlink: rule OR write rights on document;
|
||||
"""
|
||||
self.check_access_rights(operation, raise_exception=True) # will raise an AccessError
|
||||
|
||||
if operation in ('write', 'unlink'):
|
||||
try:
|
||||
self.check_access_rule(operation)
|
||||
except exceptions.AccessError:
|
||||
pass
|
||||
else:
|
||||
return
|
||||
|
||||
doc_operation = 'read' if operation == 'read' else 'write'
|
||||
activity_to_documents = dict()
|
||||
for activity in self.sudo():
|
||||
activity_to_documents.setdefault(activity.res_model, list()).append(activity.res_id)
|
||||
for model, res_ids in activity_to_documents.items():
|
||||
self.env[model].check_access_rights(doc_operation, raise_exception=True)
|
||||
try:
|
||||
self.env[model].browse(res_ids).check_access_rule(doc_operation)
|
||||
except exceptions.AccessError:
|
||||
raise exceptions.AccessError(
|
||||
_('The requested operation cannot be completed due to security restrictions. Please contact your system administrator.\n\n(Document type: %s, Operation: %s)') %
|
||||
(self._description, operation))
|
||||
|
||||
@api.model
|
||||
def create(self, values):
|
||||
activity = super(MailActivity, self).create(values)
|
||||
self.env[activity.res_model].browse(activity.res_id).message_subscribe(partner_ids=[activity.user_id.partner_id.id])
|
||||
return activity
|
||||
# already compute default values to be sure those are computed using the current user
|
||||
values_w_defaults = self.default_get(self._fields.keys())
|
||||
values_w_defaults.update(values)
|
||||
|
||||
# continue as sudo because activities are somewhat protected
|
||||
activity = super(MailActivity, self.sudo()).create(values_w_defaults)
|
||||
activity_user = activity.sudo(self.env.user)
|
||||
activity_user._check_access('create')
|
||||
self.env[activity_user.res_model].browse(activity_user.res_id).message_subscribe(partner_ids=[activity_user.user_id.partner_id.id])
|
||||
return activity_user
|
||||
|
||||
@api.multi
|
||||
def write(self, values):
|
||||
res = super(MailActivity, self).write(values)
|
||||
self._check_access('write')
|
||||
res = super(MailActivity, self.sudo()).write(values)
|
||||
|
||||
if values.get('user_id'):
|
||||
for activity in self:
|
||||
self.env[activity.res_model].browse(activity.res_id).message_subscribe(partner_ids=[activity.user_id.partner_id.id])
|
||||
return res
|
||||
|
||||
@api.multi
|
||||
def unlink(self):
|
||||
self._check_access('unlink')
|
||||
return super(MailActivity, self.sudo()).unlink()
|
||||
|
||||
@api.multi
|
||||
def action_done(self):
|
||||
message = self.env['mail.message']
|
||||
|
||||
@@ -96,6 +96,7 @@ class Message(models.Model):
|
||||
tracking_value_ids = fields.One2many(
|
||||
'mail.tracking.value', 'mail_message_id',
|
||||
string='Tracking values',
|
||||
groups="base.group_no_one",
|
||||
help='Tracked values are stored in a separate model. This field allow to reconstruct '
|
||||
'the tracking and to generate statistics on the model.')
|
||||
# mail gateway
|
||||
@@ -279,7 +280,7 @@ class Message(models.Model):
|
||||
# 1. Aggregate partners (author_id and partner_ids), attachments and tracking values
|
||||
partners = self.env['res.partner'].sudo()
|
||||
attachments = self.env['ir.attachment']
|
||||
trackings = self.env['mail.tracking.value']
|
||||
message_ids = message_tree.keys()
|
||||
for key, message in message_tree.iteritems():
|
||||
if message.author_id:
|
||||
partners |= message.author_id
|
||||
@@ -291,8 +292,6 @@ class Message(models.Model):
|
||||
partners |= message.needaction_partner_ids
|
||||
if message.attachment_ids:
|
||||
attachments |= message.attachment_ids
|
||||
if message.tracking_value_ids:
|
||||
trackings |= message.tracking_value_ids
|
||||
# Read partners as SUPERUSER -> message being browsed as SUPERUSER it is already the case
|
||||
partners_names = partners.name_get()
|
||||
partner_tree = dict((partner[0], partner) for partner in partners_names)
|
||||
@@ -307,13 +306,18 @@ class Message(models.Model):
|
||||
}) for attachment in attachments_data)
|
||||
|
||||
# 3. Tracking values
|
||||
tracking_tree = dict((tracking.id, {
|
||||
'id': tracking.id,
|
||||
'changed_field': tracking.field_desc,
|
||||
'old_value': tracking.get_old_display_value()[0],
|
||||
'new_value': tracking.get_new_display_value()[0],
|
||||
'field_type': tracking.field_type,
|
||||
}) for tracking in trackings)
|
||||
tracking_values = self.env['mail.tracking.value'].sudo().search([('mail_message_id', 'in', message_ids)])
|
||||
message_to_tracking = dict()
|
||||
tracking_tree = dict.fromkeys(tracking_values.ids, False)
|
||||
for tracking in tracking_values:
|
||||
message_to_tracking.setdefault(tracking.mail_message_id.id, list()).append(tracking.id)
|
||||
tracking_tree[tracking.id] = {
|
||||
'id': tracking.id,
|
||||
'changed_field': tracking.field_desc,
|
||||
'old_value': tracking.get_old_display_value()[0],
|
||||
'new_value': tracking.get_new_display_value()[0],
|
||||
'field_type': tracking.field_type,
|
||||
}
|
||||
|
||||
# 4. Update message dictionaries
|
||||
for message_dict in messages:
|
||||
@@ -340,9 +344,9 @@ class Message(models.Model):
|
||||
if attachment.id in attachments_tree:
|
||||
attachment_ids.append(attachments_tree[attachment.id])
|
||||
tracking_value_ids = []
|
||||
for tracking_value in message.tracking_value_ids:
|
||||
if tracking_value.id in tracking_tree:
|
||||
tracking_value_ids.append(tracking_tree[tracking_value.id])
|
||||
for tracking_value_id in message_to_tracking.get(message_id, list()):
|
||||
if tracking_value_id in tracking_tree:
|
||||
tracking_value_ids.append(tracking_tree[tracking_value_id])
|
||||
|
||||
message_dict.update({
|
||||
'author_id': author,
|
||||
@@ -721,7 +725,12 @@ class Message(models.Model):
|
||||
if 'record_name' not in values and 'default_record_name' not in self.env.context:
|
||||
values['record_name'] = self._get_record_name(values)
|
||||
|
||||
# delegate creation of tracking after the create as sudo to avoid access rights issues
|
||||
tracking_values_cmd = values.pop('tracking_value_ids', False)
|
||||
message = super(Message, self).create(values)
|
||||
if tracking_values_cmd:
|
||||
message.sudo().write({'tracking_value_ids': tracking_values_cmd})
|
||||
|
||||
message._invalidate_documents()
|
||||
|
||||
if not self.env.context.get('message_create_from_mail_mail'):
|
||||
|
||||
@@ -61,7 +61,7 @@ class Partner(models.Model):
|
||||
record_name = message.record_name
|
||||
|
||||
tracking = []
|
||||
for tracking_value in message.tracking_value_ids:
|
||||
for tracking_value in self.env['mail.tracking.value'].sudo().search([('mail_message_id', '=', message.id)]):
|
||||
tracking.append((tracking_value.field_desc,
|
||||
tracking_value.get_old_display_value()[0],
|
||||
tracking_value.get_new_display_value()[0]))
|
||||
|
||||
@@ -23,9 +23,9 @@ access_mail_alias_user,mail.alias.user,model_mail_alias,base.group_user,1,1,1,1
|
||||
access_mail_alias_system,mail.alias.system,model_mail_alias,base.group_system,1,1,1,1
|
||||
access_mail_message_subtype_all,mail.message.subtype.all,model_mail_message_subtype,,1,0,0,0
|
||||
access_mail_message_subtype_user,mail.message.subtype.user,model_mail_message_subtype,,1,1,1,1
|
||||
access_mail_tracking_value_all,mail.tracking.value.all,model_mail_tracking_value,,1,0,0,0
|
||||
access_mail_tracking_value_portal,mail.tracking.value.portal,model_mail_tracking_value,base.group_portal,1,1,1,1
|
||||
access_mail_tracking_value_user,mail.tracking.value.user,model_mail_tracking_value,base.group_user,1,1,1,1
|
||||
access_mail_tracking_value_all,mail.tracking.value.all,model_mail_tracking_value,,0,0,0,0
|
||||
access_mail_tracking_value_portal,mail.tracking.value.portal,model_mail_tracking_value,base.group_portal,0,0,0,0
|
||||
access_mail_tracking_value_user,mail.tracking.value.user,model_mail_tracking_value,base.group_user,0,0,0,0
|
||||
access_mail_tracking_value_system,mail.tracking.value.system,model_mail_tracking_value,base.group_system,1,1,1,1
|
||||
access_mail_thread_all,mail.thread.all,model_mail_thread,,1,1,1,1
|
||||
access_publisher_warranty_contract_all,publisher.warranty.contract.all,model_publisher_warranty_contract,,1,1,1,1
|
||||
@@ -35,7 +35,7 @@ access_mail_shortcode,mail.shortcode,model_mail_shortcode,base.group_user,1,1,1,
|
||||
access_mail_shortcode_portal,mail.shortcode.portal,model_mail_shortcode,base.group_portal,1,0,0,0
|
||||
access_mail_test_user,mail.test.all,model_mail_test,base.group_user,1,1,1,1
|
||||
access_mail_test_portal,mail.test.all,model_mail_test,base.group_portal,1,1,0,0
|
||||
access_mail_activity_all,mail.activity.all,model_mail_activity,,1,0,0,0
|
||||
access_mail_activity_all,mail.activity.all,model_mail_activity,,0,0,0,0
|
||||
access_mail_activity_user,mail.activity.user,model_mail_activity,base.group_user,1,1,1,1
|
||||
access_mail_activity_type_all,mail.activity.type.all,model_mail_activity_type,,1,0,0,0
|
||||
access_mail_activity_type_user,mail.activity.type.user,model_mail_activity_type,base.group_user,1,1,1,1
|
||||
access_mail_activity_type_all,mail.activity.type.all,model_mail_activity_type,,0,0,0,0
|
||||
access_mail_activity_type_user,mail.activity.type.user,model_mail_activity_type,base.group_user,1,1,1,1
|
||||
|
||||
|
@@ -40,5 +40,16 @@
|
||||
<field name="groups" eval="[(4, ref('base.group_portal')), (4, ref('base.group_public'))]"/>
|
||||
</record>
|
||||
|
||||
<record id="mail_activity_rule_user" model="ir.rule">
|
||||
<field name="name">mail.activity: user: own only</field>
|
||||
<field name="model_id" ref="model_mail_activity"/>
|
||||
<field name="domain_force">[('user_id', '=', user.id)]</field>
|
||||
<field name="groups" eval="[(4, ref('base.group_user'))]"/>
|
||||
<field name="perm_create" eval="False"/>
|
||||
<field name="perm_read" eval="False"/>
|
||||
<field name="perm_write" eval="True"/>
|
||||
<field name="perm_unlink" eval="True"/>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
|
||||
@@ -293,9 +293,11 @@ class WebsiteSlides(http.Controller):
|
||||
|
||||
@http.route(['/slides/add_slide'], type='json', auth='user', methods=['POST'], website=True)
|
||||
def create_slide(self, *args, **post):
|
||||
file_size = len(post['datas']) * 3 / 4; # base64
|
||||
if (file_size / 1024.0 / 1024.0) > 25:
|
||||
return {'error': _('File is too big. File size cannot exceed 25MB')}
|
||||
# check the size only when we upload a file.
|
||||
if post.get('datas'):
|
||||
file_size = len(post['datas']) * 3 / 4 # base64
|
||||
if (file_size / 1024.0 / 1024.0) > 25:
|
||||
return {'error': _('File is too big. File size cannot exceed 25MB')}
|
||||
|
||||
values = dict((fname, post[fname]) for fname in [
|
||||
'name', 'url', 'tag_ids', 'slide_type', 'channel_id',
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
France, 2017-11-06
|
||||
|
||||
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,
|
||||
|
||||
Rémi CAZENAVE remi@le-filament.com https://github.com/remi-filament
|
||||
@@ -17,6 +17,13 @@ _logger = logging.getLogger(__name__)
|
||||
BASE_VERSION = odoo.modules.load_information_from_description_file('base')['version']
|
||||
|
||||
|
||||
class BadVersion(Exception):
|
||||
pass
|
||||
|
||||
class BadModuleState(Exception):
|
||||
pass
|
||||
|
||||
|
||||
_intervalTypes = {
|
||||
'days': lambda interval: relativedelta(days=interval),
|
||||
'hours': lambda interval: relativedelta(hours=interval),
|
||||
@@ -140,34 +147,103 @@ class ir_cron(models.Model):
|
||||
cron_cr.commit()
|
||||
|
||||
@classmethod
|
||||
def _acquire_job(cls, db_name):
|
||||
# TODO remove 'check' argument from addons/base_automation/base_automation.py
|
||||
""" Try to process one cron job.
|
||||
def _process_jobs(cls, db_name):
|
||||
""" Try to process all cron jobs.
|
||||
|
||||
This selects in database all the jobs that should be processed. It then
|
||||
tries to lock each of them and, if it succeeds, run the cron job (if it
|
||||
doesn't succeed, it means the job was already locked to be taken care
|
||||
of by another thread) and return.
|
||||
|
||||
If a job was processed, returns True, otherwise returns False.
|
||||
:raise BadVersion: if the version is different from the worker's
|
||||
:raise BadModuleState: if modules are to install/upgrade/remove
|
||||
"""
|
||||
db = odoo.sql_db.db_connect(db_name)
|
||||
threading.current_thread().dbname = db_name
|
||||
jobs = []
|
||||
try:
|
||||
with db.cursor() as cr:
|
||||
# Make sure the database we poll has the same version as the code of base
|
||||
cr.execute("SELECT 1 FROM ir_module_module WHERE name=%s AND latest_version=%s", ('base', BASE_VERSION))
|
||||
if cr.fetchone():
|
||||
# Careful to compare timestamps with 'UTC' - everything is UTC as of v6.1.
|
||||
cr.execute("""SELECT * FROM ir_cron
|
||||
WHERE numbercall != 0
|
||||
AND active AND nextcall <= (now() at time zone 'UTC')
|
||||
ORDER BY priority""")
|
||||
jobs = cr.dictfetchall()
|
||||
else:
|
||||
_logger.warning('Skipping database %s as its base version is not %s.', db_name, BASE_VERSION)
|
||||
except psycopg2.ProgrammingError, e:
|
||||
# Make sure the database has the same version as the code of
|
||||
# base and that no module must be installed/upgraded/removed
|
||||
cr.execute("SELECT latest_version FROM ir_module_module WHERE name=%s", ['base'])
|
||||
(version,) = cr.fetchone()
|
||||
cr.execute("SELECT COUNT(*) FROM ir_module_module WHERE state LIKE %s", ['to %'])
|
||||
(changes,) = cr.fetchone()
|
||||
if not version or changes:
|
||||
raise BadModuleState()
|
||||
elif version != BASE_VERSION:
|
||||
raise BadVersion()
|
||||
# Careful to compare timestamps with 'UTC' - everything is UTC as of v6.1.
|
||||
cr.execute("""SELECT * FROM ir_cron
|
||||
WHERE numbercall != 0
|
||||
AND active AND nextcall <= (now() at time zone 'UTC')
|
||||
ORDER BY priority""")
|
||||
jobs = cr.dictfetchall()
|
||||
|
||||
for job in jobs:
|
||||
lock_cr = db.cursor()
|
||||
try:
|
||||
# Try to grab an exclusive lock on the job row from within the task transaction
|
||||
# Restrict to the same conditions as for the search since the job may have already
|
||||
# been run by an other thread when cron is running in multi thread
|
||||
lock_cr.execute("""SELECT *
|
||||
FROM ir_cron
|
||||
WHERE numbercall != 0
|
||||
AND active
|
||||
AND nextcall <= (now() at time zone 'UTC')
|
||||
AND id=%s
|
||||
FOR UPDATE NOWAIT""",
|
||||
(job['id'],), log_exceptions=False)
|
||||
|
||||
locked_job = lock_cr.fetchone()
|
||||
if not locked_job:
|
||||
_logger.debug("Job `%s` already executed by another process/thread. skipping it", job['cron_name'])
|
||||
continue
|
||||
# Got the lock on the job row, run its code
|
||||
_logger.debug('Starting job `%s`.', job['cron_name'])
|
||||
job_cr = db.cursor()
|
||||
try:
|
||||
registry = odoo.registry(db_name)
|
||||
registry[cls._name]._process_job(job_cr, job, lock_cr)
|
||||
except Exception:
|
||||
_logger.exception('Unexpected exception while processing cron job %r', job)
|
||||
finally:
|
||||
job_cr.close()
|
||||
|
||||
except psycopg2.OperationalError, e:
|
||||
if e.pgcode == '55P03':
|
||||
# Class 55: Object not in prerequisite state; 55P03: lock_not_available
|
||||
_logger.debug('Another process/thread is already busy executing job `%s`, skipping it.', job['cron_name'])
|
||||
continue
|
||||
else:
|
||||
# Unexpected OperationalError
|
||||
raise
|
||||
finally:
|
||||
# we're exiting due to an exception while acquiring the lock
|
||||
lock_cr.close()
|
||||
|
||||
finally:
|
||||
if hasattr(threading.current_thread(), 'dbname'):
|
||||
del threading.current_thread().dbname
|
||||
|
||||
@classmethod
|
||||
def _acquire_job(cls, db_name):
|
||||
""" Try to process all cron jobs.
|
||||
|
||||
This selects in database all the jobs that should be processed. It then
|
||||
tries to lock each of them and, if it succeeds, run the cron job (if it
|
||||
doesn't succeed, it means the job was already locked to be taken care
|
||||
of by another thread) and return.
|
||||
|
||||
This method hides most exceptions related to the database's version, the
|
||||
modules' state, and such.
|
||||
"""
|
||||
try:
|
||||
cls._process_jobs(db_name)
|
||||
except BadVersion:
|
||||
_logger.warning('Skipping database %s as its base version is not %s.', db_name, BASE_VERSION)
|
||||
except BadModuleState:
|
||||
_logger.warning('Skipping database %s because of modules to install/upgrade/remove.', db_name)
|
||||
except psycopg2.ProgrammingError as e:
|
||||
if e.pgcode == '42P01':
|
||||
# Class 42 — Syntax Error or Access Rule Violation; 42P01: undefined_table
|
||||
# The table ir_cron does not exist; this is probably not an OpenERP database.
|
||||
@@ -177,51 +253,6 @@ class ir_cron(models.Model):
|
||||
except Exception:
|
||||
_logger.warning('Exception in cron:', exc_info=True)
|
||||
|
||||
for job in jobs:
|
||||
lock_cr = db.cursor()
|
||||
try:
|
||||
# Try to grab an exclusive lock on the job row from within the task transaction
|
||||
# Restrict to the same conditions as for the search since the job may have already
|
||||
# been run by an other thread when cron is running in multi thread
|
||||
lock_cr.execute("""SELECT *
|
||||
FROM ir_cron
|
||||
WHERE numbercall != 0
|
||||
AND active
|
||||
AND nextcall <= (now() at time zone 'UTC')
|
||||
AND id=%s
|
||||
FOR UPDATE NOWAIT""",
|
||||
(job['id'],), log_exceptions=False)
|
||||
|
||||
locked_job = lock_cr.fetchone()
|
||||
if not locked_job:
|
||||
_logger.debug("Job `%s` already executed by another process/thread. skipping it", job['cron_name'])
|
||||
continue
|
||||
# Got the lock on the job row, run its code
|
||||
_logger.debug('Starting job `%s`.', job['cron_name'])
|
||||
job_cr = db.cursor()
|
||||
try:
|
||||
registry = odoo.registry(db_name)
|
||||
registry[cls._name]._process_job(job_cr, job, lock_cr)
|
||||
except Exception:
|
||||
_logger.exception('Unexpected exception while processing cron job %r', job)
|
||||
finally:
|
||||
job_cr.close()
|
||||
|
||||
except psycopg2.OperationalError, e:
|
||||
if e.pgcode == '55P03':
|
||||
# Class 55: Object not in prerequisite state; 55P03: lock_not_available
|
||||
_logger.debug('Another process/thread is already busy executing job `%s`, skipping it.', job['cron_name'])
|
||||
continue
|
||||
else:
|
||||
# Unexpected OperationalError
|
||||
raise
|
||||
finally:
|
||||
# we're exiting due to an exception while acquiring the lock
|
||||
lock_cr.close()
|
||||
|
||||
if hasattr(threading.current_thread(), 'dbname'): # cron job could have removed it as side-effect
|
||||
del threading.current_thread().dbname
|
||||
|
||||
@api.multi
|
||||
def _try_lock(self):
|
||||
"""Try to grab a dummy exclusive write-lock to the rows with the given ids,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<template id="report_irmodeloverview">
|
||||
<t t-call="report.html_container">
|
||||
<t t-foreach="docs" t-as="o">
|
||||
<div class="page">
|
||||
<div class="article">
|
||||
<table class="table table-bordered mb64">
|
||||
<tr>
|
||||
<td colspan="12">
|
||||
|
||||
@@ -216,14 +216,11 @@ class ThreadedServer(CommonServer):
|
||||
registries = odoo.modules.registry.Registry.registries
|
||||
_logger.debug('cron%d polling for jobs', number)
|
||||
for db_name, registry in registries.iteritems():
|
||||
while registry.ready:
|
||||
if registry.ready:
|
||||
try:
|
||||
acquired = odoo.addons.base.ir.ir_cron.ir_cron._acquire_job(db_name)
|
||||
if not acquired:
|
||||
break
|
||||
odoo.addons.base.ir.ir_cron.ir_cron._acquire_job(db_name)
|
||||
except Exception:
|
||||
_logger.warning('cron%d encountered an Exception:', number, exc_info=True)
|
||||
break
|
||||
|
||||
def cron_spawn(self):
|
||||
""" Start the above runner function in a daemon thread.
|
||||
|
||||
Reference in New Issue
Block a user