From 72c1a4f96a1219d98ce9b90ee54fd7297b9ab999 Mon Sep 17 00:00:00 2001 From: bve-odoo Date: Fri, 3 Nov 2023 18:05:47 +0100 Subject: [PATCH] [IMP] *: replace type where possible MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit do not compare types, for exact checks use `is` / `is not`, for instance checks use `isinstance()`Flake8(E721) Signed-off-by: Rémy Voet (ryv) --- .../models/account_journal_dashboard.py | 2 +- addons/account/models/chart_template.py | 4 +-- addons/account/models/partner.py | 2 +- .../base_automation/models/base_automation.py | 2 +- addons/bus/websocket.py | 20 +++++++------- addons/event_booth/models/event_booth.py | 2 +- .../models/fleet_vehicle_log_services.py | 2 +- addons/google_account/controllers/main.py | 2 +- addons/google_gmail/controllers/main.py | 2 +- addons/hr_contract/models/hr_contract.py | 2 +- addons/link_tracker/models/link_tracker.py | 2 +- addons/mail/models/ir_model.py | 6 ++--- addons/mail/models/mail_mail.py | 7 +++-- addons/mail/models/mail_notification.py | 2 +- addons/mail/models/mail_thread.py | 4 +-- addons/mail/wizard/mail_compose_message.py | 2 +- addons/mass_mailing/models/mail_thread.py | 2 +- addons/mass_mailing/models/mailing.py | 2 +- .../models/mailing_mailing.py | 2 +- addons/microsoft_outlook/controllers/main.py | 2 +- addons/portal/controllers/mail.py | 2 +- addons/portal/models/mail_thread.py | 2 +- addons/rating/models/mail_thread.py | 2 +- addons/sms/wizard/sms_composer.py | 10 +++---- addons/web/models/base_document_layout.py | 2 +- addons/web/models/ir_qweb_fields.py | 4 +-- addons/web_editor/models/ir_qweb_fields.py | 2 +- odoo/addons/base/models/ir_actions.py | 6 ++--- odoo/addons/base/models/ir_autovacuum.py | 2 +- odoo/addons/base/models/ir_model.py | 4 +-- odoo/addons/base/models/ir_module.py | 6 ++--- odoo/addons/base/models/ir_qweb_fields.py | 2 +- odoo/addons/base/models/ir_ui_view.py | 10 +++---- odoo/addons/base/models/res_partner.py | 2 +- odoo/addons/base/models/res_users.py | 2 +- odoo/addons/base/wizard/base_partner_merge.py | 2 +- odoo/addons/test_limits/models.py | 2 +- odoo/addons/test_read_group/models.py | 2 +- odoo/api.py | 4 ++- odoo/models.py | 26 +++++++++---------- odoo/modules/registry.py | 4 +-- odoo/service/server.py | 2 +- odoo/tools/date_utils.py | 22 +++++++++++----- odoo/tools/mail.py | 3 ++- odoo/tools/misc.py | 4 +-- 45 files changed, 106 insertions(+), 94 deletions(-) diff --git a/addons/account/models/account_journal_dashboard.py b/addons/account/models/account_journal_dashboard.py index c099f04e1c7..3b3add3b583 100644 --- a/addons/account/models/account_journal_dashboard.py +++ b/addons/account/models/account_journal_dashboard.py @@ -746,7 +746,7 @@ class account_journal(models.Model): action = self.env["ir.actions.act_window"]._for_xml_id(action_name) context = self._context.copy() - if 'context' in action and type(action['context']) == str: + if 'context' in action and isinstance(action['context'], str): context.update(ast.literal_eval(action['context'])) else: context.update(action.get('context', {})) diff --git a/addons/account/models/chart_template.py b/addons/account/models/chart_template.py index f00fcadde01..43b13575d0e 100644 --- a/addons/account/models/chart_template.py +++ b/addons/account/models/chart_template.py @@ -61,7 +61,7 @@ class AccountChartTemplate(models.AbstractModel): def is_template(func): return callable(func) and hasattr(func, '_l10n_template') template_register = defaultdict(lambda: defaultdict(list)) - cls = type(self) + cls = self.env.registry[self._name] for _attr, func in getmembers(cls, is_template): template, model = func._l10n_template template_register[template][model].append(func) @@ -70,7 +70,7 @@ class AccountChartTemplate(models.AbstractModel): def _setup_complete(self): super()._setup_complete() - type(self)._template_register = AccountChartTemplate._template_register + self.env.registry[self._name]._template_register = AccountChartTemplate._template_register # -------------------------------------------------------------------------------- diff --git a/addons/account/models/partner.py b/addons/account/models/partner.py index aa88261db30..2768de196bb 100644 --- a/addons/account/models/partner.py +++ b/addons/account/models/partner.py @@ -373,7 +373,7 @@ class ResPartner(models.Model): def _asset_difference_search(self, account_type, operator, operand): if operator not in ('<', '=', '>', '>=', '<='): return [] - if type(operand) not in (float, int): + if not isinstance(operand, (float, int)): return [] sign = 1 if account_type == 'liability_payable': diff --git a/addons/base_automation/models/base_automation.py b/addons/base_automation/models/base_automation.py index f1cf22ee3e5..b0d076bb5fe 100644 --- a/addons/base_automation/models/base_automation.py +++ b/addons/base_automation/models/base_automation.py @@ -816,7 +816,7 @@ class BaseAutomation(models.Model): """ Patch method `name` on `model`, unless it has been patched already. """ if model not in patched_models[name]: patched_models[name].add(model) - ModelClass = type(model) + ModelClass = model.env.registry[model._name] method.origin = getattr(ModelClass, name) setattr(ModelClass, name, method) diff --git a/addons/bus/websocket.py b/addons/bus/websocket.py index 58164ec157d..1775b9b47a5 100644 --- a/addons/bus/websocket.py +++ b/addons/bus/websocket.py @@ -237,7 +237,7 @@ class Websocket: self._close_received = False self._timeout_manager = TimeoutManager() # Used for rate limiting. - self._incoming_frame_timestamps = deque(maxlen=type(self).RL_BURST) + self._incoming_frame_timestamps = deque(maxlen=self.RL_BURST) # Used to notify the websocket that bus notifications are # available. self.__notif_sock_w, self.__notif_sock_r = socket.socketpair() @@ -264,7 +264,7 @@ class Websocket: try: readables = { selector_key[0].fileobj for selector_key in - self.__selector.select(type(self).INACTIVITY_TIMEOUT) + self.__selector.select(self.INACTIVITY_TIMEOUT) } if self._timeout_manager.has_timed_out() and self.state is ConnectionState.OPEN: self.disconnect( @@ -405,7 +405,7 @@ class Websocket: payload_length = struct.unpack('!H', recv_bytes(2))[0] elif payload_length == 127: payload_length = struct.unpack('!Q', recv_bytes(8))[0] - if payload_length > type(self).MESSAGE_MAX_SIZE: + if payload_length > self.MESSAGE_MAX_SIZE: raise PayloadTooLargeException() mask = recv_bytes(4) @@ -454,7 +454,7 @@ class Websocket: if frame.opcode is not Opcode.CONTINUE: raise ProtocolError("A continuation frame was expected") message_fragments.extend(frame.payload) - if len(message_fragments) > type(self).MESSAGE_MAX_SIZE: + if len(message_fragments) > self.MESSAGE_MAX_SIZE: raise PayloadTooLargeException() if frame.fin: return bytes(message_fragments) @@ -599,9 +599,9 @@ class Websocket: `RateLimitExceededException`. """ now = time.time() - if len(self._incoming_frame_timestamps) >= type(self).RL_BURST: + if len(self._incoming_frame_timestamps) >= self.RL_BURST: elapsed_time = now - self._incoming_frame_timestamps[0] - if elapsed_time < type(self).RL_DELAY * type(self).RL_BURST: + if elapsed_time < self.RL_DELAY * self.RL_BURST: raise RateLimitExceededException() self._incoming_frame_timestamps.append(now) @@ -611,11 +611,11 @@ class Websocket: registered for this event type. Every callback is given both the environment and the related websocket. """ - if not type(self).__event_callbacks[event_type]: + if not self.__event_callbacks[event_type]: return with closing(acquire_cursor(self._db)) as cr: env = api.Environment(cr, self._session.uid, self._session.context) - for callback in type(self).__event_callbacks[event_type]: + for callback in self.__event_callbacks[event_type]: try: service_model.retrying(functools.partial(callback, env, self), env) except Exception: @@ -674,7 +674,7 @@ class TimeoutManager: # Custom keep alive timeout for each TimeoutManager to avoid multiple # connections timing out at the same time. self._keep_alive_timeout = ( - type(self).KEEP_ALIVE_TIMEOUT + random.uniform(0, type(self).KEEP_ALIVE_TIMEOUT / 2) + self.KEEP_ALIVE_TIMEOUT + random.uniform(0, self.KEEP_ALIVE_TIMEOUT / 2) ) self.timeout_reason = None # Start time recorded when we started awaiting an answer to a @@ -711,7 +711,7 @@ class TimeoutManager: if now - self._opened_at >= self._keep_alive_timeout: self.timeout_reason = TimeoutReason.KEEP_ALIVE return True - if self._awaited_opcode and now - self._waiting_start_time >= type(self).TIMEOUT: + if self._awaited_opcode and now - self._waiting_start_time >= self.TIMEOUT: self.timeout_reason = TimeoutReason.NO_RESPONSE return True return False diff --git a/addons/event_booth/models/event_booth.py b/addons/event_booth/models/event_booth.py index b3e79def61a..8eee800184c 100644 --- a/addons/event_booth/models/event_booth.py +++ b/addons/event_booth/models/event_booth.py @@ -59,7 +59,7 @@ class EventBooth(models.Model): return [('state', '=', 'available')] def _group_expand_states(self, states, domain, order): - return [key for key, val in type(self).state.selection] + return [key for key, val in self._fields['state'].selection] @api.model_create_multi def create(self, vals_list): diff --git a/addons/fleet/models/fleet_vehicle_log_services.py b/addons/fleet/models/fleet_vehicle_log_services.py index b8e8d2ecc2e..2ef06061131 100644 --- a/addons/fleet/models/fleet_vehicle_log_services.py +++ b/addons/fleet/models/fleet_vehicle_log_services.py @@ -72,4 +72,4 @@ class FleetVehicleLogServices(models.Model): service.purchaser_id = service.vehicle_id.driver_id def _expand_states(self, states, domain, order): - return [key for key, dummy in type(self).state.selection] + return [key for key, dummy in self._fields['state'].selection] diff --git a/addons/google_account/controllers/main.py b/addons/google_account/controllers/main.py index 243d686f22b..070c5fea5b1 100644 --- a/addons/google_account/controllers/main.py +++ b/addons/google_account/controllers/main.py @@ -28,7 +28,7 @@ class GoogleAuth(http.Controller): ) service_field = f'google_{service}_account_id' if service_field in request.env.user: - getattr(request.env.user, service_field)._set_auth_tokens(access_token, refresh_token, ttl) + request.env.user[service_field]._set_auth_tokens(access_token, refresh_token, ttl) else: raise Warning('No callback field for service <%s>' % service) return request.redirect(url_return) diff --git a/addons/google_gmail/controllers/main.py b/addons/google_gmail/controllers/main.py index f9bf00d9008..407e2dc8140 100644 --- a/addons/google_gmail/controllers/main.py +++ b/addons/google_gmail/controllers/main.py @@ -42,7 +42,7 @@ class GoogleGmailController(http.Controller): model = request.env[model_name] - if not issubclass(type(model), request.env.registry['google.gmail.mixin']): + if not isinstance(model, request.env.registry['google.gmail.mixin']): # The model must inherits from the "google.gmail.mixin" mixin raise Forbidden() diff --git a/addons/hr_contract/models/hr_contract.py b/addons/hr_contract/models/hr_contract.py index 3b5e9189351..6b03df09545 100644 --- a/addons/hr_contract/models/hr_contract.py +++ b/addons/hr_contract/models/hr_contract.py @@ -84,7 +84,7 @@ class Contract(models.Model): contract.calendar_mismatch = contract.resource_calendar_id != contract.employee_id.resource_calendar_id def _expand_states(self, states, domain, order): - return [key for key, val in type(self).state.selection] + return [key for key, val in self._fields['state'].selection] @api.depends('employee_id') def _compute_employee_contract(self): diff --git a/addons/link_tracker/models/link_tracker.py b/addons/link_tracker/models/link_tracker.py index 6865576f6aa..7256db750a2 100644 --- a/addons/link_tracker/models/link_tracker.py +++ b/addons/link_tracker/models/link_tracker.py @@ -100,7 +100,7 @@ class LinkTracker(models.Model): utms = {} for key, field_name, cook in self.env['utm.mixin'].tracking_fields(): field = self._fields[field_name] - attr = getattr(tracker, field_name) + attr = tracker[field_name] if field.type == 'many2one': attr = attr.name if attr: diff --git a/addons/mail/models/ir_model.py b/addons/mail/models/ir_model.py index 30084eb1a7b..15247d322e7 100644 --- a/addons/mail/models/ir_model.py +++ b/addons/mail/models/ir_model.py @@ -92,9 +92,9 @@ class IrModel(models.Model): def _reflect_model_params(self, model): vals = super(IrModel, self)._reflect_model_params(model) - vals['is_mail_thread'] = issubclass(type(model), self.pool['mail.thread']) - vals['is_mail_activity'] = issubclass(type(model), self.pool['mail.activity.mixin']) - vals['is_mail_blacklist'] = issubclass(type(model), self.pool['mail.thread.blacklist']) + vals['is_mail_thread'] = isinstance(model, self.pool['mail.thread']) + vals['is_mail_activity'] = isinstance(model, self.pool['mail.activity.mixin']) + vals['is_mail_blacklist'] = isinstance(model, self.pool['mail.thread.blacklist']) return vals @api.model diff --git a/addons/mail/models/mail_mail.py b/addons/mail/models/mail_mail.py index 0b7388311f5..aa3454abef4 100644 --- a/addons/mail/models/mail_mail.py +++ b/addons/mail/models/mail_mail.py @@ -35,9 +35,9 @@ class MailMail(models.Model): def default_get(self, fields): # protection for `default_type` values leaking from menu action context (e.g. for invoices) # To remove when automatic context propagation is removed in web client - if self._context.get('default_type') not in type(self).message_type.base_field.selection: + if self._context.get('default_type') not in self._fields['message_type'].base_field.selection: self = self.with_context(dict(self._context, default_type=None)) - if self._context.get('default_state') not in type(self).state.base_field.selection: + if self._context.get('default_state') not in self._fields['state'].base_field.selection: self = self.with_context(dict(self._context, default_state='outgoing')) return super(MailMail, self).default_get(fields) @@ -173,9 +173,8 @@ class MailMail(models.Model): SQL queries. """ super()._add_inherited_fields() - cls = type(self) for field in ('email_from', 'reply_to', 'subject'): - cls._fields[field].related_sudo = True + self._fields[field].related_sudo = True def action_retry(self): self.filtered(lambda mail: mail.state == 'exception').mark_outgoing() diff --git a/addons/mail/models/mail_notification.py b/addons/mail/models/mail_notification.py index 70523b5d418..f961ae67b45 100644 --- a/addons/mail/models/mail_notification.py +++ b/addons/mail/models/mail_notification.py @@ -108,7 +108,7 @@ class MailNotification(models.Model): def format_failure_reason(self): self.ensure_one() if self.failure_type != 'unknown': - return dict(type(self).failure_type.selection).get(self.failure_type, _('No Error')) + return dict(self._fields['failure_type'].selection).get(self.failure_type, _('No Error')) else: return _("Unknown error") + ": %s" % (self.failure_reason or '') diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py index 1989e93be6f..d2e2ff22578 100644 --- a/addons/mail/models/mail_thread.py +++ b/addons/mail/models/mail_thread.py @@ -763,7 +763,7 @@ class MailThread(models.AbstractModel): bounced_record_done = bounced_record_done or (bounced_record and model.model == bounced_model and bounced_record in rec_bounce_w_email) # set record as bounced unless already done due to blacklist mixin - if bounced_record and not bounced_record_done and issubclass(type(bounced_record), self.pool['mail.thread']): + if bounced_record and not bounced_record_done and isinstance(bounced_record, self.pool['mail.thread']): bounced_record._message_receive_bounce(bounced_email, bounced_partner) if bounced_partner and bounced_message: @@ -1931,7 +1931,7 @@ class MailThread(models.AbstractModel): If no partner has been found and/or created for a given emails its matching partner is an empty record. """ - if records and issubclass(type(records), self.pool['mail.thread']): + if records and isinstance(records, self.pool['mail.thread']): followers = records.mapped('message_partner_ids') else: followers = self.env['res.partner'] diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py index 7534156d65c..5e48ffccefb 100644 --- a/addons/mail/wizard/mail_compose_message.py +++ b/addons/mail/wizard/mail_compose_message.py @@ -1271,7 +1271,7 @@ class MailComposer(models.TransientModel): blacklist = {x[0] for x in self._cr.fetchall()} if not blacklist: return blacklisted_rec_ids - if issubclass(type(self.env[self.model]), self.pool['mail.thread.blacklist']): + if isinstance(self.env[self.model], self.pool['mail.thread.blacklist']): targets = self.env[self.model].browse(mail_values_dict.keys()) targets.fetch(['email_normalized']) # First extract email from recipient before comparing with blacklist diff --git a/addons/mass_mailing/models/mail_thread.py b/addons/mass_mailing/models/mail_thread.py index 189831441c7..92ca81f5f31 100644 --- a/addons/mass_mailing/models/mail_thread.py +++ b/addons/mass_mailing/models/mail_thread.py @@ -82,7 +82,7 @@ class MailThread(models.AbstractModel): """ defaults = {} - if issubclass(type(self), self.pool['utm.mixin']): + if isinstance(self, self.pool['utm.mixin']): thread_references = msg_dict.get('references', '') or msg_dict.get('in_reply_to', '') msg_references = tools.mail_header_msgid_re.findall(thread_references) if msg_references: diff --git a/addons/mass_mailing/models/mailing.py b/addons/mass_mailing/models/mailing.py index 851edc5100e..082bed3c1a0 100644 --- a/addons/mass_mailing/models/mailing.py +++ b/addons/mass_mailing/models/mailing.py @@ -559,7 +559,7 @@ class MassMailing(models.Model): return super(MassMailing, self).copy(default=default) def _group_expand_states(self, states, domain, order): - return [key for key, val in type(self).state.selection] + return [key for key, val in self._fields['state'].selection] # ------------------------------------------------------ # ACTIONS diff --git a/addons/mass_mailing_sms/models/mailing_mailing.py b/addons/mass_mailing_sms/models/mailing_mailing.py index 49b48ea7dd0..7401aff14d4 100644 --- a/addons/mass_mailing_sms/models/mailing_mailing.py +++ b/addons/mass_mailing_sms/models/mailing_mailing.py @@ -169,7 +169,7 @@ class Mailing(models.Model): target = self.env[self.mailing_model_real] partner_fields = [] - if issubclass(type(target), self.pool['mail.thread.phone']): + if isinstance(target, self.pool['mail.thread.phone']): phone_fields = ['phone_sanitized'] else: phone_fields = [ diff --git a/addons/microsoft_outlook/controllers/main.py b/addons/microsoft_outlook/controllers/main.py index fd1bb08465e..3dd18197012 100644 --- a/addons/microsoft_outlook/controllers/main.py +++ b/addons/microsoft_outlook/controllers/main.py @@ -46,7 +46,7 @@ class MicrosoftOutlookController(http.Controller): model = request.env[model_name] - if not issubclass(type(model), request.env.registry['microsoft.outlook.mixin']): + if not isinstance(model, request.env.registry['microsoft.outlook.mixin']): # The model must inherits from the "microsoft.outlook.mixin" mixin raise Forbidden() diff --git a/addons/portal/controllers/mail.py b/addons/portal/controllers/mail.py index 91c832f0649..a0c198a0b3f 100644 --- a/addons/portal/controllers/mail.py +++ b/addons/portal/controllers/mail.py @@ -253,7 +253,7 @@ class MailController(mail.MailController): if not model or not res_id or model not in request.env: return super(MailController, cls)._redirect_to_record(model, res_id, access_token=access_token, **kwargs) - if issubclass(type(request.env[model]), request.env.registry['portal.mixin']): + if isinstance(request.env[model], request.env.registry['portal.mixin']): uid = request.session.uid or request.env.ref('base.public_user').id record_sudo = request.env[model].sudo().browse(res_id).exists() try: diff --git a/addons/portal/models/mail_thread.py b/addons/portal/models/mail_thread.py index e1ecb562819..f4f19b5319f 100644 --- a/addons/portal/models/mail_thread.py +++ b/addons/portal/models/mail_thread.py @@ -24,7 +24,7 @@ class MailThread(models.AbstractModel): if not self: return groups - portal_enabled = isinstance(self, type(self.env['portal.mixin'])) + portal_enabled = isinstance(self, self.env.registry['portal.mixin']) if not portal_enabled: return groups diff --git a/addons/rating/models/mail_thread.py b/addons/rating/models/mail_thread.py index e33f6e0c334..e855fcadb8a 100644 --- a/addons/rating/models/mail_thread.py +++ b/addons/rating/models/mail_thread.py @@ -136,7 +136,7 @@ class MailThread(models.AbstractModel): raise ValueError(_('Invalid token or rating.')) rating.write({'rating': rate, 'feedback': feedback, 'consumed': True}) - if issubclass(type(self), self.env.registry['mail.thread']): + if isinstance(self, self.env.registry['mail.thread']): if subtype_xmlid is None: subtype_id = self._rating_apply_get_default_subtype_id() else: diff --git a/addons/sms/wizard/sms_composer.py b/addons/sms/wizard/sms_composer.py index ab5704ac46e..ec744f46ac4 100644 --- a/addons/sms/wizard/sms_composer.py +++ b/addons/sms/wizard/sms_composer.py @@ -103,7 +103,7 @@ class SendSMS(models.TransientModel): continue records = composer._get_records() - if records and issubclass(type(records), self.pool['mail.thread']): + if records and isinstance(records, self.pool['mail.thread']): res = records._sms_get_recipients_info(force_field=composer.number_field_name, partner_fallback=not composer.comment_single_recipient) composer.recipient_valid_count = len([rid for rid, rvalues in res.items() if rvalues['sanitized']]) composer.recipient_invalid_count = len([rid for rid, rvalues in res.items() if not rvalues['sanitized']]) @@ -116,7 +116,7 @@ class SendSMS(models.TransientModel): def _compute_recipient_single_stored(self): for composer in self: records = composer._get_records() - if not records or not issubclass(type(records), self.pool['mail.thread']) or not composer.comment_single_recipient: + if not records or not isinstance(records, self.pool['mail.thread']) or not composer.comment_single_recipient: composer.recipient_single_number_itf = '' continue records.ensure_one() @@ -130,7 +130,7 @@ class SendSMS(models.TransientModel): def _compute_recipient_single_non_stored(self): for composer in self: records = composer._get_records() - if not records or not issubclass(type(records), self.pool['mail.thread']) or not composer.comment_single_recipient: + if not records or not isinstance(records, self.pool['mail.thread']) or not composer.comment_single_recipient: composer.recipient_single_description = False composer.recipient_single_number = '' continue @@ -194,7 +194,7 @@ class SendSMS(models.TransientModel): if self.composition_mode == 'numbers': return self._action_send_sms_numbers() elif self.composition_mode == 'comment': - if records is None or not issubclass(type(records), self.pool['mail.thread']): + if records is None or not isinstance(records, self.pool['mail.thread']): return self._action_send_sms_numbers() if self.comment_single_recipient: return self._action_send_sms_comment_single(records) @@ -240,7 +240,7 @@ class SendSMS(models.TransientModel): sms_record_values = self._prepare_mass_sms_values(records) sms_all = self._prepare_mass_sms(records, sms_record_values) - if sms_all and self.mass_keep_log and records and issubclass(type(records), self.pool['mail.thread']): + if sms_all and self.mass_keep_log and records and isinstance(records, self.pool['mail.thread']): log_values = self._prepare_mass_log_values(records, sms_record_values) records._message_log_batch(**log_values) diff --git a/addons/web/models/base_document_layout.py b/addons/web/models/base_document_layout.py index 2c40c6b8c11..eec07cd18cc 100644 --- a/addons/web/models/base_document_layout.py +++ b/addons/web/models/base_document_layout.py @@ -208,7 +208,7 @@ class BaseDocumentLayout(models.TransientModel): if not logo: return False, False # The "===" gives different base64 encoding a correct padding - logo += b'===' if type(logo) == bytes else '===' + logo += b'===' if isinstance(logo, bytes) else '===' try: # Catches exceptions caused by logo not being an image image = tools.image_fix_orientation(tools.base64_to_image(logo)) diff --git a/addons/web/models/ir_qweb_fields.py b/addons/web/models/ir_qweb_fields.py index 225d156c171..2bf7f7164b3 100644 --- a/addons/web/models/ir_qweb_fields.py +++ b/addons/web/models/ir_qweb_fields.py @@ -39,7 +39,7 @@ class Image(models.AbstractModel): sha = hashlib.sha512(str(getattr(record, 'write_date', fields.Datetime.now())).encode('utf-8')).hexdigest()[:7] max_size = '' if max_size is None else '/%s' % max_size - if options.get('filename-field') and getattr(record, options['filename-field'], None): + if options.get('filename-field') and options['filename-field'] in record and record[options['filename-field']]: filename = record[options['filename-field']] elif options.get('filename'): filename = options['filename'] @@ -73,7 +73,7 @@ class Image(models.AbstractModel): src, src_zoom = self._get_src_urls(record, field_name, options) - if options.get('alt-field') and getattr(record, options['alt-field'], None): + if options.get('alt-field') and options['alt-field'] in record and record[options['alt-field']]: alt = escape(record[options['alt-field']]) elif options.get('alt'): alt = options['alt'] diff --git a/addons/web_editor/models/ir_qweb_fields.py b/addons/web_editor/models/ir_qweb_fields.py index 7df43391ca6..118e91fe9e0 100644 --- a/addons/web_editor/models/ir_qweb_fields.py +++ b/addons/web_editor/models/ir_qweb_fields.py @@ -211,7 +211,7 @@ class ManyToOne(models.AbstractModel): def attributes(self, record, field_name, options, values): attrs = super(ManyToOne, self).attributes(record, field_name, options, values) if options.get('inherit_branding'): - many2one = getattr(record, field_name) + many2one = record[field_name] if many2one: attrs['data-oe-many2one-id'] = many2one.id attrs['data-oe-many2one-model'] = many2one._name diff --git a/odoo/addons/base/models/ir_actions.py b/odoo/addons/base/models/ir_actions.py index bb6da7e0167..9309c23ca55 100644 --- a/odoo/addons/base/models/ir_actions.py +++ b/odoo/addons/base/models/ir_actions.py @@ -188,7 +188,7 @@ class IrActions(models.Model): :return: A read() view of the ir.actions.action safe for web use """ record = self.env.ref(full_xml_id) - assert isinstance(self.env[record._name], type(self)) + assert isinstance(self.env[record._name], self.env.registry[self._name]) return record._get_action_dict() def _get_action_dict(self): @@ -725,7 +725,7 @@ class IrActionsServer(models.Model): def _get_runner(self): multi = True - t = type(self) + t = self.env.registry[self._name] fn = getattr(t, f'_run_action_{self.state}_multi', None)\ or getattr(t, f'run_action_{self.state}_multi', None) if not fn: @@ -739,7 +739,7 @@ class IrActionsServer(models.Model): def _register_hook(self): super()._register_hook() - for cls in type(self).mro(): + for cls in self.env.registry[self._name].mro(): for symbol in vars(cls).keys(): if symbol.startswith('run_action_'): _logger.warning( diff --git a/odoo/addons/base/models/ir_autovacuum.py b/odoo/addons/base/models/ir_autovacuum.py index 39c07ad40a8..4434e96e5d6 100644 --- a/odoo/addons/base/models/ir_autovacuum.py +++ b/odoo/addons/base/models/ir_autovacuum.py @@ -31,7 +31,7 @@ class AutoVacuum(models.AbstractModel): raise AccessDenied() for model in self.env.values(): - cls = type(model) + cls = self.env.registry[self._name] for attr, func in inspect.getmembers(cls, is_autovacuum): _logger.debug('Calling %s.%s()', model, attr) try: diff --git a/odoo/addons/base/models/ir_model.py b/odoo/addons/base/models/ir_model.py index 2f4ffb39f31..a55d711afd1 100644 --- a/odoo/addons/base/models/ir_model.py +++ b/odoo/addons/base/models/ir_model.py @@ -384,7 +384,7 @@ class IrModel(models.Model): 'model': model._name, 'name': model._description, 'order': model._order, - 'info': next(cls.__doc__ for cls in type(model).mro() if cls.__doc__), + 'info': next(cls.__doc__ for cls in self.env.registry[model._name].mro() if cls.__doc__), 'state': 'manual' if model._custom else 'base', 'transient': model._transient, } @@ -1859,7 +1859,7 @@ class IrModelConstraint(models.Model): # map each constraint on the name of the module where it is defined constraint_module = { constraint[0]: cls._module - for cls in reversed(type(model).mro()) + for cls in reversed(self.env.registry[model._name].mro()) if models.is_definition_class(cls) for constraint in getattr(cls, '_local_sql_constraints', ()) } diff --git a/odoo/addons/base/models/ir_module.py b/odoo/addons/base/models/ir_module.py index c9b69a1798a..ddce47d8ac5 100644 --- a/odoo/addons/base/models/ir_module.py +++ b/odoo/addons/base/models/ir_module.py @@ -462,7 +462,7 @@ class Module(models.Model): # configure the CoA on his own company, which makes no sense. if request: request.allowed_company_ids = self.env.companies.ids - return self._button_immediate_function(type(self).button_install) + return self._button_immediate_function(self.env.registry[self._name].button_install) @assert_log_admin_access def button_install_cancel(self): @@ -615,7 +615,7 @@ class Module(models.Model): returns the next res.config action to execute """ _logger.info('User #%d triggered module uninstallation', self.env.uid) - return self._button_immediate_function(type(self).button_uninstall) + return self._button_immediate_function(self.env.registry[self._name].button_uninstall) @assert_log_admin_access def button_uninstall(self): @@ -653,7 +653,7 @@ class Module(models.Model): Upgrade the selected module(s) immediately and fully, return the next res.config action to execute """ - return self._button_immediate_function(type(self).button_upgrade) + return self._button_immediate_function(self.env.registry[self._name].button_upgrade) @assert_log_admin_access def button_upgrade(self): diff --git a/odoo/addons/base/models/ir_qweb_fields.py b/odoo/addons/base/models/ir_qweb_fields.py index 47b2a412cb3..f3844173ab2 100644 --- a/odoo/addons/base/models/ir_qweb_fields.py +++ b/odoo/addons/base/models/ir_qweb_fields.py @@ -809,7 +809,7 @@ class QwebView(models.AbstractModel): @api.model def record_to_html(self, record, field_name, options): - view = getattr(record, field_name) + view = record[field_name] if not view: return '' diff --git a/odoo/addons/base/models/ir_ui_view.py b/odoo/addons/base/models/ir_ui_view.py index 94cc8d5bbd9..dcdc071a7aa 100644 --- a/odoo/addons/base/models/ir_ui_view.py +++ b/odoo/addons/base/models/ir_ui_view.py @@ -628,8 +628,8 @@ actual arch. views = self.browse(row[0] for row in rows) # optimization: fill in cache of inherit_id and mode - self.env.cache.update(views, type(self).inherit_id, [row[1] for row in rows]) - self.env.cache.update(views, type(self).mode, [row[2] for row in rows]) + self.env.cache.update(views, self._fields['inherit_id'], [row[1] for row in rows]) + self.env.cache.update(views, self._fields['mode'], [row[2] for row in rows]) # During an upgrade, we can only use the views that have been # fully upgraded already. @@ -1266,7 +1266,7 @@ actual arch. if func is not None: return func(node, name_manager) # by default views are non-editable - return node.tag not in (item[0] for item in type(self).type.selection) + return node.tag not in (item[0] for item in self._fields['type'].selection) def _editable_tag_form(self, node, name_manager): return True @@ -1488,7 +1488,7 @@ actual arch. elif not name: self._raise_view_error(_("Button must have a name"), node) elif type_ == 'object': - func = getattr(type(name_manager.model), name, None) + func = getattr(name_manager.model, name, None) if not func: msg = _( "%(action_name)s is not a valid action on %(model_name)s", @@ -1504,7 +1504,7 @@ actual arch. ) self._raise_view_error(msg, node) try: - inspect.signature(func).bind(self=name_manager.model) + inspect.signature(func).bind() except TypeError: msg = "%s on %s has parameters and cannot be called from a button" self._log_view_warning(msg % (name, name_manager.model._name), node) diff --git a/odoo/addons/base/models/res_partner.py b/odoo/addons/base/models/res_partner.py index 9ef8c02ea35..2e2c4d2f27d 100644 --- a/odoo/addons/base/models/res_partner.py +++ b/odoo/addons/base/models/res_partner.py @@ -1000,7 +1000,7 @@ class Partner(models.Model): 'company_name': self.commercial_company_name or '', }) for field in self._formatting_address_fields(): - args[field] = getattr(self, field) or '' + args[field] = self[field] or '' if without_company: args['company_name'] = '' elif self.commercial_company_name: diff --git a/odoo/addons/base/models/res_users.py b/odoo/addons/base/models/res_users.py index 9957aff815f..b447e6a602c 100644 --- a/odoo/addons/base/models/res_users.py +++ b/odoo/addons/base/models/res_users.py @@ -1340,7 +1340,7 @@ class UsersImplied(models.Model): user = self.new(values) gs = user.groups_id._origin gs = gs | gs.trans_implied_ids - values['groups_id'] = type(self).groups_id.convert_to_write(gs, user) + values['groups_id'] = self._fields['groups_id'].convert_to_write(gs, user) return super(UsersImplied, self).create(vals_list) def write(self, values): diff --git a/odoo/addons/base/wizard/base_partner_merge.py b/odoo/addons/base/wizard/base_partner_merge.py index 2dfeaca7839..300ddd72c84 100644 --- a/odoo/addons/base/wizard/base_partner_merge.py +++ b/odoo/addons/base/wizard/base_partner_merge.py @@ -406,7 +406,7 @@ class MergePartnerAutomatic(models.TransientModel): for field_name in self._fields: if field_name.startswith(group_by_prefix): - if getattr(self, field_name, False): + if field_name in self and self[field_name]: groups.append(field_name[len(group_by_prefix):]) if not groups: diff --git a/odoo/addons/test_limits/models.py b/odoo/addons/test_limits/models.py index fe659c863fb..679a92c586a 100644 --- a/odoo/addons/test_limits/models.py +++ b/odoo/addons/test_limits/models.py @@ -25,7 +25,7 @@ class m(models.Model): @api.model def leak_memory(self, size): if not hasattr(self, 'l'): - type(self).l = [] + self.env.registry[self._name].l = [] self.l.append([0] * size) return True diff --git a/odoo/addons/test_read_group/models.py b/odoo/addons/test_read_group/models.py index 34306503641..7d1634ea7d9 100644 --- a/odoo/addons/test_read_group/models.py +++ b/odoo/addons/test_read_group/models.py @@ -49,7 +49,7 @@ class GroupOnSelection(models.Model): def _expand_states(self, states, domain, order): # return all possible states, in order - return [key for key, val in type(self).state.selection] + return [key for key, val in self._fields['state'].selection] class FillTemporal(models.Model): diff --git a/odoo/api.py b/odoo/api.py index 1e9ced399f9..31823ef7a07 100644 --- a/odoo/api.py +++ b/odoo/api.py @@ -456,7 +456,9 @@ def _call_kw_multi(method, self, args, kwargs): def call_kw(model, name, args, kwargs): """ Invoke the given method ``name`` on the recordset ``model``. """ - method = getattr(type(model), name) + method = getattr(type(model), name, None) + if not method: + raise AttributeError(f"The method '{name}' does not exist on the model '{model._name}'") api = getattr(method, '_api', None) if api == 'model': result = _call_kw_model(method, model, args, kwargs) diff --git a/odoo/models.py b/odoo/models.py index a9370ef4842..d2f7fe54ae0 100644 --- a/odoo/models.py +++ b/odoo/models.py @@ -637,7 +637,7 @@ class BaseModel(metaclass=MetaModel): @api.model def _add_field(self, name, field): """ Add the given ``field`` under the given ``name`` in the class """ - cls = type(self) + cls = self.env.registry[self._name] # Assert the name is an existing field in the model, or any model in the _inherits # or a custom field (starting by `x_`) @@ -667,7 +667,7 @@ class BaseModel(metaclass=MetaModel): """ Remove the field with the given ``name`` from the model. This method should only be used for manual fields. """ - cls = type(self) + cls = self.env.registry[self._name] field = cls._fields.pop(name, None) discardattr(cls, name) if cls._rec_name == name: @@ -852,7 +852,7 @@ class BaseModel(metaclass=MetaModel): return func(self) return wrapper - cls = type(self) + cls = self.env.registry[self._name] methods = [] for attr, func in getmembers(cls, is_constraint): if callable(func._constrains): @@ -875,7 +875,7 @@ class BaseModel(metaclass=MetaModel): def is_ondelete(func): return callable(func) and hasattr(func, '_ondelete') - cls = type(self) + cls = self.env.registry[self._name] methods = [func for _, func in getmembers(cls, is_ondelete)] # optimization: memoize results on cls, it will not be recomputed cls._ondelete_methods = methods @@ -888,7 +888,7 @@ class BaseModel(metaclass=MetaModel): return callable(func) and hasattr(func, '_onchange') # collect onchange methods on the model's class - cls = type(self) + cls = self.env.registry[self._name] methods = defaultdict(list) for attr, func in getmembers(cls, is_onchange): missing = [] @@ -1102,7 +1102,7 @@ class BaseModel(metaclass=MetaModel): # collect all the tuples in "lines" (along with their coordinates) for i, line in enumerate(lines): for j, cell in enumerate(line): - if type(cell) is tuple: + if isinstance(cell, tuple): bymodels[cell[0]].add(cell[1]) xidmap[cell].append((i, j)) # for each model, xid-export everything and inject in matrix @@ -2196,7 +2196,7 @@ class BaseModel(metaclass=MetaModel): # columns should be displayed even if they don't contain any record. group_expand = field.group_expand if isinstance(group_expand, str): - group_expand = getattr(type(self), group_expand) + group_expand = getattr(self.env.registry[self._name], group_expand) assert callable(group_expand) # determine all groups that should be returned @@ -3253,13 +3253,13 @@ class BaseModel(metaclass=MetaModel): field.required = True if field.ondelete.lower() not in ('cascade', 'restrict'): field.ondelete = 'cascade' - type(self)._inherits = {**self._inherits, field.comodel_name: field.name} + self.pool[self._name]._inherits = {**self._inherits, field.comodel_name: field.name} self.pool[field.comodel_name]._inherits_children.add(self._name) @api.model def _prepare_setup(self): """ Prepare the setup of the model. """ - cls = type(self) + cls = self.env.registry[self._name] cls._setup_done = False # changing base classes is costly, do it only when necessary @@ -3273,7 +3273,7 @@ class BaseModel(metaclass=MetaModel): @api.model def _setup_base(self): """ Determine the inherited and custom fields of the model. """ - cls = type(self) + cls = self.env.registry[self._name] if cls._setup_done: return @@ -3358,7 +3358,7 @@ class BaseModel(metaclass=MetaModel): @api.model def _setup_fields(self): """ Setup the fields, except for recomputation triggers. """ - cls = type(self) + cls = self.env.registry[self._name] # set up fields bad_fields = [] @@ -3381,7 +3381,7 @@ class BaseModel(metaclass=MetaModel): @api.model def _setup_complete(self): """ Setup recomputation triggers, and complete the model setup. """ - cls = type(self) + cls = self.env.registry[self._name] # register constraints and onchange methods cls._init_constraints_onchanges() @@ -6574,7 +6574,7 @@ class BaseModel(metaclass=MetaModel): """ if isinstance(key, str): # important: one must call the field's getter - return self._fields[key].__get__(self, type(self)) + return self._fields[key].__get__(self, self.env.registry[self._name]) elif isinstance(key, slice): return self.browse(self._ids[key]) else: diff --git a/odoo/modules/registry.py b/odoo/modules/registry.py index 09fcef82488..96647c87e5e 100644 --- a/odoo/modules/registry.py +++ b/odoo/modules/registry.py @@ -707,8 +707,8 @@ class Registry(Mapping): env = odoo.api.Environment(cr, SUPERUSER_ID, {}) table2model = { model._table: name - for name, model in env.items() - if not model._abstract and model.__class__._table_query is None + for name, model in env.registry.items() + if not model._abstract and model._table_query is None } missing_tables = set(table2model).difference(existing_tables(cr, table2model)) diff --git a/odoo/service/server.py b/odoo/service/server.py index 7e1d9c546f2..d8dcfbe7176 100644 --- a/odoo/service/server.py +++ b/odoo/service/server.py @@ -362,7 +362,7 @@ class CommonServer(object): cls._on_stop_funcs.append(func) def stop(self): - for func in type(self)._on_stop_funcs: + for func in self._on_stop_funcs: try: _logger.debug("on_close call %s", func) func() diff --git a/odoo/tools/date_utils.py b/odoo/tools/date_utils.py index b66483be614..05ab361cb57 100644 --- a/odoo/tools/date_utils.py +++ b/odoo/tools/date_utils.py @@ -8,14 +8,24 @@ from dateutil.relativedelta import relativedelta from .func import lazy from odoo.loglevels import ustr + +def date_type(value): + ''' Return either the datetime.datetime class or datetime.date type whether `value` is a datetime or a date. + + :param value: A datetime.datetime or datetime.date object. + :return: datetime.datetime or datetime.date + ''' + return datetime if isinstance(value, datetime) else date + + def get_month(date): ''' Compute the month dates range on which the 'date' parameter belongs to. :param date: A datetime.datetime or datetime.date object. :return: A tuple (date_from, date_to) having the same object type as the 'date' parameter. ''' - date_from = type(date)(date.year, date.month, 1) - date_to = type(date)(date.year, date.month, calendar.monthrange(date.year, date.month)[1]) + date_from = date_type(date)(date.year, date.month, 1) + date_to = date_type(date)(date.year, date.month, calendar.monthrange(date.year, date.month)[1]) return date_from, date_to @@ -36,7 +46,7 @@ def get_quarter(date): ''' quarter_number = get_quarter_number(date) month_from = ((quarter_number - 1) * 3) + 1 - date_from = type(date)(date.year, month_from, 1) + date_from = date_type(date)(date.year, month_from, 1) date_to = (date_from + relativedelta(months=2)) date_to = date_to.replace(day=calendar.monthrange(date_to.year, date_to.month)[1]) return date_from, date_to @@ -60,18 +70,18 @@ def get_fiscal_year(date, day=31, month=12): return min(day, max_day) day = fix_day(date.year, month, day) - date_to = type(date)(date.year, month, day) + date_to = date_type(date)(date.year, month, day) if date <= date_to: date_from = date_to - relativedelta(years=1) day = fix_day(date_from.year, date_from.month, date_from.day) - date_from = type(date)(date_from.year, date_from.month, day) + date_from = date_type(date)(date_from.year, date_from.month, day) date_from += relativedelta(days=1) else: date_from = date_to + relativedelta(days=1) date_to = date_to + relativedelta(years=1) day = fix_day(date_to.year, date_to.month, date_to.day) - date_to = type(date)(date_to.year, date_to.month, day) + date_to = date_type(date)(date_to.year, date_to.month, day) return date_from, date_to diff --git a/odoo/tools/mail.py b/odoo/tools/mail.py index 5ec2d133ca0..19485809702 100644 --- a/odoo/tools/mail.py +++ b/odoo/tools/mail.py @@ -493,7 +493,8 @@ def append_content_to_html(html, content, plaintext=True, preserve=False, contai def prepend_html_content(html_body, html_content): """Prepend some HTML content at the beginning of an other HTML content.""" - html_content = type(html_content)(re.sub(r'(?i)(]*>)', '', html_content)) + replacement = re.sub(r'(?i)(]*>)', '', html_content) + html_content = markupsafe.Markup(replacement) if isinstance(html_content, markupsafe.Markup) else replacement html_content = html_content.strip() body_match = re.search(r']*>', html_body) or re.search(r']*>', html_body) diff --git a/odoo/tools/misc.py b/odoo/tools/misc.py index 9b465098900..6936cbe25ea 100644 --- a/odoo/tools/misc.py +++ b/odoo/tools/misc.py @@ -851,7 +851,7 @@ class lower_logging(logging.Handler): record.levelname = f'_{record.levelname}' record.levelno = self.to_level self.had_error_log = True - record.args = tuple(arg.replace('Traceback (most recent call last):', '_Traceback_ (most recent call last):') if type(arg) is str else arg for arg in record.args) # pylint: disable=unidiomatic-typecheck + record.args = tuple(arg.replace('Traceback (most recent call last):', '_Traceback_ (most recent call last):') if isinstance(arg, str) else arg for arg in record.args) if logging.getLogger(record.name).isEnabledFor(record.levelno): for handler in self.old_handlers: @@ -1698,7 +1698,7 @@ class DotDict(dict): """ def __getattr__(self, attrib): val = self.get(attrib) - return DotDict(val) if type(val) is dict else val + return DotDict(val) if isinstance(val, dict) else val def get_diff(data_from, data_to, custom_style=False, dark_color_scheme=False):