diff --git a/addons/crm/models/crm_lead.py b/addons/crm/models/crm_lead.py index df30d51c9c299e993d69f663a76bf980f9b86dbe..d8978e5bab16bcf5881f50609a78e1a18aa3f0f0 100644 --- a/addons/crm/models/crm_lead.py +++ b/addons/crm/models/crm_lead.py @@ -325,6 +325,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 diff --git a/addons/hr_expense/models/hr_expense.py b/addons/hr_expense/models/hr_expense.py index 8afab6fba39a0345163894a1383107f7117b4b6d..c422e613d8dbb067667508ccfea09907abab3ace 100644 --- a/addons/hr_expense/models/hr_expense.py +++ b/addons/hr_expense/models/hr_expense.py @@ -514,6 +514,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)) @@ -521,6 +523,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 diff --git a/addons/hr_timesheet_attendance/models/hr_timesheet_sheet.py b/addons/hr_timesheet_attendance/models/hr_timesheet_sheet.py index 49adf3fed7fe61fde961a3f77ead996c60c954f0..d0c980f9f5fc793f58212ea671104f7e03f085fc 100644 --- a/addons/hr_timesheet_attendance/models/hr_timesheet_sheet.py +++ b/addons/hr_timesheet_attendance/models/hr_timesheet_sheet.py @@ -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 diff --git a/addons/mail/controllers/bus.py b/addons/mail/controllers/bus.py index 517ded468cd2c600b5949eda8e90e459afac37d7..62a5225cfe58ad24927b4a25194296cc0b3336af 100644 --- a/addons/mail/controllers/bus.py +++ b/addons/mail/controllers/bus.py @@ -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) diff --git a/addons/mail/models/mail_activity.py b/addons/mail/models/mail_activity.py index 581267d443580846229edfe37eab92dc60b49522..b655ccf2fbb56202cc9b4dbdc48330ddcfef13e1 100644 --- a/addons/mail/models/mail_activity.py +++ b/addons/mail/models/mail_activity.py @@ -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): @@ -128,20 +128,65 @@ class MailActivity(models.Model): def _onchange_recommended_activity_type_id(self): self.activity_type_id = self.recommended_activity_type_id + @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): """ Wrapper without feedback because web button add context as diff --git a/addons/mail/models/mail_message.py b/addons/mail/models/mail_message.py index 5bc4678848d0aa70716a051ff1972e347385cd04..3194eb15033ccab7418de57a58de5b65ab93502b 100644 --- a/addons/mail/models/mail_message.py +++ b/addons/mail/models/mail_message.py @@ -98,6 +98,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 @@ -281,7 +282,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 @@ -293,8 +294,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) @@ -309,13 +308,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: @@ -342,9 +346,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, @@ -745,7 +749,12 @@ class Message(models.Model): return '%s%s alt="%s"' % (data_to_url[key], match.group(3), name) values['body'] = _image_dataurl.sub(base64_to_boundary, values['body']) + # 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'): diff --git a/addons/mail/models/res_partner.py b/addons/mail/models/res_partner.py index b07b7c8b118ed5e79c9e5c415f943e66d3d8ae8c..9990d9bca31f60f2c767caf7d1e00a8ac5aff3e9 100644 --- a/addons/mail/models/res_partner.py +++ b/addons/mail/models/res_partner.py @@ -63,7 +63,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])) diff --git a/addons/mail/security/ir.model.access.csv b/addons/mail/security/ir.model.access.csv index 0bd1697ca87db3dcf92f76021930f334ad35eddb..5040f812d73ea9d8d6cba701b49e1cc0e728790e 100644 --- a/addons/mail/security/ir.model.access.csv +++ b/addons/mail/security/ir.model.access.csv @@ -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 \ No newline at end of file +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 diff --git a/addons/mail/security/mail_security.xml b/addons/mail/security/mail_security.xml index d5a5b5422c911d522ecd089e5669f57fc837f431..b9b13d9de3bc1baa8d63dd7ac59065341fa19a81 100644 --- a/addons/mail/security/mail_security.xml +++ b/addons/mail/security/mail_security.xml @@ -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> diff --git a/addons/website_slides/controllers/main.py b/addons/website_slides/controllers/main.py index 4226f54dc34a58805eff906a14274b097f20bfe0..7b67bfcbb403993b57b2ea8a76c14072b9208003 100644 --- a/addons/website_slides/controllers/main.py +++ b/addons/website_slides/controllers/main.py @@ -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', diff --git a/doc/cla/individual/remi-filament.md b/doc/cla/individual/remi-filament.md new file mode 100644 index 0000000000000000000000000000000000000000..b57242f471f4377cd61738cdd940cdb949c1950d --- /dev/null +++ b/doc/cla/individual/remi-filament.md @@ -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 diff --git a/odoo/addons/base/ir/ir_cron.py b/odoo/addons/base/ir/ir_cron.py index 17b92cae609c0bbf120de26052bea901ac4d6c07..bcb2df904a56168e7f1b135f9645df237c2c0e6c 100644 --- a/odoo/addons/base/ir/ir_cron.py +++ b/odoo/addons/base/ir/ir_cron.py @@ -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), @@ -141,33 +148,102 @@ 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) + # 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 as 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 @@ -178,51 +254,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 as 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, diff --git a/odoo/addons/base/ir/report_ir_model.xml b/odoo/addons/base/ir/report_ir_model.xml index 284767a3247a4dda8f706014c755b28296e51247..8dbbf9aba2f1a8afc4fbc15506db2d127617e648 100644 --- a/odoo/addons/base/ir/report_ir_model.xml +++ b/odoo/addons/base/ir/report_ir_model.xml @@ -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"> diff --git a/odoo/service/server.py b/odoo/service/server.py index d3ee988216fe90a8c304a46a19323e75f924c78e..8ca97efe65d3beb592f218bb6c65c009fd1863ee 100644 --- a/odoo/service/server.py +++ b/odoo/service/server.py @@ -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.