From 13b935b34aaa0e685d8797e2fc6b6d20bd190526 Mon Sep 17 00:00:00 2001 From: romer8 Date: Sat, 15 Aug 2026 13:34:33 -0600 Subject: [PATCH 1/3] Render the captcha field on the login and register pages PR #1293 replaced whole-form rendering with an explicit list of fields in order to hide the labels. Enumerating the fields dropped the captcha, which is not one of them. With ENABLE_CAPTCHA enabled the field is still required by LoginForm and RegisterForm, so every submission fails validation on a field the page never rendered and the form simply re-renders. That locks all users out of a portal that has captcha turned on. Render the field when the form has one. get_captcha returns None when ENABLE_CAPTCHA is off, in which case the form has no such field and bootstrap_field would raise, so the tag is guarded. The gap was untested in both directions: the account view tests mock render and get_template, and the form tests never render a template. Adds tests that render both pages with captcha enabled and disabled. --- .../test_views/test_accounts_captcha.py | 69 +++++++++++++++++++ .../tethys_portal/accounts/login.html | 1 + .../tethys_portal/accounts/register.html | 1 + 3 files changed, 71 insertions(+) create mode 100644 tests/unit_tests/test_tethys_portal/test_views/test_accounts_captcha.py diff --git a/tests/unit_tests/test_tethys_portal/test_views/test_accounts_captcha.py b/tests/unit_tests/test_tethys_portal/test_views/test_accounts_captcha.py new file mode 100644 index 000000000..c01b85400 --- /dev/null +++ b/tests/unit_tests/test_tethys_portal/test_views/test_accounts_captcha.py @@ -0,0 +1,69 @@ +from importlib import reload +from unittest import mock + +from django.test import TestCase, override_settings +from django.urls import reverse + +import tethys_portal.forms as tp_forms + + +class TethysPortalAccountsCaptchaRenderingTest(TestCase): + def tearDown(self): + reload(tp_forms) + + def render_login(self): + with mock.patch("tethys_portal.views.accounts.LoginForm", tp_forms.LoginForm): + return self.client.get(reverse("accounts:login")) + + def render_register(self): + with mock.patch( + "tethys_portal.views.accounts.RegisterForm", tp_forms.RegisterForm + ): + return self.client.get(reverse("accounts:register")) + + @override_settings( + ENABLE_CAPTCHA=True, RECAPTCHA_PRIVATE_KEY="", RECAPTCHA_PUBLIC_KEY="" + ) + def test_login_page_renders_captcha_when_enabled(self): + reload(tp_forms) + self.assertIn("captcha", tp_forms.LoginForm().fields) + + response = self.render_login() + + self.assertEqual(200, response.status_code) + self.assertContains(response, 'name="captcha_1"') + + @override_settings(ENABLE_CAPTCHA=False) + def test_login_page_omits_captcha_when_disabled(self): + reload(tp_forms) + self.assertNotIn("captcha", tp_forms.LoginForm().fields) + + response = self.render_login() + + self.assertEqual(200, response.status_code) + self.assertNotContains(response, 'name="captcha_1"') + + @override_settings( + ENABLE_CAPTCHA=True, + ENABLE_OPEN_SIGNUP=True, + RECAPTCHA_PRIVATE_KEY="", + RECAPTCHA_PUBLIC_KEY="", + ) + def test_register_page_renders_captcha_when_enabled(self): + reload(tp_forms) + self.assertIn("captcha", tp_forms.RegisterForm().fields) + + response = self.render_register() + + self.assertEqual(200, response.status_code) + self.assertContains(response, 'name="captcha_1"') + + @override_settings(ENABLE_CAPTCHA=False, ENABLE_OPEN_SIGNUP=True) + def test_register_page_omits_captcha_when_disabled(self): + reload(tp_forms) + self.assertNotIn("captcha", tp_forms.RegisterForm().fields) + + response = self.render_register() + + self.assertEqual(200, response.status_code) + self.assertNotContains(response, 'name="captcha_1"') diff --git a/tethys_portal/templates/tethys_portal/accounts/login.html b/tethys_portal/templates/tethys_portal/accounts/login.html index aeea6be06..1508f34ec 100644 --- a/tethys_portal/templates/tethys_portal/accounts/login.html +++ b/tethys_portal/templates/tethys_portal/accounts/login.html @@ -24,6 +24,7 @@ {% csrf_token %} {% bootstrap_field form.username show_label=False %} {% bootstrap_field form.password show_label=False %} + {% if form.captcha %}{% bootstrap_field form.captcha show_label=False %}{% endif %} {% if signup_enabled %} Don't have an account? Sign Up diff --git a/tethys_portal/templates/tethys_portal/accounts/register.html b/tethys_portal/templates/tethys_portal/accounts/register.html index 917dd5bd4..167eae6c5 100644 --- a/tethys_portal/templates/tethys_portal/accounts/register.html +++ b/tethys_portal/templates/tethys_portal/accounts/register.html @@ -26,6 +26,7 @@ {% bootstrap_field form.email show_label=False %} {% bootstrap_field form.password1 show_label=False %} {% bootstrap_field form.password2 show_label=False %} + {% if form.captcha %}{% bootstrap_field form.captcha show_label=False %}{% endif %} Already have an account? Login From 1f28027d9b518bc872c8d2181c9917e26b3d512d Mon Sep 17 00:00:00 2001 From: romer8 Date: Sat, 15 Aug 2026 13:35:31 -0600 Subject: [PATCH 2/3] Note the captcha rendering fix in whats_new --- docs/whats_new.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/whats_new.rst b/docs/whats_new.rst index 0eda0317d..41ff291b0 100644 --- a/docs/whats_new.rst +++ b/docs/whats_new.rst @@ -42,6 +42,7 @@ See: :doc:`tethys_sdk/gizmos/time_picker` Bug Fixes --------- +* Captcha field rendering fix for the login and register pages: `PR 1297 `_ * Login and register form fixes: `PR 1293 `_ * Static file discovery fix for ``STATICFILES_USE_NPM``: `PR 1291 `_ * Django 5 app initialization warning fix: `PR 1288 `_ From 697ee0e908fed7809f0dd8074b8c0e00e8397ff5 Mon Sep 17 00:00:00 2001 From: romer8 Date: Mon, 17 Aug 2026 14:57:28 -0600 Subject: [PATCH 3/3] Fix captcha rendering settings in login and register tests --- .../test_views/test_accounts_captcha.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/tests/unit_tests/test_tethys_portal/test_views/test_accounts_captcha.py b/tests/unit_tests/test_tethys_portal/test_views/test_accounts_captcha.py index c01b85400..584e77fee 100644 --- a/tests/unit_tests/test_tethys_portal/test_views/test_accounts_captcha.py +++ b/tests/unit_tests/test_tethys_portal/test_views/test_accounts_captcha.py @@ -22,7 +22,10 @@ def render_register(self): return self.client.get(reverse("accounts:register")) @override_settings( - ENABLE_CAPTCHA=True, RECAPTCHA_PRIVATE_KEY="", RECAPTCHA_PUBLIC_KEY="" + ENABLE_CAPTCHA=True, + RECAPTCHA_PRIVATE_KEY="", + RECAPTCHA_PUBLIC_KEY="", + SHOW_PUBLIC_IF_NO_TENANT_FOUND=True, ) def test_login_page_renders_captcha_when_enabled(self): reload(tp_forms) @@ -33,7 +36,7 @@ def test_login_page_renders_captcha_when_enabled(self): self.assertEqual(200, response.status_code) self.assertContains(response, 'name="captcha_1"') - @override_settings(ENABLE_CAPTCHA=False) + @override_settings(ENABLE_CAPTCHA=False, SHOW_PUBLIC_IF_NO_TENANT_FOUND=True) def test_login_page_omits_captcha_when_disabled(self): reload(tp_forms) self.assertNotIn("captcha", tp_forms.LoginForm().fields) @@ -48,6 +51,7 @@ def test_login_page_omits_captcha_when_disabled(self): ENABLE_OPEN_SIGNUP=True, RECAPTCHA_PRIVATE_KEY="", RECAPTCHA_PUBLIC_KEY="", + SHOW_PUBLIC_IF_NO_TENANT_FOUND=True, ) def test_register_page_renders_captcha_when_enabled(self): reload(tp_forms) @@ -58,7 +62,11 @@ def test_register_page_renders_captcha_when_enabled(self): self.assertEqual(200, response.status_code) self.assertContains(response, 'name="captcha_1"') - @override_settings(ENABLE_CAPTCHA=False, ENABLE_OPEN_SIGNUP=True) + @override_settings( + ENABLE_CAPTCHA=False, + ENABLE_OPEN_SIGNUP=True, + SHOW_PUBLIC_IF_NO_TENANT_FOUND=True, + ) def test_register_page_omits_captcha_when_disabled(self): reload(tp_forms) self.assertNotIn("captcha", tp_forms.RegisterForm().fields)