diff --git a/accounts/tests/test_admin_email_otp.py b/accounts/tests/test_admin_email_otp.py index 1cb139e1..f3d14f41 100644 --- a/accounts/tests/test_admin_email_otp.py +++ b/accounts/tests/test_admin_email_otp.py @@ -30,3 +30,9 @@ class AdminEmailOtpTestCase(TestCase): response = self.client.post(self.url) self.assertEqual(response.status_code, 302) mock_send.assert_called_once() + + def test_the_smtp_connection_waits_a_bounded_time(self): + from django.core.mail.backends.smtp import EmailBackend + + # None would wait for a server that stopped answering forever + self.assertIsNotNone(EmailBackend().timeout) diff --git a/accounts/tests/test_audit_rows.py b/accounts/tests/test_audit_rows.py index 17b68a65..c75a4f42 100644 --- a/accounts/tests/test_audit_rows.py +++ b/accounts/tests/test_audit_rows.py @@ -85,9 +85,9 @@ class AdminAccountRowTests(TestCase): with patch("accounts.views.send_email_with_otp") as send: self.client.post(url) send.assert_called_once() - with patch("accounts.views.send_email_with_otp", side_effect=SMTPException("refused")), \ - self.assertRaises(SMTPException): - self.client.post(url) + with patch("accounts.views.send_email_with_otp", side_effect=SMTPException("refused")): + response = self.client.post(url, follow=True) + self.assertContains(response, "The OTP QR code could not be mailed to bob: refused") self.assertEqual(rows(), [ ("User bob: mail OTP QR code: no email address", False), ("User bob: mail OTP QR code", True), diff --git a/accounts/views.py b/accounts/views.py index 52a29a02..f85b1b52 100644 --- a/accounts/views.py +++ b/accounts/views.py @@ -343,11 +343,16 @@ def admin_email_otp(request, user_id): user = get_object_or_404(get_user_model(), pk=user_id) subject = f"User {user.username}" if user.email != "": - with audit(request, None, subject, "mail OTP QR code"): - send_email_with_otp(user, get_user_totp_device(user)) - messages.success( - request, _("OTP QR code was emailed to user %(user)s") % {"user": user} - ) + try: + with audit(request, None, subject, "mail OTP QR code"): + send_email_with_otp(user, get_user_totp_device(user)) + except OSError as err: # the SMTP server refused it, or did not answer within EMAIL_TIMEOUT + messages.error(request, _("The OTP QR code could not be mailed to %(user)s: %(error)s") % { + "user": user, "error": err}) + else: + messages.success( + request, _("OTP QR code was emailed to user %(user)s") % {"user": user} + ) else: failed(request, None, subject, "mail OTP QR code", "no email address") messages.error(request, _("User email not set, failed to send QR code")) diff --git a/locale/tr/LC_MESSAGES/django.mo b/locale/tr/LC_MESSAGES/django.mo index c0e036c5..0db7dc95 100644 Binary files a/locale/tr/LC_MESSAGES/django.mo and b/locale/tr/LC_MESSAGES/django.mo differ diff --git a/locale/tr/LC_MESSAGES/django.po b/locale/tr/LC_MESSAGES/django.po index a60d2057..cbdb8bb5 100644 --- a/locale/tr/LC_MESSAGES/django.po +++ b/locale/tr/LC_MESSAGES/django.po @@ -11,7 +11,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-11-05 06:39+0000\n" +"POT-Creation-Date: 2026-11-05 07:49+0000\n" "PO-Revision-Date: 2020-11-04 12:00+0000\n" "Last-Translator: catborise , 2020\n" "Language-Team: Turkish (https://www.transifex.com/catborise/teams/110663/tr/)\n" @@ -636,12 +636,17 @@ msgstr "OTP %(email)s ye gönderildi" msgid "Email OTP" msgstr "OTP E-Postası" -#: accounts/views.py:349 +#: accounts/views.py:350 +#, python-format +msgid "The OTP QR code could not be mailed to %(user)s: %(error)s" +msgstr "OTP QR kodu %(user)s kullanıcısına e-postayla gönderilemedi: %(error)s" + +#: accounts/views.py:354 #, python-format msgid "OTP QR code was emailed to user %(user)s" msgstr "OTP QR kodu %(user)s kullanıcısına gönderildi" -#: accounts/views.py:353 +#: accounts/views.py:358 msgid "User email not set, failed to send QR code" msgstr "Kullanıcı e-postası girilmemiş, QR kod gönderimi başarısız" diff --git a/locale/zh/LC_MESSAGES/django.mo b/locale/zh/LC_MESSAGES/django.mo index 06b5670b..6bf6d42b 100644 Binary files a/locale/zh/LC_MESSAGES/django.mo and b/locale/zh/LC_MESSAGES/django.mo differ diff --git a/locale/zh/LC_MESSAGES/django.po b/locale/zh/LC_MESSAGES/django.po index 9a7cf0a1..4ce41557 100644 --- a/locale/zh/LC_MESSAGES/django.po +++ b/locale/zh/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-11-05 06:39+0000\n" +"POT-Creation-Date: 2026-11-05 07:49+0000\n" "PO-Revision-Date: \n" "Last-Translator: \n" "Language-Team: \n" @@ -623,12 +623,17 @@ msgstr "OTP 已发送至 %(email)s" msgid "Email OTP" msgstr "邮件发送 OTP" -#: accounts/views.py:349 +#: accounts/views.py:350 +#, python-format +msgid "The OTP QR code could not be mailed to %(user)s: %(error)s" +msgstr "无法将 OTP 二维码通过邮件发送给 %(user)s:%(error)s" + +#: accounts/views.py:354 #, python-format msgid "OTP QR code was emailed to user %(user)s" msgstr "OTP 二维码已通过邮件发送给用户 %(user)s" -#: accounts/views.py:353 +#: accounts/views.py:358 msgid "User email not set, failed to send QR code" msgstr "未设置用户邮箱,无法发送二维码" diff --git a/webvirtcloud/settings.py.template b/webvirtcloud/settings.py.template index 1d81179e..788fbfb3 100644 --- a/webvirtcloud/settings.py.template +++ b/webvirtcloud/settings.py.template @@ -401,6 +401,8 @@ EMAIL_PORT = int(os.getenv("EMAIL_PORT", "587")) EMAIL_HOST_USER = os.getenv("EMAIL_HOST_USER", "") ## password associated with above email-id EMAIL_HOST_PASSWORD = os.getenv("EMAIL_HOST_PASSWORD", "") +# seconds an SMTP server may take to answer; without it a server that stops answering holds the request for good +EMAIL_TIMEOUT = 10 # LDAP Config #