mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 08:11:08 -05:00
257 lines
9.5 KiB
Python
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')
|