From 6d793b1e16527445e8b5f7587f87fcf825c55666 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 16 Oct 2026 15:41:34 +0000 Subject: [PATCH 1/8] Login page: styled guest button, hidden when no guest account bg-secondary-600 is not compiled into styles.css, so the guest button had no background. Use Unfold's secondary button, and only render it when an active staff 'guest' user exists (otherwise clicking it did nothing). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01YXGgGiATURgjVcvhP18qJ5 --- whiteneuron/base/sites.py | 12 ++++++++++ whiteneuron/base/tests_regressions.py | 21 ++++++++++++++++++ whiteneuron/templates/admin/login_after.html | 23 +++++++++----------- 3 files changed, 43 insertions(+), 13 deletions(-) diff --git a/whiteneuron/base/sites.py b/whiteneuron/base/sites.py index 1423a64..d754b5f 100755 --- a/whiteneuron/base/sites.py +++ b/whiteneuron/base/sites.py @@ -16,6 +16,18 @@ def logout(self, request, extra_context=None): return redirect(reverse("%s:login" % self.name)) return super().logout(request, extra_context=extra_context) + def login(self, request, extra_context=None): + from .models import User + + # Only offer "Log in as guest" when GuestLoginView can actually log someone in + extra_context = { + "guest_login_available": User.objects.filter( + username="guest", is_active=True, is_staff=True + ).exists(), + **(extra_context or {}), + } + return super().login(request, extra_context=extra_context) + def get_urls(self): return [ path("about/", self.admin_view(self.about_view), name="about"), diff --git a/whiteneuron/base/tests_regressions.py b/whiteneuron/base/tests_regressions.py index c142582..5396be9 100644 --- a/whiteneuron/base/tests_regressions.py +++ b/whiteneuron/base/tests_regressions.py @@ -461,3 +461,24 @@ def test_missing_template_returns_empty_content(self): def test_unset_setting_returns_empty_content(self): response = self._get() # used to return None (500) self.assertEqual((response.status_code, response.content), (200, b"")) + + +@package_test_settings() +class GuestLoginButtonTests(TestCase): + def setUp(self): + # AutoGuestLoginMiddleware (if enabled) would log the guest in instead of showing the form + self.client.cookies["skip_auto_guest"] = "1" + + def test_hidden_without_guest_user(self): + # the button used to show even without a guest account; clicking it did nothing + page = self.client.get(reverse("admin:login")) + self.assertNotContains(page, reverse("guest_login")) + + def test_shown_as_styled_button_with_guest_user(self): + User.objects.create(username="guest", is_staff=True) + page = self.client.get(reverse("admin:login"), {"next": "/admin/"}) + self.assertContains(page, reverse("guest_login")) + self.assertContains(page, 'name="next" value="/admin/"') + # bg-secondary-600 is not compiled into any stylesheet: the button had no background + self.assertNotContains(page, "bg-secondary-600") + self.assertContains(page, "bg-base-100") diff --git a/whiteneuron/templates/admin/login_after.html b/whiteneuron/templates/admin/login_after.html index 0862969..eb0466d 100644 --- a/whiteneuron/templates/admin/login_after.html +++ b/whiteneuron/templates/admin/login_after.html @@ -1,14 +1,11 @@ -{% load i18n %} +{% load i18n unfold %} -
-
- {% csrf_token %} - {% if next %}{% endif %} - -
-
\ No newline at end of file +{% if guest_login_available %} +
+ {% csrf_token %} + {% if next %}{% endif %} + {% component "unfold/components/button.html" with submit=1 variant="secondary" class="w-full" %} + {% translate "Log in as guest" %} person + {% endcomponent %} +
+{% endif %} From 814642e359a13602b0ffcfd743de106d776d0be0 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 17 Oct 2026 03:17:42 +0000 Subject: [PATCH 2/8] Guest login: share lookup, hide button when it can't work, report failures - get_guest_user() in base/utils replaces three hand-copied queries (GuestLoginView, AutoGuestLoginMiddleware, login button) - {% guest_login_available %} tag decides the button in the template itself, so it works on any admin site rendering admin/login.html; hidden for authenticated users (GuestLoginView only redirects them) and on lookup errors (logged, never breaks the login page) - GuestLoginView shows an error and returns to the login page instead of silently bouncing; exceptions are logged rather than swallowed - icon is aria-hidden; tests cover inactive/non-staff guest, authenticated user, lookup error and the view itself Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01YXGgGiATURgjVcvhP18qJ5 --- whiteneuron/base/middleware.py | 3 +- whiteneuron/base/sites.py | 12 ---- whiteneuron/base/templatetags/wn_guest.py | 22 +++++++ whiteneuron/base/tests_regressions.py | 58 +++++++++++++++---- whiteneuron/base/utils.py | 5 ++ whiteneuron/base/views.py | 13 +++-- whiteneuron/locale/vi/LC_MESSAGES/django.mo | Bin 53690 -> 53843 bytes whiteneuron/locale/vi/LC_MESSAGES/django.po | 4 ++ whiteneuron/templates/admin/login_after.html | 9 +-- 9 files changed, 94 insertions(+), 32 deletions(-) create mode 100644 whiteneuron/base/templatetags/wn_guest.py diff --git a/whiteneuron/base/middleware.py b/whiteneuron/base/middleware.py index f975c2a..24a8a16 100755 --- a/whiteneuron/base/middleware.py +++ b/whiteneuron/base/middleware.py @@ -605,6 +605,7 @@ def _sanitize_post(self, data) -> dict: from .thread_local import thread_local from django.contrib.auth import login from .models import User +from .utils import get_guest_user class ThreadLocalMiddleware: def __init__(self, get_response): @@ -640,7 +641,7 @@ def __call__(self, request): ): try: # Kiểm tra xem guest user có tồn tại, đang active và có quyền staff không - guest_user = User.objects.filter(username='guest', is_active=True, is_staff=True).first() + guest_user = get_guest_user() if guest_user: # Tự động đăng nhập guest user login(request, guest_user, backend='django.contrib.auth.backends.ModelBackend') diff --git a/whiteneuron/base/sites.py b/whiteneuron/base/sites.py index d754b5f..1423a64 100755 --- a/whiteneuron/base/sites.py +++ b/whiteneuron/base/sites.py @@ -16,18 +16,6 @@ def logout(self, request, extra_context=None): return redirect(reverse("%s:login" % self.name)) return super().logout(request, extra_context=extra_context) - def login(self, request, extra_context=None): - from .models import User - - # Only offer "Log in as guest" when GuestLoginView can actually log someone in - extra_context = { - "guest_login_available": User.objects.filter( - username="guest", is_active=True, is_staff=True - ).exists(), - **(extra_context or {}), - } - return super().login(request, extra_context=extra_context) - def get_urls(self): return [ path("about/", self.admin_view(self.about_view), name="about"), diff --git a/whiteneuron/base/templatetags/wn_guest.py b/whiteneuron/base/templatetags/wn_guest.py new file mode 100644 index 0000000..e726978 --- /dev/null +++ b/whiteneuron/base/templatetags/wn_guest.py @@ -0,0 +1,22 @@ +import logging + +from django import template + +from ..utils import get_guest_user + +logger = logging.getLogger(__name__) +register = template.Library() + + +@register.simple_tag(takes_context=True) +def guest_login_available(context): + """Whether "Log in as guest" can work here: GuestLoginView redirects authenticated + users without logging them in, and needs an active staff ``guest`` account.""" + request = context.get("request") + if request is None or request.user.is_authenticated: + return False + try: + return get_guest_user() is not None + except Exception: # an optional button must never break the login page + logger.exception("Guest user lookup failed") + return False diff --git a/whiteneuron/base/tests_regressions.py b/whiteneuron/base/tests_regressions.py index 5396be9..df9fc12 100644 --- a/whiteneuron/base/tests_regressions.py +++ b/whiteneuron/base/tests_regressions.py @@ -465,20 +465,58 @@ def test_unset_setting_returns_empty_content(self): @package_test_settings() class GuestLoginButtonTests(TestCase): + FORM = 'id="guest-login-form"' + ACTION = 'action="/base/guest-login/"' + def setUp(self): # AutoGuestLoginMiddleware (if enabled) would log the guest in instead of showing the form self.client.cookies["skip_auto_guest"] = "1" - def test_hidden_without_guest_user(self): - # the button used to show even without a guest account; clicking it did nothing - page = self.client.get(reverse("admin:login")) - self.assertNotContains(page, reverse("guest_login")) + def _login_page(self, **params): + page = self.client.get(reverse("admin:login"), params) + self.assertEqual(page.status_code, 200) + return page + + def _guest_form(self, page): + html = page.content.decode() + start = html.index(self.FORM) + return html[start:html.index("", start)] def test_shown_as_styled_button_with_guest_user(self): User.objects.create(username="guest", is_staff=True) - page = self.client.get(reverse("admin:login"), {"next": "/admin/"}) - self.assertContains(page, reverse("guest_login")) - self.assertContains(page, 'name="next" value="/admin/"') - # bg-secondary-600 is not compiled into any stylesheet: the button had no background - self.assertNotContains(page, "bg-secondary-600") - self.assertContains(page, "bg-base-100") + form = self._guest_form(self._login_page(next="/admin/")) + self.assertIn('name="next" value="/admin/"', form) + # the raw