diff --git a/penparse/webui/auth.py b/penparse/webui/auth.py index b50574d..40161a7 100644 --- a/penparse/webui/auth.py +++ b/penparse/webui/auth.py @@ -1,5 +1,6 @@ from django.contrib.auth import get_user_model from django.contrib.auth.backends import ModelBackend +from django.contrib.auth.base_user import BaseUserManager class EmailBackend(ModelBackend): @@ -13,3 +14,30 @@ class EmailBackend(ModelBackend): if user.check_password(password): # type: ignore return user return None + + +class UserProfileManager(BaseUserManager): + """ Manager for user profiles """ + + def create_user(self, email, name, password=None): + """ Create a new user profile """ + if not email: + raise ValueError('User must have an email address') + + email = self.normalize_email(email) + user = self.model(email=email, name=name) + + user.set_password(password) + user.save(using=self._db) + + return user + + def create_superuser(self, email, name, password): + """ Create a new superuser profile """ + user = self.create_user(email, name, password) + user.is_superuser = True + user.is_staff = True + + user.save(using=self._db) + + return user diff --git a/penparse/webui/forms.py b/penparse/webui/forms.py new file mode 100644 index 0000000..1eed6e0 --- /dev/null +++ b/penparse/webui/forms.py @@ -0,0 +1,22 @@ +from django import forms +from .models import User +from django.contrib.auth.forms import UserCreationForm + + +class RegisterForm(UserCreationForm): + class Meta: + model = User + fields = ['first_name', 'last_name', 'email', 'password1', 'password2'] + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['first_name'].widget.attrs.update( + {'class': 'shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline'}) + self.fields['last_name'].widget.attrs.update( + {'class': 'shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline'}) + self.fields['email'].widget.attrs.update( + {'class': 'shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline'}) + self.fields['password1'].widget.attrs.update( + {'class': 'shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline'}) + self.fields['password2'].widget.attrs.update( + {'class': 'shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline'}) diff --git a/penparse/webui/migrations/0001_initial.py b/penparse/webui/migrations/0001_initial.py index b9f933c..79ad4da 100644 --- a/penparse/webui/migrations/0001_initial.py +++ b/penparse/webui/migrations/0001_initial.py @@ -1,9 +1,8 @@ -# Generated by Django 4.2.16 on 2024-11-23 13:03 +# Generated by Django 4.2.16 on 2024-11-30 06:31 -import django.contrib.auth.models -import django.contrib.auth.validators from django.db import migrations, models import django.utils.timezone +import webui.models class Migration(migrations.Migration): @@ -19,16 +18,15 @@ class Migration(migrations.Migration): 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')), ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), - ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), - ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), - ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), ('email', models.EmailField(max_length=254, unique=True)), - ('password', models.CharField(blank=True, max_length=256)), + ('first_name', models.CharField(max_length=150)), + ('last_name', models.CharField(max_length=150)), ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')), ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')), ], @@ -38,7 +36,7 @@ class Migration(migrations.Migration): 'abstract': False, }, managers=[ - ('objects', django.contrib.auth.models.UserManager()), + ('objects', webui.models.UserManager()), ], ), ] diff --git a/penparse/webui/models.py b/penparse/webui/models.py index a49cae3..de2610d 100644 --- a/penparse/webui/models.py +++ b/penparse/webui/models.py @@ -1,7 +1,53 @@ from django.contrib.auth.models import AbstractUser +from django.contrib.auth.base_user import BaseUserManager from django.db import models +class UserManager(BaseUserManager): + """Define a model manager for User model with no username field.""" + + use_in_migrations = True + + def _create_user(self, email, password, **extra_fields): + """Create and save a User with the given email and password.""" + if not email: + raise ValueError("The given email must be set") + email = self.normalize_email(email) + user = self.model(email=email, **extra_fields) + user.set_password(password) + user.save(using=self._db) + return user + + def create_user(self, email, password=None, **extra_fields): + """Create and save a regular User with the given email and password.""" + extra_fields.setdefault("is_staff", False) + extra_fields.setdefault("is_superuser", False) + return self._create_user(email, password, **extra_fields) + + def create_superuser(self, email, password, **extra_fields): + """Create and save a SuperUser with the given email and password.""" + extra_fields.setdefault("is_staff", True) + extra_fields.setdefault("is_superuser", True) + + if extra_fields.get("is_staff") is not True: + raise ValueError("Superuser must have is_staff=True.") + if extra_fields.get("is_superuser") is not True: + raise ValueError("Superuser must have is_superuser=True.") + + return self._create_user(email, password, **extra_fields) + + class User(AbstractUser): email = models.EmailField(unique=True) - password = models.CharField(max_length=256, blank=True) + username = None + first_name = models.CharField(max_length=150, blank=False) + last_name = models.CharField(max_length=150, blank=False) + + USERNAME_FIELD = 'email' + REQUIRED_FIELDS = ['full_name'] + + objects = UserManager() # type: ignore + + def __str__(self): + """ Return string representation of our user """ + return self.email diff --git a/penparse/webui/templates/register.html b/penparse/webui/templates/register.html index e6ded5e..2eb2f8b 100644 --- a/penparse/webui/templates/register.html +++ b/penparse/webui/templates/register.html @@ -5,70 +5,67 @@