Skip to content

Commit

Permalink
Add custom User model
Browse files Browse the repository at this point in the history
- Put it in a separate app so it can be shared by the frontend and the
  admin interface, which will in turn be a separate app
- No username — have email as the primary identifier for users
- Most users won't need passwords and Django needs the column for the
  admin interface, so default to... a uuid4?
- Punt the has_perm and has_module_perms questions for now and have both
  return True for everything
  • Loading branch information
duncanjbrown committed Apr 12, 2024
1 parent 09ade5e commit 08b9d73
Show file tree
Hide file tree
Showing 7 changed files with 122 additions and 0 deletions.
Empty file.
6 changes: 6 additions & 0 deletions consultation_analyser/authentication/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class AuthConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "consultation_analyser.authentication"
47 changes: 47 additions & 0 deletions consultation_analyser/authentication/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Generated by Django 5.0.4 on 2024-04-11 17:09

from django.db import migrations, models


class Migration(migrations.Migration):

initial = True

dependencies = []

operations = [
migrations.CreateModel(
name="User",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("password", models.CharField(max_length=128, verbose_name="password")),
(
"last_login",
models.DateTimeField(
blank=True, null=True, verbose_name="last login"
),
),
(
"email",
models.EmailField(
max_length=255, unique=True, verbose_name="email address"
),
),
("created_at", models.DateTimeField(auto_now_add=True)),
("modified_at", models.DateTimeField(auto_now=True)),
("is_staff", models.BooleanField(default=True)),
("is_active", models.BooleanField(default=True)),
],
options={
"abstract": False,
},
),
]
Empty file.
46 changes: 46 additions & 0 deletions consultation_analyser/authentication/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import uuid
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager
from django.core.validators import validate_email
from django.db import models


class UserManager(BaseUserManager):
def create_user(self, email, password=None):
# If there's no pw, assign a long random one that won't be used
if password is None:
password = uuid.uuid4()

user = self.model(
email=self.normalize_email(email),
password=password,
)

user.full_clean()

user.save(using=self._db)
return user


class User(AbstractBaseUser):
email = models.EmailField(
verbose_name="email address",
max_length=255,
unique=True,
)

created_at = models.DateTimeField(editable=False, auto_now_add=True)
modified_at = models.DateTimeField(editable=False, auto_now=True)

objects = UserManager()

USERNAME_FIELD = "email"

# boilerplate required by django admin
is_staff = models.BooleanField(default=True)
is_active = models.BooleanField(default=True)

def has_perm(self, perm, obj=None):
return True

def has_module_perms(self, app_label):
return True
2 changes: 2 additions & 0 deletions consultation_analyser/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"django.contrib.messages",
"django.contrib.staticfiles",
"django.contrib.auth",
"consultation_analyser.authentication",
"consultation_analyser.consultations",
"compressor",
]
Expand Down Expand Up @@ -69,6 +70,7 @@

WSGI_APPLICATION = "consultation_analyser.wsgi.application"

AUTH_USER_MODEL = "authentication.User"

# Database
# https://docs.djangoproject.com/en/5.0/ref/settings/#databases
Expand Down
21 changes: 21 additions & 0 deletions tests/unit/test_authentication.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import pytest

from consultation_analyser.authentication.models import User
from django.core.exceptions import ValidationError

@pytest.mark.django_db
def test_create_valid_user():
user = User.objects.create_user(email="[email protected]")
assert user.id

@pytest.mark.django_db
def test_create_user_with_invalid_email():
with pytest.raises(ValidationError):
User.objects.create_user(email="sdfdsf")

@pytest.mark.django_db
def test_create_user_with_duplicate_email():
User.objects.create_user(email="[email protected]")

with pytest.raises(ValidationError):
User.objects.create_user(email="[email protected]")

0 comments on commit 08b9d73

Please sign in to comment.