diff --git a/mail_ux/__init__.py b/mail_ux/__init__.py index d0337769..173091eb 100644 --- a/mail_ux/__init__.py +++ b/mail_ux/__init__.py @@ -3,3 +3,4 @@ # directory ############################################################################## from . import models +from . import wizard diff --git a/mail_ux/__manifest__.py b/mail_ux/__manifest__.py index 0fb49370..2febe4ec 100644 --- a/mail_ux/__manifest__.py +++ b/mail_ux/__manifest__.py @@ -36,6 +36,9 @@ "mail", ], "data": [ + "security/ir.model.access.csv", + "wizard/mail_server_test_wizard_views.xml", + "views/ir_mail_server_views.xml", "views/res_users_views.xml", ], "demo": [], diff --git a/mail_ux/models/__init__.py b/mail_ux/models/__init__.py index dd002ae6..c27049e8 100644 --- a/mail_ux/models/__init__.py +++ b/mail_ux/models/__init__.py @@ -2,6 +2,7 @@ # For copyright and license notices, see __manifest__.py file in module root # directory ############################################################################## +from . import ir_http +from . import ir_mail_server from . import mail_compose_message from . import res_users -from . import ir_http diff --git a/mail_ux/models/ir_mail_server.py b/mail_ux/models/ir_mail_server.py new file mode 100644 index 00000000..82d27a6d --- /dev/null +++ b/mail_ux/models/ir_mail_server.py @@ -0,0 +1,29 @@ +############################################################################## +# For copyright and license notices, see __manifest__.py file in module root +# directory +############################################################################## +from odoo import _, models + + +class IrMailServer(models.Model): + _inherit = "ir.mail_server" + + def action_send_test_mail(self): + """Test the SMTP connection and, if successful, open the test mail wizard. + + Raises the native UserError if the connection test fails so the user + sees the standard Odoo connection-error message. + """ + self.ensure_one() + # Test connection first; any UserError propagates natively to the UI + self.test_smtp_connection() + return { + "type": "ir.actions.act_window", + "name": _("Enviar mail de prueba"), + "res_model": "mail.server.test.wizard", + "view_mode": "form", + "target": "new", + "context": { + "default_mail_server_id": self.id, + }, + } diff --git a/mail_ux/security/ir.model.access.csv b/mail_ux/security/ir.model.access.csv new file mode 100644 index 00000000..1f0ce645 --- /dev/null +++ b/mail_ux/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_mail_server_test_wizard_user,mail.server.test.wizard user,model_mail_server_test_wizard,base.group_system,1,1,1,0 diff --git a/mail_ux/views/ir_mail_server_views.xml b/mail_ux/views/ir_mail_server_views.xml new file mode 100644 index 00000000..f64e7df3 --- /dev/null +++ b/mail_ux/views/ir_mail_server_views.xml @@ -0,0 +1,18 @@ + + + + ir.mail_server.form.send.test + ir.mail_server + + + + + + diff --git a/mail_ux/wizard/__init__.py b/mail_ux/wizard/__init__.py new file mode 100644 index 00000000..6b7de349 --- /dev/null +++ b/mail_ux/wizard/__init__.py @@ -0,0 +1,5 @@ +############################################################################## +# For copyright and license notices, see __manifest__.py file in module root +# directory +############################################################################## +from . import mail_server_test_wizard diff --git a/mail_ux/wizard/mail_server_test_wizard.py b/mail_ux/wizard/mail_server_test_wizard.py new file mode 100644 index 00000000..e31d3f18 --- /dev/null +++ b/mail_ux/wizard/mail_server_test_wizard.py @@ -0,0 +1,104 @@ +import smtplib +import ssl +from socket import gaierror + +from odoo import _, fields, models +from odoo.exceptions import UserError + + +class MailServerTestWizard(models.TransientModel): + _name = "mail.server.test.wizard" + _description = "Wizard de prueba de servidor de correo saliente" + + mail_server_id = fields.Many2one( + comodel_name="ir.mail_server", + string="Servidor de correo saliente", + required=True, + context={"active_test": False}, + ) + email_to = fields.Char( + string="Correo destinatario", + required=True, + ) + + def action_send_test_mail(self): + """Build and send a test email directly via SMTP, bypassing all + Python-level sending guards (server_mode, mail neutralization, etc.). + + The bypass is achieved by calling ``ir.mail_server.connect()`` with + ``allow_archived=True`` and then invoking ``smtp.send_message()`` + directly — never going through ``send_email()``, which is the layer + where restrictions such as ``server_mode.allow_send_mail`` operate. + """ + self.ensure_one() + IrMailServer = self.env["ir.mail_server"] + + # Verify the user has read access on ir.mail_server before elevating. + IrMailServer.check_access_rights("read") + + # Browse with active_test=False so archived servers (neutralized DBs) + # are accessible, but without bypassing ACLs / record rules. + mail_server = IrMailServer.with_context(active_test=False).browse(self.mail_server_id.id) + if not mail_server.exists(): + raise UserError( + _("No se encontró el servidor de correo. " "Por favor, recargue la página e intente de nuevo.") + ) + mail_server.check_access_rule("read") + mail_server = mail_server.sudo() + + email_from = mail_server._get_test_email_from() + message = IrMailServer.build_email( + email_from=email_from, + email_to=[self.email_to], + subject=_("Prueba de mail desde Odoo"), + body=_("Mail de prueba enviado desde mi base de Odoo. " "Por favor no responder."), + subtype="plain", + ) + + smtp = None + try: + # allow_archived=True is critical for neutralized databases where + # all real servers are deactivated by the neutralize process. + smtp = IrMailServer.connect( + mail_server_id=mail_server.id, + allow_archived=True, + ) + if smtp: + smtp.send_message(message) + except (gaierror, TimeoutError) as e: + raise UserError( + _( + "No se pudo enviar el mail: Sin respuesta del servidor. " "Verifique la dirección y el puerto.\n%s", + e, + ) + ) from e + except smtplib.SMTPRecipientsRefused as e: + raise UserError( + _( + "No se pudo enviar el mail: El servidor rechazó la dirección destinataria.\n%s", + e, + ) + ) from e + except smtplib.SMTPException as e: + raise UserError(_("No se pudo enviar el mail: %s", e)) from e + except ssl.SSLError as e: + raise UserError(_("No se pudo enviar el mail: Error de SSL.\n%s", e)) from e + except Exception as e: + raise UserError(_("No se pudo enviar el mail: %s", e)) from e + finally: + if smtp: + try: + smtp.quit() + except Exception: + pass + + return { + "type": "ir.actions.client", + "tag": "display_notification", + "params": { + "message": _("¡Mail de prueba enviado con éxito! " "Por favor, revise su casilla de correo."), + "type": "success", + "sticky": False, + "next": {"type": "ir.actions.act_window_close"}, + }, + } diff --git a/mail_ux/wizard/mail_server_test_wizard_views.xml b/mail_ux/wizard/mail_server_test_wizard_views.xml new file mode 100644 index 00000000..80e6f337 --- /dev/null +++ b/mail_ux/wizard/mail_server_test_wizard_views.xml @@ -0,0 +1,29 @@ + + + + mail.server.test.wizard.form + mail.server.test.wizard + +
+ + + + + +
+
+
+