Skip to main content

django-mvp-accounts

Sign-up, sign-in, account management and API access for django-mvp projects, built from the third-party packages that already do each job well.

It is not released yet. So far it puts django-allauth's sign-in, sign-up and sign-out pages inside django-mvp's application shell.

Scope & philosophy

A project built on django-mvp needs people to be able to create an account, sign in, change their details, and get back in when they forget how. Once the project has an API, those same people need a way to reach it: a token they can create, see and revoke themselves. This package is where that is wired into django-mvp, so each project does not do it again by hand.

It implements none of it. Accounts, sign-in and recovery come from an existing Django authentication package, and API tokens come from Django REST framework and a token package alongside it. The package is not tied to one authentication package, but django-allauth is the only one supported for now. What this package owns is the part in between: the pages those packages render through django-mvp's application shell, and the places they appear in its menus. What appears depends on what the project has installed. Nothing shows up for a package or a feature the project has not turned on.

It is about access to your own account. It does not decide what a signed-in person is allowed to do: roles, groups, object permissions and authorisation rules stay the host project's. Handling people's data rights, such as producing what a site holds about someone, belongs to django-mvp-compliance.

When two designs conflict, the one that leaves more of the work to the integrated package wins. A feature it already has is rendered here, never rebuilt. Adopting this package should take as little as possible, and each integrated package is supported at its latest release wherever that can be done.

This package supersedes django-accounts-center, which will be retired once everything it provides is available here.

Installation

Install the package and django-allauth, which renders the account pages this package restyles:

pip install django-mvp-accounts "django-allauth>=65.19.4,<66"

This package works with django-allauth 65.19.4 up to, but not including, 66. It requires django-mvp as well, since it renders inside django-mvp's layout and reads its colours from the theme django-mvp supplies.

Add it to INSTALLED_APPS ahead of both allauth and mvp:

INSTALLED_APPS = [
    # ...
    "mvp_accounts",
    "allauth",
    "allauth.account",
    "mvp",
]

The order matters. This package replaces templates that allauth and django-mvp each ship, and Django takes a template from the first installed app that has one by that name. Listed after either of them, this package's version is never reached and the pages look as they did before.

Then include allauth's URLs and django-mvp's, and set allauth up as its own quickstart describes:

from django.urls import include, path

urlpatterns = [
    path("accounts/", include("allauth.urls")),
    path("", include("mvp.urls")),
]

Nothing about that setup is checked or configured for you. This package adds no system check and sets no default, so allauth's middleware, authentication backend and settings are yours to choose.

What appears in the Account Center

With django-allauth installed, this package adds to django-mvp's Account Center:

  • Menu entries for Email, Password, Phone number, Connected accounts, Two-factor authentication and Sessions, listed under an "Account" heading below its Overview entry.
  • A card for each of those pages on the Account Center landing page, linking to it.

API tokens join the same entries and cards when the project has turned them on, with or without allauth (see API tokens).

A page allauth has not routed gets neither. With phone numbers turned off ("phone" left out of ACCOUNT_SIGNUP_FIELDS), there is no Phone number entry or card. Connected accounts appears only with the social account app (allauth.socialaccount) installed, Two-factor authentication only with the multi-factor app (allauth.mfa) installed, and Sessions only with the user sessions app (allauth.usersessions) installed. Without allauth installed the package adds none of those and raises nothing; the API tokens entry and card, which need nothing from allauth, are the only ones it can still add.

allauth's account management pages (email, change email, password change and set, phone change and verification, connected accounts, sessions, and re-authentication) render in the Account Center, inside the shell with its sidebar and messages. Pages that allauth builds on its entrance base render the same way for a signed-in person, so re-authentication and the phone verification that follows a change are management pages, while phone verification during sign-up stays an entrance page. The social sign-in pages, including the confirmation a signed-in person sees when connecting another account, always render as entrance pages.

The Account Center itself, the "Account Center" and "Log out" entries in the user menu, and the sign-out form are django-mvp's. Another installed app can add its own card the same way: ship a template named mvp/account/overview.html that extends mvp/account/overview.html and adds to {% block account.cards %} after {{ block.super }}.

API tokens

A person can see and manage their own API tokens in the Account Center when the project uses django-rest-knox, which keeps the tokens but ships no pages for them. This package adds the pages, an "API tokens" entry under the "Account" heading and an "API tokens" card on the landing page. Every signed-in person may use them unless the project says otherwise (see Who may hold tokens), and a visitor is sent to sign in.

Nothing is on until the project turns it on:

  1. Install the api extra, which brings in Django REST framework 3.16 or later and django-rest-knox 5:

    pip install "django-mvp-accounts[api]"
    
  2. Add both to INSTALLED_APPS and run migrate, which creates knox's token table:

    INSTALLED_APPS = [
        # ...
        "rest_framework",
        "knox",
    ]
    
  3. Include the pages' routes at an address of your choice, as the demo does:

    urlpatterns = [
        # ...
        path("account/tokens/", include("mvp_accounts.tokens.urls")),
        path("", include("mvp.urls")),
    ]
    
  4. Make your API accept the tokens, in your own settings, as knox documents:

    REST_FRAMEWORK = {
        "DEFAULT_AUTHENTICATION_CLASSES": ["knox.auth.TokenAuthentication"],
    }
    

The routes are served by TokensView, CreateTokenView and RevokeTokenView in mvp_accounts.tokens.views, which share TokenPageMixin. Include the routes as above rather than the views, so that the pages and their names stay together.

None of that is checked or configured for you: the package adds no system check and sets no default, so a missing route or setting shows as the entry and card not appearing, or as tokens your API does not accept. The package adds no model and no migration of its own.

The entry and the card are drawn only where the tokens routes resolve. A project that has not installed django-rest-knox or Django REST framework, or has not included the routes, gets neither, and the rest of the package works as before.

Creating a token

The create page asks for a lifetime: 7 days, 30 days, 90 days, 1 year or never, with 30 days selected. The lifetime is the token's expiry, and "never" stores none. The list is fixed in the package. The page's form is CreateTokenForm in mvp_accounts.tokens.forms: its lifetime field holds the choice, and get_expiry() turns a valid choice into a timedelta, or None for "never".

A new token is shown once. The page the person returns to after creating it holds the complete value, and no later page does: the package stores nothing but what django-rest-knox keeps, which is a digest and the token's first characters. A person who loses a token revokes it and creates another. The value travels from the create page to the tokens page in a signed cookie that lasts a minute, is sent only to the tokens pages and is deleted as the page shows it. Tokens carry no name and no last-used time.

Two of knox's settings matter here:

  • TOKEN_TTL is the lifetime of the tokens knox's own views create. The create page passes the chosen lifetime instead. With knox's AUTO_REFRESH on, each use of a token that has an expiry moves that expiry to TOKEN_TTL from then, capped by AUTO_REFRESH_MAX_TTL, whatever lifetime the person chose. A token that never expires is not touched.

  • TOKEN_LIMIT_PER_USER is the number of working tokens a person may hold, and the pages honour it: at the limit the create page creates nothing and returns to the tokens page with a message, and the tokens page stops offering to create one. A token that has expired does not count, and one with no expiry does. The count is taken just before a token is created, so two requests sent at the same moment can both pass it, as they can in knox's own sign-in view. knox sets no limit by default, so a project should set one:

    REST_KNOX = {
        "TOKEN_LIMIT_PER_USER": 5,
    }
    

A request carries the token in the Authorization header, as Authorization: Token <token>. The word before the token is knox's AUTH_HEADER_PREFIX, and the page shows the one your project uses. The demo answers at /api/whoami/ with the email of the person the token belongs to.

Revoking a token

Each row of the tokens page links to a page that shows the token and asks before revoking it. Confirming deletes that one token and returns to the tokens page; anything that still presents it is refused from that moment, and nothing brings it back. Cancelling changes nothing, and neither does opening the page. A token that is already gone, has expired or belongs to someone else gets the same reply: a message that it no longer exists.

Changing a password does not revoke a person's tokens, so a person who suspects a leak should revoke them here as well. knox's own sign-out-everywhere endpoint, LogoutAllView, deletes all of a person's tokens at once; the pages in this package revoke one at a time.

Who may hold tokens

Some sites give API access to a few people, such as staff. Set MVP_ACCOUNTS_API_TOKEN_ACCESS to the dotted path of a function that takes the signed-in person and says whether they may hold tokens:

# settings.py
MVP_ACCOUNTS_API_TOKEN_ACCESS = "myproject.access.staff_only"
# myproject/access.py
def staff_only(user):
    return user.is_staff

Left unset, or set to None, every signed-in person may. A visitor who is not signed in never may, and your function is not called for one. Whatever your function returns is read as true or false.

One answer decides everything. For a person it says no to, the tokens, create and revoke pages reply with 403 Forbidden, to a GET or a POST, so nothing is created or deleted, and the Account Center shows neither the "API tokens" entry nor the card. The demo sets the setting to demo.access.staff_only.

The setting is read in one place, may_use_tokens(user) in mvp_accounts.tokens.access. The pages call it, and so do the menu entry and the may_use_api_tokens template tag the card uses. A project that adds its own page for tokens can call it too.

The setting is not checked for you: a path that does not import raises ImportError the first time a page, the entry or the card asks.

It decides who sees the pages and nothing more. It does not revoke tokens a person already holds, and it is not consulted when a token is used, because this package checks no API request. A project that needs either deletes those tokens itself or checks in its own API.

Two-factor authentication

To offer two-factor authentication, install allauth's multi-factor app with its mfa extra (pip install "django-allauth[mfa]"), add allauth.mfa to INSTALLED_APPS, and run migrate. Which factors are on is your choice, through allauth's own settings: MFA_SUPPORTED_TYPES (authenticator app, recovery codes and security keys), MFA_PASSKEY_LOGIN_ENABLED, MFA_TRUST_ENABLED and the rest. This package sets none of them and checks none of them.

Once allauth.mfa is installed, its pages render in django-mvp's shell:

  • The two-factor overview, activating and deactivating the authenticator app, and viewing, downloading and generating recovery codes are management pages in the Account Center, with the Two-factor authentication entry and card described above.
  • A page with no factor it can offer leaves that factor out. With "totp" missing from MFA_SUPPORTED_TYPES, nothing on the overview offers the authenticator app.
  • With "webauthn" in MFA_SUPPORTED_TYPES, the security-key pages (the list, adding, renaming and removing a key, and re-authenticating with one) are management pages too. The list also needs django.contrib.humanize in INSTALLED_APPS, which allauth's page loads.
  • Signing in with a passkey (MFA_PASSKEY_LOGIN_ENABLED) adds a "Sign in with a passkey" button to the sign-in page. Creating an account with a passkey (MFA_PASSKEY_SIGNUP_ENABLED) adds its own sign-up page and a page to create the passkey. allauth refuses to start with it unless webauthn is in MFA_SUPPORTED_TYPES, ACCOUNT_EMAIL_VERIFICATION = "mandatory", ACCOUNT_EMAIL_VERIFICATION_BY_CODE_ENABLED = True and ACCOUNT_SIGNUP_FIELDS requires email*. All four are your project's settings, and the demo leaves passkey sign-up off. Sign-in and sign-up pages are entrance pages. Every page keeps the ids and data attributes allauth's JavaScript looks for.
  • The QR code is always drawn dark on white, in every theme, because a scanner cannot read it from a dark background.
  • Security keys and passkeys work only over HTTPS or on localhost. On any other address the browser refuses them, whatever this package renders.

Without allauth.mfa installed, the Account Center has no Two-factor authentication entry or card, and nothing else changes.

Signed-in sessions

allauth's sessions page lists the browsers and devices a person is signed in from, and it renders in the Account Center like the other management pages. This package adds a Sessions entry and card for it, and draws its table with django-mvp's table class inside a wrapper that scrolls sideways on a narrow screen. To turn the page on, install allauth's user sessions app, its middleware and django.contrib.humanize, which allauth's page loads its date filters from, as allauth documents:

INSTALLED_APPS = [
    # ...
    "allauth.usersessions",
    "django.contrib.humanize",
]

MIDDLEWARE = [
    # ...
    "allauth.account.middleware.AccountMiddleware",
    "allauth.usersessions.middleware.UserSessionsMiddleware",
]

Set USERSESSIONS_TRACK_ACTIVITY = True to add a "Last seen at" column. The package leaves the setting to the project.

Only sessions allauth has recorded are listed. A browser that was already signed in before the app was installed appears after its next sign-in, or after its next request when activity tracking is on. The page offers one action: signing out every session except the current one, without asking first, as allauth's does. Signing out one chosen session is not offered, because allauth does not offer it.

Without the user sessions app the Account Center has no Sessions entry or card, and nothing else changes.

Signing in with other accounts

To offer sign-in with GitHub, Google or another provider, install allauth.socialaccount and each provider's app, and configure them as allauth documents. Nothing about that is checked or configured for you.

The social sign-in pages render as django-mvp entrance pages, like the rest of allauth's sign-in pages. The sign-in and sign-up pages show one button for each provider allauth lists, with the provider's name as its text.

Each button's icon is named after allauth's provider id (github, google), so the project's django-easy-icons setup needs an icon under each id of a provider it configures:

EASY_ICONS = {
    "default": {
        # ...
        "icons": {
            "google": "bi bi-google",
        },
    },
}

What a missing icon does is django-easy-icons' decision. EASY_ICONS_FAIL_SILENTLY defaults to the value of DEBUG, so with DEBUG off a missing icon raises and breaks the sign-in and sign-up pages, and with it on (or the setting turned on) the button shows its name alone. This package ships no provider icons and checks for none. The OpenID buttons, one for each brand, all use the openid icon.

Quickstart

Public surface

Contributing

Standards for this repository live in CONSTITUTION.md, and the vocabulary to use in issues and commits lives in CONTEXT.md.

uv sync
uv run pytest
uv run pre-commit install

demo/ is a Django project on django-mvp's application shell, for looking at this package in a browser while working on it:

uv run python manage.py migrate
uv run python manage.py seed_demo
uv run python manage.py runserver

seed_demo creates accounts you can sign in with, all with the password password: regular.user@example.com, staff.user@example.com, super.user@example.com and mfa.user@example.com. The last one has an authenticator app and recovery codes, so signing in ends at the second-factor step. The demo sets MFA_TOTP_INSECURE_BYPASS_CODE to 123456, so that code passes the step without a phone. That is a convenience for this demo only: allauth refuses the setting when DEBUG is off, and a project of your own should not copy it.

License

MIT. See LICENSE.

Metadata

Release files for django-mvp-accounts 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for django-mvp-accounts 0.2.0
File Size Uploaded
django_mvp_accounts-0.2.0.tar.gz 27.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for django-mvp-accounts 0.2.0
File Interpreter ABI Platform
django_mvp_accounts-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 68.4 kB

Release files / django_mvp_accounts-0.2.0.tar.gz

Download URL django_mvp_accounts-0.2.0.tar.gz
Size 27.4 kB
Tags Source
SHA-256 checksum
How to use checksums
934c850ae2212b4e29cb24ac33f5ee12130070af5c348fe65bf381f89cd54658
BLAKE2b-256 checksum
How to use checksums
32cc060a500847774e6b9e07b6b1f6dabbc9772d6cac68e8a332761abba5fa57
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 8, 2026.

Transparency log

Release files / django_mvp_accounts-0.2.0-py3-none-any.whl

Download URL django_mvp_accounts-0.2.0-py3-none-any.whl
Size 41.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
9d7eee76e8ffb96f02860444f8ef954b7c80e35bfbccc60420913930945aa5bf
BLAKE2b-256 checksum
How to use checksums
15319c1ca199c82614087a586f7f6c52a73863a585d1ac61ffd025781285389c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 8, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page