progress on conversion to tailwind

This commit is contained in:
badblocks 2025-03-06 21:28:36 -08:00
parent 6a872124c6
commit 6e2843c60e
110 changed files with 4997 additions and 1691 deletions

View file

@ -1,15 +1,63 @@
from django import forms
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
from .models import CustomUser
from .models import CustomUser, FriendCode
from allauth.account.forms import SignupForm
class CustomUserCreationForm(UserCreationForm):
class Meta(UserCreationForm.Meta):
model = CustomUser
fields = ('email', 'username',)
fields = ('email',)
class CustomUserChangeForm(UserChangeForm):
class Meta:
model = CustomUser
fields = ('email', 'username',)
fields = ('email',)
class FriendCodeForm(forms.ModelForm):
class Meta:
model = FriendCode
fields = ["friend_code"]
def clean_friend_code(self):
friend_code = self.cleaned_data.get("friend_code", "").strip()
# Remove any dashes from the input for validation.
friend_code_clean = friend_code.replace("-", "")
if len(friend_code_clean) != 16 or not friend_code_clean.isdigit():
raise forms.ValidationError("Friend code must be exactly 16 digits long.")
# Format the friend code as: XXXX-XXXX-XXXX-XXXX.
friend_code_formatted = f"{friend_code_clean[:4]}-{friend_code_clean[4:8]}-{friend_code_clean[8:12]}-{friend_code_clean[12:16]}"
return friend_code_formatted
class CustomSignupForm(SignupForm):
friend_code = forms.CharField(
max_length=19,
required=True,
label="Friend Code",
help_text="Enter your friend code in the format XXXX-XXXX-XXXX-XXXX.",
widget=forms.TextInput(attrs={'placeholder': 'XXXX-XXXX-XXXX-XXXX'})
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Remove the username field completely.
if "username" in self.fields:
del self.fields["username"]
def clean_friend_code(self):
friend_code = self.cleaned_data.get("friend_code", "").strip().replace("-", "")
if len(friend_code) != 16 or not friend_code.isdigit():
raise forms.ValidationError("Friend code must be exactly 16 digits long.")
formatted = f"{friend_code[:4]}-{friend_code[4:8]}-{friend_code[8:12]}-{friend_code[12:16]}"
return formatted
def save(self, request):
# First, complete the normal signup process.
user = super().save(request)
# Create the associated FriendCode record.
FriendCode.objects.create(
friend_code=self.cleaned_data["friend_code"],
user=user
)
return user

View file

View file

View file

@ -0,0 +1,19 @@
from django.core.management.base import BaseCommand
from accounts.models import CustomUser, FriendCode
class Command(BaseCommand):
help = "Seed default friend codes for TestUsers after friend codes have been loaded."
def handle(self, *args, **options):
users_updated = 0
for user in CustomUser.objects.all():
# Automatically select the earliest friend code added for the user:
default_code = FriendCode.objects.filter(user=user).order_by('created_at').first()
if default_code:
user.default_friend_code = default_code
user.save(update_fields=["default_friend_code"])
self.stdout.write(f"Set default friend code for user {user.username} to {default_code.friend_code}.")
users_updated += 1
else:
self.stdout.write(f"No friend code found for user {user.username}.")
self.stdout.write(self.style.SUCCESS(f"Seeded default friend codes for {users_updated} user(s)."))

View file

@ -0,0 +1,61 @@
# Generated by Django 5.1.2 on 2025-03-07 01:04
import django.contrib.auth.models
import django.contrib.auth.validators
import django.db.models.deletion
import django.utils.timezone
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('auth', '0012_alter_user_first_name_max_length'),
]
operations = [
migrations.CreateModel(
name='CustomUser',
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')),
('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
('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')),
('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')),
],
options={
'verbose_name': 'user',
'verbose_name_plural': 'users',
'abstract': False,
},
managers=[
('objects', django.contrib.auth.models.UserManager()),
],
),
migrations.CreateModel(
name='FriendCode',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('friend_code', models.CharField(max_length=19)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='friend_codes', to=settings.AUTH_USER_MODEL)),
],
),
migrations.AddField(
model_name='customuser',
name='default_friend_code',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='accounts.friendcode'),
),
]

View file

@ -1,8 +1,47 @@
from django.contrib.auth.models import AbstractUser
from django.db import models
from django.core.exceptions import ValidationError
class CustomUser(AbstractUser):
pass
default_friend_code = models.ForeignKey("FriendCode", on_delete=models.SET_NULL, null=True, blank=True)
def __str__(self):
return self.email
return self.email
def set_default_friend_code(self, friend_code):
"""Set a friend code as default if it belongs to the user."""
if friend_code.user != self:
raise ValidationError("Friend code does not belong to this user.")
self.default_friend_code = friend_code
self.save(update_fields=["default_friend_code"])
def remove_default_friend_code(self, friend_code):
"""
If the given friend code is the current default,
assign another of the user's friend codes (if any) as default.
"""
if self.default_friend_code == friend_code:
other_codes = self.friend_codes.exclude(pk=friend_code.pk)
self.default_friend_code = other_codes.first() if other_codes.exists() else None
self.save(update_fields=["default_friend_code"])
class FriendCode(models.Model):
friend_code = models.CharField(max_length=19)
user = models.ForeignKey(CustomUser, on_delete=models.PROTECT, related_name='friend_codes')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def save(self, *args, **kwargs):
"""
When a new friend code is saved,
if the user has no default friend code yet,
automatically set this as the default.
"""
is_new = self.pk is None
super().save(*args, **kwargs)
if is_new and not self.user.default_friend_code:
self.user.default_friend_code = self
self.user.save(update_fields=["default_friend_code"])
def __str__(self):
return self.friend_code

View file

@ -26,6 +26,7 @@ def gravatar_url(email, size=20):
params = urlencode({'d': default, 's': str(size)})
return f"https://www.gravatar.com/avatar/{email_hash}?{params}"
@register.filter
def gravatar_profile_url(email=None):
"""
Returns the Gravatar Profile URL for a given email.

10
accounts/urls.py Normal file
View file

@ -0,0 +1,10 @@
from django.urls import path
from .views import ListFriendCodesView, AddFriendCodeView, DeleteFriendCodeView, ChangeDefaultFriendCodeView
urlpatterns = [
# ... other account URLs ...
path("friend-codes/", ListFriendCodesView.as_view(), name="list_friend_codes"),
path("friend-codes/add/", AddFriendCodeView.as_view(), name="add_friend_code"),
path("friend-codes/delete/<int:pk>/", DeleteFriendCodeView.as_view(), name="delete_friend_code"),
path("friend-codes/default/<int:pk>/", ChangeDefaultFriendCodeView.as_view(), name="change_default_friend_code"),
]

View file

@ -1,3 +1,113 @@
from django.shortcuts import render
from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
from django.urls import reverse_lazy
from django.shortcuts import redirect, get_object_or_404
from django.views.generic import ListView, CreateView, DeleteView, View
from accounts.models import FriendCode
from accounts.forms import FriendCodeForm
# Create your views here.
class ListFriendCodesView(LoginRequiredMixin, ListView):
"""
Display the current user's friend codes.
"""
model = FriendCode
template_name = "friend_codes/list_friend_codes.html"
context_object_name = "friend_codes"
def get_queryset(self):
return self.request.user.friend_codes.all()
class AddFriendCodeView(LoginRequiredMixin, CreateView):
"""
Add a new friend code for the current user. If the user does not yet have a default,
the newly added code will automatically become the default.
"""
model = FriendCode
form_class = FriendCodeForm
template_name = "friend_codes/add_friend_code.html"
success_url = reverse_lazy("list_friend_codes")
def form_valid(self, form):
form.instance.user = self.request.user
messages.success(self.request, "Friend code added successfully.")
return super().form_valid(form)
class DeleteFriendCodeView(LoginRequiredMixin, DeleteView):
"""
Remove an existing friend code.
Prevent deletion if the friend code is bound to any trade offers.
Also, prevent deletion if the friend code is either the only one or
is set as the default friend code.
"""
model = FriendCode
template_name = "friend_codes/confirm_delete_friend_code.html"
context_object_name = "friend_code"
success_url = reverse_lazy("list_friend_codes")
def get_queryset(self):
# Only allow deletion of friend codes owned by the current user.
return FriendCode.objects.filter(user=self.request.user)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
friend_code = self.get_object()
user = self.request.user
# Determine if the deletion should be disabled.
disable_delete = False
error_message = None
if user.friend_codes.count() == 1:
disable_delete = True
error_message = "Cannot delete your only friend code."
elif user.default_friend_code == friend_code:
disable_delete = True
error_message = (
"Cannot delete your default friend code. "
"Please set a different default first."
)
context["disable_delete"] = disable_delete
context["error_message"] = error_message
return context
def post(self, request, *args, **kwargs):
self.object = self.get_object()
user = self.object.user
# Check if the friend code is the only one; prevent deletion.
if user.friend_codes.count() == 1:
messages.error(request, "Cannot remove your only friend code.")
return redirect(self.success_url)
# Check if the friend code is set as default; prevent deletion.
if user.default_friend_code == self.object:
messages.error(
request,
"Cannot delete your default friend code. Please set a different default first."
)
return redirect(self.success_url)
# Also check if this friend code is referenced by any trade offer.
if self.object.initiated_by.exists() or self.object.accepted_by.exists():
messages.error(
request,
"Cannot remove this friend code because there are existing trade offers associated with it."
)
return redirect(self.success_url)
# Proceed to safe deletion.
self.object.delete()
messages.success(request, "Friend code removed successfully.")
return redirect(self.success_url + "?deleted=true")
class ChangeDefaultFriendCodeView(LoginRequiredMixin, View):
"""
Change the default friend code for the current user.
"""
def post(self, request, *args, **kwargs):
friend_code_id = kwargs.get("pk")
friend_code = get_object_or_404(FriendCode, pk=friend_code_id, user=request.user)
request.user.set_default_friend_code(friend_code)
messages.success(request, "Default friend code updated successfully.")
return redirect("list_friend_codes")