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 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 User = get_user_model() @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')