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