Lay out the login card and wire up the passkey button
Sign In and "Sign in with a passkey" (btn-accent) now sit side by side with "Remember Me" on the same row, and the email/password block is given room above and below. The passkey button was dead. It submits a *different* form -- the hidden `mfa_login` that allauth renders from its `extra_body` block -- and our layout base never defined that block, so neither the form nor the webauthn script was ever emitted and clicking the button did nothing. _base.html now has the block, and there is a test asserting the form and script are on the page. The `fields` element grows an `exclude`, so a page can lay a field out itself (here: "remember", moved onto the button row). It splits on commas rather than testing for a substring -- "password" is a substring of "password2", and a page excluding one would otherwise silently drop the other. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -64,6 +64,17 @@ def field_icon(field):
|
||||
return FIELD_ICONS.get(field.name, "")
|
||||
|
||||
|
||||
@register.filter
|
||||
def excluded(field, names):
|
||||
"""Is this field in a comma-separated exclude list?
|
||||
|
||||
Split rather than a substring test: ``"password" in "password2"`` is true, and the
|
||||
login page excluding "remember" must not silently drop a field whose name happens
|
||||
to contain it.
|
||||
"""
|
||||
return field.name in (names or "").split(",")
|
||||
|
||||
|
||||
@register.filter
|
||||
def daisy(field, css=None):
|
||||
"""Render a bound form field with the right daisyUI classes.
|
||||
|
||||
@@ -19,7 +19,7 @@ from teams.models import Position, Team, TeamMembership
|
||||
from .services.admins import grant_club_admin
|
||||
from .services.platform_admins import PlatformAdminError, is_last_superuser, set_platform_access
|
||||
from .services.statistics import club_statistics, clubs_with_totals, platform_totals
|
||||
from .templatetags.ui import as_alert, daisy, field_icon
|
||||
from .templatetags.ui import as_alert, daisy, excluded, field_icon
|
||||
|
||||
User = get_user_model()
|
||||
Flag = get_waffle_flag_model()
|
||||
@@ -513,3 +513,43 @@ class LoginFormRenderingTests(TestCase):
|
||||
self.assertContains(self.response, 'id="id_password_helptext"')
|
||||
self.assertContains(self.response, "mt-3")
|
||||
self.assertContains(self.response, "Forgot your password?")
|
||||
|
||||
|
||||
class LoginLayoutTests(TestCase):
|
||||
def setUp(self):
|
||||
self.response = self.client.get(reverse("account_login"))
|
||||
|
||||
def test_remember_me_is_laid_out_by_the_page_not_the_fields_element(self):
|
||||
# It sits on the button row, so the fields element must not also render it.
|
||||
self.assertEqual(self.response.content.count(b'name="remember"'), 1)
|
||||
self.assertContains(self.response, '<span class="label-text">Remember Me</span>')
|
||||
|
||||
def test_the_passkey_button_sits_beside_sign_in(self):
|
||||
self.assertContains(self.response, "btn btn-accent")
|
||||
self.assertContains(self.response, "Sign in with a passkey")
|
||||
|
||||
def test_the_passkey_button_has_a_form_to_submit(self):
|
||||
# The button posts to the hidden `mfa_login` form via its `form` attribute. That
|
||||
# form comes from allauth's extra_body block — without it the button is dead.
|
||||
self.assertContains(self.response, 'form="mfa_login"')
|
||||
self.assertContains(self.response, 'id="mfa_login"')
|
||||
self.assertContains(self.response, "allauth.webauthn.forms.loginForm")
|
||||
|
||||
|
||||
class ExcludedFilterTests(TestCase):
|
||||
def field(self, name):
|
||||
class Form(forms.Form):
|
||||
pass
|
||||
|
||||
Form.base_fields[name] = forms.CharField()
|
||||
return Form()[name]
|
||||
|
||||
def test_a_field_is_excluded_by_exact_name(self):
|
||||
self.assertTrue(excluded(self.field("remember"), "remember"))
|
||||
|
||||
def test_a_name_that_merely_contains_another_is_not_excluded(self):
|
||||
# A substring test would drop "password" when excluding "password2".
|
||||
self.assertFalse(excluded(self.field("password"), "password2,remember"))
|
||||
|
||||
def test_nothing_is_excluded_without_a_list(self):
|
||||
self.assertFalse(excluded(self.field("remember"), None))
|
||||
|
||||
Reference in New Issue
Block a user