The MFA pages (manage, TOTP activate/deactivate, recovery codes, security keys,
reauthenticate) are built almost entirely from allauth's `element` primitives, so
they are styled by overriding the elements rather than by rewriting eight page
templates. New allauth pages then inherit the look for free.
- field + img elements were missing entirely, so allauth fell back to bare HTML:
the TOTP secret and recovery-code list rendered as unstyled inputs. The QR now
sits on a white plate -- it is dark modules on a transparent ground, so on the
dark theme it was dark-on-dark and phones could not scan it.
- button now honours the tags allauth sets. They were all flattened to
btn-primary, which made "Deactivate" look exactly as safe as "View".
- the `code` field renders as a daisyUI otp wherever it appears, so the
reauthenticate and activate pages get the same input as the login challenge.
The boxes step aside past six characters: allauth accepts a TOTP code (6) or a
recovery code (8) in that one field.
Fixes a crash: the security-key list does {% load humanize %}, which raised
TemplateSyntaxError because django.contrib.humanize was not installed. That page
500'd on every request; it is now installed and covered by a test.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
85 lines
4.3 KiB
HTML
85 lines
4.3 KiB
HTML
{% load lucide ui %}
|
|
|
|
{% comment %}
|
|
allauth passes `unlabeled=True` on the entrance forms (login, signup, reset) and
|
|
already sets a placeholder on each of their fields ("Email address", "Password"),
|
|
so dropping the visible label loses nothing on screen. The label is still emitted
|
|
sr-only inside the wrapper: a placeholder is not a label, and it disappears the
|
|
moment you start typing.
|
|
|
|
Fields with an icon use daisyUI's icon-in-field layout, where the `input` class
|
|
goes on the *wrapping label* and the input itself carries only `grow`. Putting
|
|
`input` on both draws a box inside a box.
|
|
{% endcomment %}
|
|
{% for field in attrs.form.hidden_fields %}{{ field }}{% endfor %}
|
|
{% for error in attrs.form.non_field_errors %}
|
|
<div class="alert alert-error my-2">
|
|
<span>{{ error }}</span>
|
|
</div>
|
|
{% endfor %}
|
|
{% comment %}
|
|
`exclude` lets a page lay a field out itself — the login page puts "remember" on the
|
|
button row. It is pinned to a real variable first, and that is not cosmetic: passing
|
|
`attrs.exclude` straight into a filter on a page that never set it raises
|
|
VariableDoesNotExist, which Django catches for the *main* variable of an expression
|
|
but not for a filter *argument*. {% if %} then swallows it and reads the condition as
|
|
false — silently dropping every field on every form that doesn't pass `exclude`.
|
|
{% endcomment %}
|
|
{% with exclude=attrs.exclude|default:"" %}
|
|
{% for field in attrs.form.visible_fields %}
|
|
{% if not field|excluded:exclude %}
|
|
<div class="form-control my-3 w-full">
|
|
{% if field.field.widget.input_type == "checkbox" %}
|
|
<label class="label cursor-pointer justify-start gap-3" for="{{ field.id_for_label }}">
|
|
{{ field|daisy }}
|
|
<span class="label-text">{{ field.label }}</span>
|
|
</label>
|
|
{% else %}
|
|
{% if not attrs.unlabeled %}
|
|
<label class="label" for="{{ field.id_for_label }}">
|
|
<span class="label-text">{{ field.label }}</span>
|
|
</label>
|
|
{% endif %}
|
|
{% with icon=field|field_icon %}
|
|
{% if field|is_otp %}
|
|
{% comment %}
|
|
daisyUI's otp is one input overlaid on the boxes, and it sizes itself from
|
|
its `span` children — so the sr-only label goes OUTSIDE the container, or it
|
|
would count as a seventh box and throw the width off.
|
|
{% endcomment %}
|
|
{% if attrs.unlabeled %}<label class="sr-only" for="{{ field.id_for_label }}">{{ field.label }}</label>{% endif %}
|
|
<div class="flex justify-center">
|
|
<div class="otp otp-lg" data-otp>
|
|
{{ field|daisy:"" }}
|
|
<span></span>
|
|
<span></span>
|
|
<span></span>
|
|
<span></span>
|
|
<span></span>
|
|
<span></span>
|
|
</div>
|
|
</div>
|
|
{% elif icon %}
|
|
<label class="input flex w-full items-center gap-2 {% if field.errors %}input-error{% endif %}" for="{{ field.id_for_label }}">
|
|
<span class="opacity-50">{% lucide icon size=16 %}</span>
|
|
{% if attrs.unlabeled %}<span class="sr-only">{{ field.label }}</span>{% endif %}
|
|
{{ field|daisy:"grow" }}
|
|
</label>
|
|
{% else %}
|
|
{{ field|daisy }}
|
|
{% endif %}
|
|
{% endwith %}
|
|
{% endif %}
|
|
{% comment %}
|
|
The password field's help_text is allauth's "Forgot your password?" link, so it
|
|
gets room to breathe. The id is not decorative: Django points the input's
|
|
aria-describedby at `<auto_id>_helptext`, and without it that reference dangles
|
|
and the link is never announced.
|
|
{% endcomment %}
|
|
{% if field.help_text %}<span id="{{ field.auto_id }}_helptext" class="label-text-alt mt-3 block text-base-content/70">{{ field.help_text }}</span>{% endif %}
|
|
{% for error in field.errors %}<span class="label-text-alt mt-1 text-error">{{ error }}</span>{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
{% endfor %}
|
|
{% endwith %}
|