feat: Implement initial schema and add various API, service, and management command enhancements across the application.

This commit is contained in:
pacnpal
2026-01-01 15:13:01 -05:00
parent c95f99ca10
commit b243b17af7
413 changed files with 11164 additions and 17433 deletions

View File

@@ -81,21 +81,15 @@ class AccountService:
"""
# Verify old password
if not user.check_password(old_password):
logger.warning(
f"Password change failed: incorrect current password for user {user.id}"
)
return {
'success': False,
'message': "Current password is incorrect",
'redirect_url': None
}
logger.warning(f"Password change failed: incorrect current password for user {user.id}")
return {"success": False, "message": "Current password is incorrect", "redirect_url": None}
# Validate new password
if not AccountService.validate_password(new_password):
return {
'success': False,
'message': "Password must be at least 8 characters and contain uppercase, lowercase, and numbers",
'redirect_url': None
"success": False,
"message": "Password must be at least 8 characters and contain uppercase, lowercase, and numbers",
"redirect_url": None,
}
# Update password
@@ -111,9 +105,9 @@ class AccountService:
logger.info(f"Password changed successfully for user {user.id}")
return {
'success': True,
'message': "Password changed successfully. Please check your email for confirmation.",
'redirect_url': None
"success": True,
"message": "Password changed successfully. Please check your email for confirmation.",
"redirect_url": None,
}
@staticmethod
@@ -125,9 +119,7 @@ class AccountService:
"site_name": site.name,
}
email_html = render_to_string(
"accounts/email/password_change_confirmation.html", context
)
email_html = render_to_string("accounts/email/password_change_confirmation.html", context)
try:
EmailService.send_email(
@@ -166,26 +158,17 @@ class AccountService:
}
"""
if not new_email:
return {
'success': False,
'message': "New email is required"
}
return {"success": False, "message": "New email is required"}
# Check if email is already in use
if User.objects.filter(email=new_email).exclude(id=user.id).exists():
return {
'success': False,
'message': "This email address is already in use"
}
return {"success": False, "message": "This email address is already in use"}
# Generate verification token
token = get_random_string(64)
# Create or update email verification record
EmailVerification.objects.update_or_create(
user=user,
defaults={"token": token}
)
EmailVerification.objects.update_or_create(user=user, defaults={"token": token})
# Store pending email
user.pending_email = new_email
@@ -196,18 +179,10 @@ class AccountService:
logger.info(f"Email change initiated for user {user.id} to {new_email}")
return {
'success': True,
'message': "Verification email sent to your new email address"
}
return {"success": True, "message": "Verification email sent to your new email address"}
@staticmethod
def _send_email_verification(
request: HttpRequest,
user: User,
new_email: str,
token: str
) -> None:
def _send_email_verification(request: HttpRequest, user: User, new_email: str, token: str) -> None:
"""Send email verification for email change."""
from django.urls import reverse
@@ -245,22 +220,14 @@ class AccountService:
Dictionary with success status and message
"""
try:
verification = EmailVerification.objects.select_related("user").get(
token=token
)
verification = EmailVerification.objects.select_related("user").get(token=token)
except EmailVerification.DoesNotExist:
return {
'success': False,
'message': "Invalid or expired verification token"
}
return {"success": False, "message": "Invalid or expired verification token"}
user = verification.user
if not user.pending_email:
return {
'success': False,
'message': "No pending email change found"
}
return {"success": False, "message": "No pending email change found"}
# Update email
old_email = user.email
@@ -273,10 +240,7 @@ class AccountService:
logger.info(f"Email changed for user {user.id} from {old_email} to {user.email}")
return {
'success': True,
'message': "Email address updated successfully"
}
return {"success": True, "message": "Email address updated successfully"}
class UserDeletionService:
@@ -337,39 +301,17 @@ class UserDeletionService:
# Count submissions before transfer
submission_counts = {
"park_reviews": getattr(
user, "park_reviews", user.__class__.objects.none()
).count(),
"ride_reviews": getattr(
user, "ride_reviews", user.__class__.objects.none()
).count(),
"uploaded_park_photos": getattr(
user, "uploaded_park_photos", user.__class__.objects.none()
).count(),
"uploaded_ride_photos": getattr(
user, "uploaded_ride_photos", user.__class__.objects.none()
).count(),
"top_lists": getattr(
user, "top_lists", user.__class__.objects.none()
).count(),
"edit_submissions": getattr(
user, "edit_submissions", user.__class__.objects.none()
).count(),
"photo_submissions": getattr(
user, "photo_submissions", user.__class__.objects.none()
).count(),
"moderated_park_reviews": getattr(
user, "moderated_park_reviews", user.__class__.objects.none()
).count(),
"moderated_ride_reviews": getattr(
user, "moderated_ride_reviews", user.__class__.objects.none()
).count(),
"handled_submissions": getattr(
user, "handled_submissions", user.__class__.objects.none()
).count(),
"handled_photos": getattr(
user, "handled_photos", user.__class__.objects.none()
).count(),
"park_reviews": getattr(user, "park_reviews", user.__class__.objects.none()).count(),
"ride_reviews": getattr(user, "ride_reviews", user.__class__.objects.none()).count(),
"uploaded_park_photos": getattr(user, "uploaded_park_photos", user.__class__.objects.none()).count(),
"uploaded_ride_photos": getattr(user, "uploaded_ride_photos", user.__class__.objects.none()).count(),
"top_lists": getattr(user, "top_lists", user.__class__.objects.none()).count(),
"edit_submissions": getattr(user, "edit_submissions", user.__class__.objects.none()).count(),
"photo_submissions": getattr(user, "photo_submissions", user.__class__.objects.none()).count(),
"moderated_park_reviews": getattr(user, "moderated_park_reviews", user.__class__.objects.none()).count(),
"moderated_ride_reviews": getattr(user, "moderated_ride_reviews", user.__class__.objects.none()).count(),
"handled_submissions": getattr(user, "handled_submissions", user.__class__.objects.none()).count(),
"handled_photos": getattr(user, "handled_photos", user.__class__.objects.none()).count(),
}
# Transfer all submissions to deleted user
@@ -440,11 +382,17 @@ class UserDeletionService:
return False, "Cannot delete the system deleted user placeholder"
if user.is_superuser:
return False, "Superuser accounts cannot be deleted for security reasons. Please contact system administrator or remove superuser privileges first."
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:
return False, "Admin accounts with staff privileges cannot be deleted. Please remove admin privileges first or contact system administrator."
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
@@ -492,9 +440,7 @@ class UserDeletionService:
site = Site.objects.get_current()
except Site.DoesNotExist:
# Fallback to default site
site = Site.objects.get_or_create(
id=1, defaults={"domain": "localhost:8000", "name": "localhost:8000"}
)[0]
site = Site.objects.get_or_create(id=1, defaults={"domain": "localhost:8000", "name": "localhost:8000"})[0]
# Prepare email context
context = {
@@ -502,9 +448,7 @@ class UserDeletionService:
"verification_code": deletion_request.verification_code,
"expires_at": deletion_request.expires_at,
"site_name": getattr(settings, "SITE_NAME", "ThrillWiki"),
"frontend_domain": getattr(
settings, "FRONTEND_DOMAIN", "http://localhost:3000"
),
"frontend_domain": getattr(settings, "FRONTEND_DOMAIN", "http://localhost:3000"),
}
# Render email content
@@ -564,11 +508,9 @@ The ThrillWiki Team
ValueError: If verification fails
"""
try:
deletion_request = UserDeletionRequest.objects.get(
verification_code=verification_code
)
deletion_request = UserDeletionRequest.objects.get(verification_code=verification_code)
except UserDeletionRequest.DoesNotExist:
raise ValueError("Invalid verification code")
raise ValueError("Invalid verification code") from None
# Check if request is still valid
if not deletion_request.is_valid():