Django-allauth Template Customization

TLDR: My notes on how to customize various templates used by django-allauth.

django-allauth provides default templates for many scenarios such as authentication, registration, password reset, etc. These templates can be found at https://github.com/pennersr/django-allauth/tree/main/allauth/templates. You can see that there are quite a few of them. So in this notes, I just focus on login template used for username/password authentication. The approach to customize other templates will be similar.

As of this writing Jan 2024, the current implementation has the following structure:

In Django, DIRS is searched before APP_DIRS, so to override any of the allauth’s default templates, you just need to create your overrides in the same folder structure and adds the overrides to DIRS.

Example 1: to override login.html I would need to do the followings:

  • create a new login.html in BASE_DIR / 'templates' / 'account' / 'login.html'
  • adds BASE_DIR / ‘templates’ to settings.py like so
from pathlib import Path

BASE_DIR = Path(__file__).resolve().parent.parent

TEMPLATES = [
    {
        # ...
        "DIRS": [
            BASE_DIR / "templates"
        ],
        "APP_DIRS": True,
        # ...
    },
]

Why does the folder structure start with BASE_DIR/ 'templates' ? That’s because allauth’s current implementation looks up login.html in [root]/templates/account (reference the diagram above)

Example 2: to override button, I would need to do the followings:

  • create button.html in BASE_DIR/'templates'/'allauth'/'elements'/'button.html'
  • also make sure BASE_DIR/'templates is listed in settings.py > TEMPLATES > DIRS

Example 3: to override the entrance layout, I would need to do the followings

  • create entrance.html in BASE_DIR/'allauth'/'layout'/'entrance.html'
  • also make sure BASE_DIR/'templates is listed in settings.py > TEMPLATES > DIRS

Override plus Reusing built-in templates

You can mix and match your custom templates with built-in templates. By this I mean in your override, you can still reference built-in templates.

For example, if you decide to override login.html so that you can extend your own base template, you can still reference the built-in form element if you choose to do so.

Element

Initially when I was looking at allauth’s source, I was very confused by the use of element, slot, and attrs. For example:

  • This is how fields element is used
    {% element form form=form method="post" action=login_url tags="entrance,login" %}
        {% slot body %}
            {% csrf_token %}
            {% element fields form=form unlabeled=True %}
            {% endelement %}
            {% if redirect_field_value %}
                <input type="hidden"
                       name="{{ redirect_field_name }}"
                       value="{{ redirect_field_value }}" />
            {% endif %}
        {% endslot %}
        {% slot actions %}
            {% element button type="submit" tags="prominent,login" %}
                {% trans "Sign In" %}
            {% endelement %}
        {% endslot %}
    {% endelement %}
  • This is how it’s defined:
<form method="{{ attrs.method }}" action="{{ attrs.action }}">
    {% slot body %}
    {% endslot %}
    {% slot actions %}
    {% endslot %}
</form>

For anyone like me who’s more experienced with js frameworks such as React, the above snippets are equivalent to the following

// how to use form
<FormComponent
  form={form}
  method={"post"}
  action={login_url}
  tags={"entrance, login"}
>
  <BodyComponent />
  <ActionComponent />
</FormComponent>;

// how the FormComponent is defined

type Props = {
  form: DjangoForm,
  method: "post" | "get",
  action: string,
  tags: string,
};

const FormComponent = (attrs: Props) => {
  const method = attrs.method;
  const action = attrs.action;

  // ..
};

I guess the most confusing thing to me was the attrs naming. As a n00b, at a first glance I thought it references some context variable passed to the template. But no, it’s just “props” passed by the template that uses the element. In hindsight, it’s kind of obvious :)

Customize template per layout

allauth also has another trick up its sleeve for overriding elements such as form, h1 etc. per layout.

For each element in allauth/elements/ that is referenced in another template that extends a layout from allauth/layouts, you can create an override by creating a file named <element>__<layout>.html.

For example, you can override h1 element used in login.html, by creating h1__entrance.html.

The allauth’s code that implements this feature is in https://github.com/pennersr/django-allauth/blob/main/allauth/templatetags/allauth.py#L74. This looks for layout information in extends tags, or {%include%} with page_layout. There’s a line that also looks for layout information using layout_context key, but I was not able to find any places where such key has been used. The code comment mentions it’s supposed to be used in {%element%} tag.

Layout

Currently there are 2 built-in layouts:

  • entrance.html
  • manage.html

In the context of regular accounts, i.e. username/password:

  • Entrance layout is extended by authentication pages (signin, signout).
  • Manage layout is extended by password and email management pages that allow you to reset your password or update your email addresses.

Hopefully you find these notes useful as you work on customizing allauth’s UI!