implement proper validation on reg form

This commit is contained in:
James Ravenscroft 2024-12-07 13:22:12 +00:00
parent 2b4d18a222
commit 3d12cd52d6
7 changed files with 196 additions and 87 deletions

View File

@ -1,5 +1,6 @@
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend from django.contrib.auth.backends import ModelBackend
from django.contrib.auth.base_user import BaseUserManager
class EmailBackend(ModelBackend): class EmailBackend(ModelBackend):
@ -13,3 +14,30 @@ class EmailBackend(ModelBackend):
if user.check_password(password): # type: ignore if user.check_password(password): # type: ignore
return user return user
return None 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

22
penparse/webui/forms.py Normal file
View File

@ -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'})

View File

@ -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 from django.db import migrations, models
import django.utils.timezone import django.utils.timezone
import webui.models
class Migration(migrations.Migration): class Migration(migrations.Migration):
@ -19,16 +18,15 @@ class Migration(migrations.Migration):
name='User', name='User',
fields=[ fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('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')), ('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')), ('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_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')), ('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')), ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
('email', models.EmailField(max_length=254, unique=True)), ('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')), ('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')), ('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, 'abstract': False,
}, },
managers=[ managers=[
('objects', django.contrib.auth.models.UserManager()), ('objects', webui.models.UserManager()),
], ],
), ),
] ]

View File

@ -1,7 +1,53 @@
from django.contrib.auth.models import AbstractUser from django.contrib.auth.models import AbstractUser
from django.contrib.auth.base_user import BaseUserManager
from django.db import models 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): class User(AbstractUser):
email = models.EmailField(unique=True) 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

View File

@ -5,70 +5,67 @@
<h2 class="text-2xl font-bold text-center text-gray-800 mb-8"> <h2 class="text-2xl font-bold text-center text-gray-800 mb-8">
Create Your AnnoMemo Account Create Your AnnoMemo Account
</h2> </h2>
{% if messages %} {% for message in messages %} <form method="POST" action="{% url 'register' %}">
<div
class="mb-4 p-4 {% if message.tags == 'error' %}bg-red-100 text-red-700{% else %}bg-green-100 text-green-700{% endif %} rounded"
>
{{ message }}
</div>
{% endfor %} {% endif %}
<form method="POST" action="/auth/register">
{% csrf_token %} {% csrf_token %}
<div class="mb-4"> <div class="mb-4 flex space-x-4">
<label <div class="flex-1">
for="fullName" <label
class="block text-gray-700 text-sm font-bold mb-2" for="{{ form.first_name.id_for_label }}"
>Full Name</label class="block text-gray-700 text-sm font-bold mb-2"
> >First Name</label
<input >
type="text" {{ form.first_name }} {% if form.first_name.errors %}
id="fullName" <p class="text-red-500 text-xs italic">
name="fullName" {{ form.first_name.errors.0 }}
class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" </p>
value="{{ form.fullName.value|default_if_none:'' }}" {% endif %}
required </div>
/> <div class="flex-1">
<label
for="{{ form.last_name.id_for_label }}"
class="block text-gray-700 text-sm font-bold mb-2"
>Last Name</label
>
{{ form.last_name }} {% if form.last_name.errors %}
<p class="text-red-500 text-xs italic">
{{ form.last_name.errors.0 }}
</p>
{% endif %}
</div>
</div> </div>
<div class="mb-4"> <div class="mb-4">
<label for="email" class="block text-gray-700 text-sm font-bold mb-2" <label
for="{{ form.email.id_for_label }}"
class="block text-gray-700 text-sm font-bold mb-2"
>Email Address</label >Email Address</label
> >
<input {{ form.email }} {% if form.email.errors %}
type="email" <p class="text-red-500 text-xs italic">{{ form.email.errors.0 }}</p>
id="email" {% endif %}
name="email"
class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
value="{{ form.email.value|default_if_none:'' }}"
required
/>
</div> </div>
<div class="mb-4"> <div class="mb-4">
<label <label
for="password" for="{{ form.password1.id_for_label }}"
class="block text-gray-700 text-sm font-bold mb-2" class="block text-gray-700 text-sm font-bold mb-2"
>Password</label >Password</label
> >
<input {{ form.password1 }} {% if form.password1.errors %}
type="password" <p class="text-red-500 text-xs italic">
id="password" {{ form.password1.errors.0 }}
name="password" </p>
class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" {% endif %}
required
/>
</div> </div>
<div class="mb-6"> <div class="mb-6">
<label <label
for="confirmPassword" for="{{ form.password2.id_for_label }}"
class="block text-gray-700 text-sm font-bold mb-2" class="block text-gray-700 text-sm font-bold mb-2"
>Confirm Password</label >Confirm Password</label
> >
<input {{ form.password2 }} {% if form.password2.errors %}
type="password" <p class="text-red-500 text-xs italic">
id="confirmPassword" {{ form.password2.errors.0 }}
name="confirmPassword" </p>
class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" {% endif %}
required
/>
</div> </div>
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<button <button

View File

@ -4,5 +4,5 @@ from . import views
urlpatterns = [ urlpatterns = [
path("", views.index, name="index"), path("", views.index, name="index"),
path("register", views.register, name="register"), path("auth/register", views.register, name="register"),
] ]

View File

@ -1,8 +1,18 @@
import logging
from django.contrib import messages from django.contrib import messages
from django.shortcuts import redirect, render from django.shortcuts import redirect, render
from django.http import HttpRequest, HttpResponse from django.http import HttpRequest, HttpResponse
from django import conf, forms
from .models import User from .models import User
from .forms import RegisterForm
logger = logging.getLogger(__name__)
def index(request): def index(request):
@ -13,36 +23,44 @@ def index(request):
def register(request: HttpRequest): def register(request: HttpRequest):
# if the form is not submitted yet, return the form # if the form is not submitted yet, return the form
if request.method != 'POST': if request.method == 'POST':
return render(request, 'register.html', {'errors': False}) form = RegisterForm(request.POST)
if form.is_valid():
email = request.POST.get('email') form.save()
password = request.POST.get('password') messages.success(request, 'Registration successful!')
confirm_password = request.POST.get('confirm_password')
errors = False
if not email:
messages.error(request, 'Email is required')
errors = True
if not password or len(password) < 8:
messages.error(request, 'Password must be at least 8 characters long')
errors = True
if password != confirm_password:
messages.error(request, 'Passwords do not match')
errors = True
if not errors:
if User.objects.filter(email=email).exists():
messages.error(request, 'Email already exists')
else:
user = User.objects.create_user(
username=username, email=email, password=password) # type: ignore
user.save()
messages.success(request, 'Account created successfully')
return redirect('login') return redirect('login')
else:
form = RegisterForm()
return render(request, 'register.html', {'form': form})
# email = request.POST.get('email')
# password = request.POST.get('password')
# confirm_password = request.POST.get('confirm_password')
# errors = False
# if not email:
# messages.error(request, 'Email is required')
# errors = True
# if not password or len(password) < 8:
# messages.error(request, 'Password must be at least 8 characters long')
# errors = True
# if password != confirm_password:
# messages.error(request, 'Passwords do not match')
# errors = True
# if not errors:
# if User.objects.filter(email=email).exists():
# messages.error(request, 'Email already exists')
# else:
# user = User.objects.create_user(
# username=username, email=email, password=password) # type: ignore
# user.save()
# messages.success(request, 'Account created successfully')
# return redirect('login')
return render(request, 'register.html') return render(request, 'register.html')