def _prepare_invoice_report(self, pdf_writer, edi_document): self.ensure_one() if self.code != 'facturx_1_0_05': return super()._prepare_invoice_report(pdf_writer, edi_document) if not edi_document.attachment_id: return pdf_writer.embed_odoo_attachment(edi_document.attachment_id, subtype='application/xml') if not pdf_writer.is_pdfa and str2bool( self.env['ir.config_parameter'].sudo().get_param( 'edi.use_pdfa', 'False')): try: pdf_writer.convert_to_pdfa() except Exception as e: _logger.exception("Error while converting to PDF/A: %s", e) metadata_template = self.env.ref( 'account_edi_facturx.account_invoice_pdfa_3_facturx_metadata', raise_if_not_found=False) if metadata_template: pdf_writer.add_file_metadata( metadata_template._render({ 'title': edi_document.move_id.name, 'date': fields.Date.context_today(self), }))
def _postprocess_contents(self, values): ICP = self.env['ir.config_parameter'].sudo().get_param supported_subtype = ICP('base.image_autoresize_extensions', 'png,jpeg,gif,bmp,tif').split(',') mimetype = values['mimetype'] = self._compute_mimetype(values) _type, _subtype = mimetype.split('/') is_image_resizable = _type == 'image' and _subtype in supported_subtype if is_image_resizable and (values.get('datas') or values.get('raw')): is_raw = values.get('raw') # Can be set to 0 to skip the resize max_resolution = ICP('base.image_autoresize_max_px', '1920x1920') if str2bool(max_resolution, True): try: img = fn_quality = False if is_raw: img = ImageProcess(False, verify_resolution=False) img.image = Image.open(io.BytesIO(values['raw'])) img.original_format = (img.image.format or '').upper() fn_quality = img.image_quality else: # datas img = ImageProcess(values['datas'], verify_resolution=False) fn_quality = img.image_quality_base64 w, h = img.image.size nw, nh = map(int, max_resolution.split('x')) if w > nw or h > nh: img.resize(nw, nh) quality = int(ICP('base.image_autoresize_quality', 80)) values[is_raw and 'raw' or 'datas'] = fn_quality(quality=quality) except UserError as e: # Catch error during test where we provide fake image # raise UserError(_("This file could not be decoded as an image file. Please try with a different file.")) _logger.info('Post processing ignored : %s', e) return values
def _allow_saml_and_password(self): """Know if both SAML and local password auth methods can coexist.""" return tools.str2bool( self.env['ir.config_parameter'].sudo().get_param( 'auth_saml.allow_saml.uid_and_internal_password', 'True' ) )
def _allow_saml_and_password(self): """Can both SAML and local password auth methods can coexist.""" return tools.str2bool( self.env["ir.config_parameter"] .sudo() .get_param("auth_saml.allow_saml_uid_and_internal_password", "True") )
def _post_pdf(self, save_in_attachment, pdf_content=None, res_ids=None): # OVERRIDE if self.model == 'account.move' and res_ids and len(res_ids) == 1: invoice = self.env['account.move'].browse(res_ids) if invoice.is_sale_document() and invoice.state != 'draft': xml_content = invoice._export_as_facturx_xml() reader_buffer = BytesIO(pdf_content) reader = PdfFileReader(reader_buffer) writer = OdooPdfFileWriter() writer.cloneReaderDocumentRoot(reader) if tools.str2bool( self.env['ir.config_parameter'].sudo().get_param( 'edi.use_pdfa', 'False')): try: writer.convert_to_pdfa() except Exception as e: _logger.exception( "Error while converting to PDF/A: %s", e) metadata_template = self.env.ref( 'account_facturx.account_invoice_pdfa_3_facturx_metadata', False) if metadata_template: metadata_content = metadata_template.render({ 'title': invoice.name, 'date': fields.Date.context_today(self), }) writer.add_file_metadata(metadata_content) writer.addAttachment('factur-x.xml', xml_content, '/application#2Fxml') buffer = BytesIO() writer.write(buffer) pdf_content = buffer.getvalue() buffer.close() reader_buffer.close() return super(IrActionsReport, self)._post_pdf(save_in_attachment, pdf_content=pdf_content, res_ids=res_ids)
def _send(self, auto_commit=False, raise_exception=False, smtp_session=None): IrMailServer = self.env['ir.mail_server'] IrAttachment = self.env['ir.attachment'] for mail_id in self.ids: success_pids = [] failure_type = None processing_pid = None mail = None try: mail = self.browse(mail_id) if mail.state != 'outgoing': if mail.state != 'exception' and mail.auto_delete: mail.sudo().unlink() continue # remove attachments if user send the link with the access_token body = mail.body_html or '' attachments = mail.attachment_ids for link in re.findall(r'/web/(?:content|image)/([0-9]+)', body): attachments = attachments - IrAttachment.browse(int(link)) # load attachment binary data with a separate read(), as prefetching all # `datas` (binary field) could bloat the browse cache, triggerring # soft/hard mem limits with temporary data. attachments = [(a['name'], base64.b64decode(a['datas']), a['mimetype']) for a in attachments.sudo().read( ['name', 'datas', 'mimetype']) if a['datas'] is not False] # specific behavior to customize the send email for notified partners email_list = [] if mail.email_to: email_list.append(mail._send_prepare_values()) for partner in mail.recipient_ids: values = mail._send_prepare_values(partner=partner) values['partner_id'] = partner email_list.append(values) # headers headers = {} ICP = self.env['ir.config_parameter'].sudo() bounce_alias = ICP.get_param("mail.bounce.alias") bounce_alias_static = tools.str2bool( ICP.get_param("mail.bounce.alias.static", "False")) catchall_domain = ICP.get_param("mail.catchall.domain") if bounce_alias and catchall_domain: if bounce_alias_static: headers['Return-Path'] = '%s@%s' % (bounce_alias, catchall_domain) elif mail.mail_message_id.is_thread_message(): headers['Return-Path'] = '%s+%d-%s-%d@%s' % ( bounce_alias, mail.id, mail.model, mail.res_id, catchall_domain) else: headers['Return-Path'] = '%s+%d@%s' % ( bounce_alias, mail.id, catchall_domain) if mail.headers: try: headers.update(ast.literal_eval(mail.headers)) except Exception: pass # Writing on the mail object may fail (e.g. lock on user) which # would trigger a rollback *after* actually sending the email. # To avoid sending twice the same email, provoke the failure earlier mail.write({ 'state': 'exception', 'failure_reason': _('Error without exception. Probably due do sending an email without computed recipients.' ), }) # Update notification in a transient exception state to avoid concurrent # update in case an email bounces while sending all emails related to current # mail record. notifs = self.env['mail.notification'].search([ ('notification_type', '=', 'email'), ('mail_id', 'in', mail.ids), ('notification_status', 'not in', ('sent', 'canceled')) ]) if notifs: notif_msg = _( 'Error without exception. Probably due do concurrent access update of notification records. Please see with an administrator.' ) notifs.sudo().write({ 'notification_status': 'exception', 'failure_type': 'UNKNOWN', 'failure_reason': notif_msg, }) # `test_mail_bounce_during_send`, force immediate update to obtain the lock. # see rev. 56596e5240ef920df14d99087451ce6f06ac6d36 notifs.flush(fnames=[ 'notification_status', 'failure_type', 'failure_reason' ], records=notifs) # build an RFC2822 email.message.Message object and send it without queuing res = None for email in email_list: msg = IrMailServer.build_email( email_from=mail.email_from, email_to=email.get('email_to'), subject=mail.subject, body=email.get('body'), body_alternative=email.get('body_alternative'), email_cc=tools.email_split(mail.email_cc), reply_to=mail.reply_to, attachments=attachments, message_id=mail.message_id, references=mail.references, object_id=mail.res_id and ('%s-%s' % (mail.res_id, mail.model)), subtype='html', subtype_alternative='plain', headers=headers) processing_pid = email.pop("partner_id", None) try: res = IrMailServer.send_email( msg, mail_server_id=mail.mail_server_id.id, smtp_session=smtp_session) if processing_pid: success_pids.append(processing_pid) processing_pid = None except AssertionError as error: if str(error) == IrMailServer.NO_VALID_RECIPIENT: failure_type = "RECIPIENT" # No valid recipient found for this particular # mail item -> ignore error to avoid blocking # delivery to next recipients, if any. If this is # the only recipient, the mail will show as failed. _logger.info( "Ignoring invalid recipients for mail.mail %s: %s", mail.message_id, email.get('email_to')) else: raise if res: # mail has been sent at least once, no major exception occured mail.write({ 'state': 'sent', 'message_id': res, 'failure_reason': False }) _logger.info( 'Mail with ID %r and Message-Id %r successfully sent', mail.id, mail.message_id) # /!\ can't use mail.state here, as mail.refresh() will cause an error # see revid:[email protected] in 6.1 mail._postprocess_sent_message(success_pids=success_pids, failure_type=failure_type) except MemoryError: # prevent catching transient MemoryErrors, bubble up to notify user or abort cron job # instead of marking the mail as failed _logger.exception( 'MemoryError while processing mail with ID %r and Msg-Id %r. Consider raising the --limit-memory-hard startup option', mail.id, mail.message_id) # mail status will stay on ongoing since transaction will be rollback raise except (psycopg2.Error, smtplib.SMTPServerDisconnected): # If an error with the database or SMTP session occurs, chances are that the cursor # or SMTP session are unusable, causing further errors when trying to save the state. _logger.exception( 'Exception while processing mail with ID %r and Msg-Id %r.', mail.id, mail.message_id) raise except Exception as e: failure_reason = tools.ustr(e) _logger.exception('failed sending mail (id: %s) due to %s', mail.id, failure_reason) mail.write({ 'state': 'exception', 'failure_reason': failure_reason }) mail._postprocess_sent_message(success_pids=success_pids, failure_reason=failure_reason, failure_type='UNKNOWN') if raise_exception: if isinstance(e, (AssertionError, UnicodeEncodeError)): if isinstance(e, UnicodeEncodeError): value = "Invalid text: %s" % e.object else: value = '. '.join(e.args) raise MailDeliveryException(value) raise if auto_commit is True: self._cr.commit() return True