Add initial migration for moderation app and resolve seed command issues

- Created an empty migration file for the moderation app to enable migrations.
- Documented the resolution of the seed command failure due to missing moderation tables.
- Identified and fixed a VARCHAR(10) constraint violation in the User model during seed data generation.
- Updated role assignment in the seed command to comply with the field length constraint.
This commit is contained in:
pacnpal
2025-09-25 08:39:05 -04:00
parent b1c369c1bb
commit 41b3c86437
13 changed files with 481 additions and 479 deletions

View File

@@ -186,7 +186,7 @@ class CustomUserAdmin(UserAdmin):
def save_model(self, request, obj, form, change):
creating = not obj.pk
super().save_model(request, obj, form, change)
if creating and obj.role != User.Roles.USER:
if creating and obj.role != "USER":
# Ensure new user with role gets added to appropriate group
group = Group.objects.filter(name=obj.role).first()
if group:

View File

@@ -15,17 +15,17 @@ class Command(BaseCommand):
create_default_groups()
# Sync existing users with groups based on their roles
users = User.objects.exclude(role=User.Roles.USER)
users = User.objects.exclude(role="USER")
for user in users:
group = Group.objects.filter(name=user.role).first()
if group:
user.groups.add(group)
# Update staff/superuser status based on role
if user.role == User.Roles.SUPERUSER:
if user.role == "SUPERUSER":
user.is_superuser = True
user.is_staff = True
elif user.role in [User.Roles.ADMIN, User.Roles.MODERATOR]:
elif user.role in ["ADMIN", "MODERATOR"]:
user.is_staff = True
user.save()

View File

@@ -121,10 +121,6 @@ class User(AbstractUser):
"""Get the user's display name, falling back to username if not set"""
if self.display_name:
return self.display_name
# Fallback to profile display_name for backward compatibility
profile = getattr(self, "profile", None)
if profile and profile.display_name:
return profile.display_name
return self.username
def save(self, *args, **kwargs):
@@ -635,4 +631,6 @@ class NotificationPreference(TrackedModel):
def create_notification_preference(sender, instance, created, **kwargs):
"""Create notification preferences when a new user is created."""
if created:
NotificationPreference.objects.create(user=instance)
NotificationPreference.objects.get_or_create(user=instance)
# Signal moved to signals.py to avoid duplication

View File

@@ -31,7 +31,7 @@ class UserDeletionService:
"is_active": False,
"is_staff": False,
"is_superuser": False,
"role": User.Roles.USER,
"role": "USER",
"is_banned": True,
"ban_reason": "System placeholder for deleted users",
"ban_date": timezone.now(),
@@ -178,7 +178,7 @@ class UserDeletionService:
return False, "Superuser accounts cannot be deleted for security reasons. Please contact system administrator or remove superuser privileges first."
# Check if user has critical admin role
if user.role == User.Roles.ADMIN and user.is_staff:
if user.role == "ADMIN" and user.is_staff:
return False, "Admin accounts with staff privileges cannot be deleted. Please remove admin privileges first or contact system administrator."
# Add any other business rules here

View File

@@ -10,59 +10,41 @@ from .models import User, UserProfile
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
"""Create UserProfile for new users"""
try:
if created:
# Create profile
profile = UserProfile.objects.create(user=instance)
# If user has a social account with avatar, download it
social_account = instance.socialaccount_set.first()
if social_account:
extra_data = social_account.extra_data
avatar_url = None
if social_account.provider == "google":
avatar_url = extra_data.get("picture")
elif social_account.provider == "discord":
avatar = extra_data.get("avatar")
discord_id = extra_data.get("id")
if avatar:
avatar_url = f"https://cdn.discordapp.com/avatars/{discord_id}/{avatar}.png"
if avatar_url:
try:
response = requests.get(avatar_url, timeout=60)
if response.status_code == 200:
img_temp = NamedTemporaryFile(delete=True)
img_temp.write(response.content)
img_temp.flush()
file_name = f"avatar_{instance.username}.png"
profile.avatar.save(file_name, File(img_temp), save=True)
except Exception as e:
print(
f"Error downloading avatar for user {instance.username}: {
str(e)
}"
)
except Exception as e:
print(f"Error creating profile for user {instance.username}: {str(e)}")
@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
"""Ensure UserProfile exists and is saved"""
try:
# Try to get existing profile first
"""Create UserProfile for new users - unified signal handler"""
if created:
try:
profile = instance.profile
profile.save()
except UserProfile.DoesNotExist:
# Profile doesn't exist, create it
UserProfile.objects.create(user=instance)
except Exception as e:
print(f"Error saving profile for user {instance.username}: {str(e)}")
# Use get_or_create to prevent duplicates
profile, profile_created = UserProfile.objects.get_or_create(user=instance)
if profile_created:
# If user has a social account with avatar, download it
try:
social_account = instance.socialaccount_set.first()
if social_account:
extra_data = social_account.extra_data
avatar_url = None
if social_account.provider == "google":
avatar_url = extra_data.get("picture")
elif social_account.provider == "discord":
avatar = extra_data.get("avatar")
discord_id = extra_data.get("id")
if avatar:
avatar_url = f"https://cdn.discordapp.com/avatars/{discord_id}/{avatar}.png"
if avatar_url:
response = requests.get(avatar_url, timeout=60)
if response.status_code == 200:
img_temp = NamedTemporaryFile(delete=True)
img_temp.write(response.content)
img_temp.flush()
file_name = f"avatar_{instance.username}.png"
profile.avatar.save(file_name, File(img_temp), save=True)
except Exception as e:
print(f"Error downloading avatar for user {instance.username}: {str(e)}")
except Exception as e:
print(f"Error creating profile for user {instance.username}: {str(e)}")
@receiver(pre_save, sender=User)
@@ -75,43 +57,43 @@ def sync_user_role_with_groups(sender, instance, **kwargs):
# Role has changed, update groups
with transaction.atomic():
# Remove from old role group if exists
if old_instance.role != User.Roles.USER:
if old_instance.role != "USER":
old_group = Group.objects.filter(name=old_instance.role).first()
if old_group:
instance.groups.remove(old_group)
# Add to new role group
if instance.role != User.Roles.USER:
if instance.role != "USER":
new_group, _ = Group.objects.get_or_create(name=instance.role)
instance.groups.add(new_group)
# Special handling for superuser role
if instance.role == User.Roles.SUPERUSER:
if instance.role == "SUPERUSER":
instance.is_superuser = True
instance.is_staff = True
elif old_instance.role == User.Roles.SUPERUSER:
elif old_instance.role == "SUPERUSER":
# If removing superuser role, remove superuser
# status
instance.is_superuser = False
if instance.role not in [
User.Roles.ADMIN,
User.Roles.MODERATOR,
"ADMIN",
"MODERATOR",
]:
instance.is_staff = False
# Handle staff status for admin and moderator roles
if instance.role in [
User.Roles.ADMIN,
User.Roles.MODERATOR,
"ADMIN",
"MODERATOR",
]:
instance.is_staff = True
elif old_instance.role in [
User.Roles.ADMIN,
User.Roles.MODERATOR,
"ADMIN",
"MODERATOR",
]:
# If removing admin/moderator role, remove staff
# status
if instance.role not in [User.Roles.SUPERUSER]:
if instance.role not in ["SUPERUSER"]:
instance.is_staff = False
except User.DoesNotExist:
pass
@@ -130,7 +112,7 @@ def create_default_groups():
from django.contrib.auth.models import Permission
# Create Moderator group
moderator_group, _ = Group.objects.get_or_create(name=User.Roles.MODERATOR)
moderator_group, _ = Group.objects.get_or_create(name="MODERATOR")
moderator_permissions = [
# Review moderation permissions
"change_review",
@@ -149,7 +131,7 @@ def create_default_groups():
]
# Create Admin group
admin_group, _ = Group.objects.get_or_create(name=User.Roles.ADMIN)
admin_group, _ = Group.objects.get_or_create(name="ADMIN")
admin_permissions = moderator_permissions + [
# User management permissions
"change_user",

View File

@@ -109,7 +109,7 @@ class SignalsTestCase(TestCase):
create_default_groups()
moderator_group = Group.objects.get(name=User.Roles.MODERATOR)
moderator_group = Group.objects.get(name="MODERATOR")
self.assertIsNotNone(moderator_group)
self.assertTrue(
moderator_group.permissions.filter(codename="change_review").exists()
@@ -118,7 +118,7 @@ class SignalsTestCase(TestCase):
moderator_group.permissions.filter(codename="change_user").exists()
)
admin_group = Group.objects.get(name=User.Roles.ADMIN)
admin_group = Group.objects.get(name="ADMIN")
self.assertIsNotNone(admin_group)
self.assertTrue(
admin_group.permissions.filter(codename="change_review").exists()

View File

@@ -42,7 +42,7 @@ class UserDeletionServiceTest(TestCase):
self.assertEqual(deleted_user.email, "deleted@thrillwiki.com")
self.assertFalse(deleted_user.is_active)
self.assertTrue(deleted_user.is_banned)
self.assertEqual(deleted_user.role, User.Roles.USER)
self.assertEqual(deleted_user.role, "USER")
# Check profile was created
self.assertTrue(hasattr(deleted_user, "profile"))