Files
thrillwiki_django_no_react/accounts/views.py

257 lines
9.5 KiB
Python

from django.views.generic import DetailView, TemplateView
from django.contrib.auth import get_user_model, login, authenticate
from django.shortcuts import get_object_or_404, redirect, render
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib import messages
from django.core.exceptions import ValidationError
from allauth.socialaccount.providers.google.views import GoogleOAuth2Adapter
from allauth.socialaccount.providers.discord.views import DiscordOAuth2Adapter
from allauth.socialaccount.providers.oauth2.client import OAuth2Client
from django.conf import settings
from django.core.mail import send_mail
from django.template.loader import render_to_string
from django.utils.crypto import get_random_string
from django.utils import timezone
from datetime import timedelta
from django.contrib.sites.shortcuts import get_current_site
from django.db.models import Prefetch
from django.http import HttpResponseRedirect
from django.urls import reverse
from accounts.models import User, PasswordReset
from reviews.models import Review
from email_service.services import EmailService
from allauth.account.views import LoginView, SignupView
from .mixins import TurnstileMixin
User = get_user_model()
class CustomLoginView(TurnstileMixin, LoginView):
def form_valid(self, form):
try:
self.validate_turnstile(self.request)
except ValidationError as e:
form.add_error(None, str(e))
return self.form_invalid(form)
return super().form_valid(form)
class CustomSignupView(TurnstileMixin, SignupView):
def form_valid(self, form):
try:
self.validate_turnstile(self.request)
except ValidationError as e:
form.add_error(None, str(e))
return self.form_invalid(form)
return super().form_valid(form)
@login_required
def user_redirect_view(request):
"""Redirect /user/ to the logged-in user's profile"""
return redirect('profile', username=request.user.username)
def email_required(request):
"""Handle cases where social auth provider doesn't provide an email"""
sociallogin = request.session.get('socialaccount_sociallogin')
if not sociallogin:
messages.error(request, 'No social login in progress')
return redirect('/')
if request.method == 'POST':
email = request.POST.get('email')
if email:
sociallogin.user.email = email
sociallogin.save()
login(request, sociallogin.user)
del request.session['socialaccount_sociallogin']
messages.success(request, 'Successfully logged in')
return redirect('/')
else:
messages.error(request, 'Email is required')
return render(request, 'accounts/email_required.html', {'error': 'Email is required'})
return render(request, 'accounts/email_required.html')
class ProfileView(DetailView):
model = User
template_name = 'accounts/profile.html'
context_object_name = 'profile_user'
slug_field = 'username'
slug_url_kwarg = 'username'
def get_queryset(self):
# Optimize the base queryset with select_related
return User.objects.select_related('profile')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
user = self.get_object()
# Get user's reviews with optimized queries
reviews_queryset = Review.objects.filter(
user=user,
is_published=True
).select_related(
'user',
'user__profile',
'content_type'
).prefetch_related(
'content_object' # This will fetch the related ride/park/etc.
).order_by('-created_at')[:5]
context['recent_reviews'] = reviews_queryset
# Get user's top lists with optimized queries
context['top_lists'] = user.top_lists.select_related(
'user',
'user__profile'
).prefetch_related(
Prefetch('items', queryset=(
user.top_lists.through.objects.select_related(
'content_type'
).prefetch_related('content_object')
))
).order_by('-created_at')[:5]
return context
class SettingsView(LoginRequiredMixin, TemplateView):
template_name = 'accounts/settings.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['user'] = self.request.user
return context
def post(self, request, *args, **kwargs):
action = request.POST.get('action')
if action == 'update_profile':
# Handle profile updates
user = request.user
user.first_name = request.POST.get('first_name', user.first_name)
user.last_name = request.POST.get('last_name', user.last_name)
if 'avatar' in request.FILES:
user.profile.avatar = request.FILES['avatar']
user.profile.save()
user.save()
messages.success(request, 'Profile updated successfully')
elif action == 'change_password':
# Handle password change
old_password = request.POST.get('old_password')
new_password = request.POST.get('new_password')
if request.user.check_password(old_password):
request.user.set_password(new_password)
request.user.save()
messages.success(request, 'Password changed successfully')
return HttpResponseRedirect(reverse('account_login'))
else:
messages.error(request, 'Current password is incorrect')
return self.get(request, *args, **kwargs)
def request_password_reset(request):
"""Request a password reset email"""
if request.method == 'POST':
email = request.POST.get('email')
if not email:
messages.error(request, 'Email is required')
return redirect('account_reset_password')
try:
user = User.objects.get(email=email)
# Generate token
token = get_random_string(64)
# Save token with expiry
PasswordReset.objects.update_or_create(
user=user,
defaults={
'token': token,
'expires_at': timezone.now() + timedelta(hours=24)
}
)
# Get current site
site = get_current_site(request)
# Send reset email
reset_url = reverse('password_reset_confirm', kwargs={'token': token})
context = {
'user': user,
'reset_url': reset_url,
'site_name': site.name,
}
email_html = render_to_string('accounts/email/password_reset.html', context)
# Use EmailService instead of send_mail
EmailService.send_email(
to=email,
subject='Reset your password',
text='Click the link to reset your password',
site=site,
html=email_html
)
messages.success(request, 'Password reset email sent')
return redirect('account_login')
except User.DoesNotExist:
# Still show success to prevent email enumeration
messages.success(request, 'Password reset email sent')
return redirect('account_login')
return render(request, 'accounts/password_reset.html')
def reset_password(request, token):
"""Reset password using token"""
try:
# Get valid reset token
reset = PasswordReset.objects.select_related('user').get(
token=token,
expires_at__gt=timezone.now(),
used=False
)
if request.method == 'POST':
new_password = request.POST.get('new_password')
if new_password:
# Reset password
user = reset.user
user.set_password(new_password)
user.save()
# Mark token as used
reset.used = True
reset.save()
# Get current site
site = get_current_site(request)
# Send confirmation email
context = {
'user': user,
'site_name': site.name,
}
email_html = render_to_string('accounts/email/password_reset_complete.html', context)
# Use EmailService instead of send_mail
EmailService.send_email(
to=user.email,
subject='Password Reset Complete',
text='Your password has been reset successfully.',
site=site,
html=email_html
)
messages.success(request, 'Password reset successfully')
return redirect('account_login')
else:
messages.error(request, 'New password is required')
return render(request, 'accounts/password_reset_confirm.html', {'token': token})
except PasswordReset.DoesNotExist:
messages.error(request, 'Invalid or expired reset token')
return redirect('account_reset_password')