DJOK_USER_TYPE

This commit is contained in:
jpt 2025-04-19 23:18:48 -05:00
parent 45f846d679
commit 2331e605a9
6 changed files with 137 additions and 12 deletions

View File

@ -21,6 +21,9 @@ If you are using this library as a baseline, there are a few steps you'll need t
2. **Recommended:** run `uv run pre-commit install`
3. Read through the various sections below to familiarize yourself with the setup.
A few of the libraries may require additional setup, documented under the **You:** steps below.
4. Replace this README & the LICENSE file with those appropriate to your project.
(**Caution**: Since this repository is licensed CC-0, failure to do so would mean licensing your code in the same way, likely not what you want.)
5. Before starting, you will need to choose which kind of user account you want. See `DJOK_USER_TYPE` below.
## File System Layout
@ -121,3 +124,36 @@ Augment's django's built in `auth` with commonly-needed views for signup, email
- `/accounts/signup`
- `/accounts/login/code`
- `/accounts/password/reset`
## DJOK_USER_TYPE
Should be set to either:
- `username` - Standard username/password login w/ optional email.
- `email` - Standard email/password login, username is set to email.
Comes with allauth-powered token-based login as well.
This must be set **before** running initial DB migrations.
Once set, you can run:
```shell
just dj makemigrations accounts
just dj migrate
```
Changing once the application is live will require careful planning and custom data migration.
<!--
Adding New Types:
rm db.sqlite3
rm -rf apps/accounts/migrations/
just createsuperuser
# visit https://localhost:8000/djadmin/ & login
# visit https://localhost:8000/accounts/logout/
# visit https://localhost:8000/accounts/signup/
# visit https://localhost:8000/accounts/logout/
# visit https://localhost:8000/accounts/login/
-->

0
apps/__init__.py Normal file
View File

View File

10
apps/accounts/admin.py Normal file
View File

@ -0,0 +1,10 @@
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from .models import User
class UserAdmin(BaseUserAdmin):
list_display = ["is_active", "username", "full_name", "is_staff", "is_superuser"]
admin.site.register(User, UserAdmin)

79
apps/accounts/models.py Normal file
View File

@ -0,0 +1,79 @@
from django.db import models
from django.contrib.auth.models import (
AbstractBaseUser,
PermissionsMixin,
UnicodeUsernameValidator,
UserManager,
)
from django.utils.translation import gettext_lazy as _
from django.utils import timezone
from django.core.mail import send_mail
from django.conf import settings
USERNAME_REQUIRED = settings.DJOK_USER_TYPE == "username"
EMAIL_REQUIRED = settings.DJOK_USER_TYPE == "email"
if not (USERNAME_REQUIRED or EMAIL_REQUIRED):
raise ValueError("Must set DJOK_USER_TYPE")
class OkUserManager(UserManager):
def create_superuser(self, **kwargs):
if "username" not in kwargs:
kwargs["username"] = kwargs["email"]
super().create_superuser(**kwargs)
class User(AbstractBaseUser, PermissionsMixin):
"""
A modification of the built-in Django user that:
- switches first_name & last_name for username & full_name
- keeps other admin-compliant options
"""
username_validator = UnicodeUsernameValidator()
email = models.EmailField(_("email address"), unique=EMAIL_REQUIRED, default="")
username = models.CharField(
max_length=255,
unique=True,
validators=[username_validator] if USERNAME_REQUIRED else [],
default="",
)
full_name = models.CharField(_("full name"), max_length=150, blank=True)
is_staff = models.BooleanField(
_("staff status"),
default=False,
help_text=_("Designates whether the user can log into this admin site."),
)
is_active = models.BooleanField(
_("active"),
default=True,
help_text=_(
"Designates whether this user should be treated as active. "
"Unselect this instead of deleting accounts."
),
)
date_joined = models.DateTimeField(_("date joined"), default=timezone.now)
objects = OkUserManager()
EMAIL_FIELD = "email"
USERNAME_FIELD = "username" if USERNAME_REQUIRED else "email"
REQUIRED_FIELDS = []
class Meta:
verbose_name = _("user")
verbose_name_plural = _("users")
def clean(self):
super().clean()
self.email = self.__class__.objects.normalize_email(self.email)
def get_short_name(self):
return self.username
def get_full_name(self):
return self.full_name
def email_user(self, subject, message, from_email=None, **kwargs):
send_mail(subject, message, from_email, [self.email], **kwargs)

View File

@ -67,6 +67,7 @@ INSTALLED_APPS = [
"allauth.account",
"django_structlog",
"django_typer",
"apps.accounts",
]
MIDDLEWARE = [
@ -116,6 +117,8 @@ USE_TZ = True
# Authentication -----
AUTH_USER_MODEL = "accounts.User"
AUTHENTICATION_BACKENDS = [
"django.contrib.auth.backends.ModelBackend",
"allauth.account.auth_backends.AuthenticationBackend",
@ -136,21 +139,18 @@ AUTH_PASSWORD_VALIDATORS = [
},
]
DJOK_AUTH_MODE = "username"
# DJOK_AUTH_MODE is a setting we introduce to pick between
# DJOK_USER_TYPE is a setting we introduce to pick between
# a few common auth patterns.
#
# Things other than 'username' currently experimental.
# It is also used in accounts/models.py.
#
# 'username'
# A username-based email
#
# 'email'
# This configures django-allauth with reasonably secure defaults
# for an email-based account.
#
# ''
# WARNING: Changing this setting after initial setup can have
# data-loss consequences.
#
# See documentation for explanation of options.
DJOK_USER_TYPE = "email"
ACCOUNT_EMAIL_UNKNOWN_ACCOUNTS = False
ACCOUNT_PRESERVE_USERNAME_CASING = False
ACCOUNT_LOGIN_BY_CODE_ENABLED = True
@ -162,7 +162,7 @@ ACCOUNT_USERNAME_BLACKLIST = ["admin"]
# ACCOUNT_LOGIN_BY_CODE_REQUIRED = False
# ACCOUNT_DEFAULT_HTTP_PROTOCOL = "https"
if DJOK_AUTH_MODE == "email":
if DJOK_USER_TYPE == "email":
ACCOUNT_USER_MODEL_USERNAME_FIELD = None
ACCOUNT_LOGIN_METHODS = {"email"}
ACCOUNT_SIGNUP_FIELDS = ["email*", "password1*", "password2*"]