Files
B42/at_django_boilerplate/accounts/views.py
2026-01-07 12:09:20 +05:30

416 lines
15 KiB
Python
Executable File

from django.urls import reverse_lazy
from django.views import View
from django.views.generic import FormView
from django.views.generic.edit import UpdateView
from django.views.generic.detail import DetailView
from django.views.generic.list import ListView
from django.contrib.auth import login,authenticate,logout
from django.contrib import messages
from django.shortcuts import redirect, render
from django.db import IntegrityError
import hashlib
from django.contrib.auth.tokens import default_token_generator
from django.contrib.auth.decorators import login_required
from .forms import (UserSignUpForm,
SigninForm,
CustomPasswordResetForm,
ProfilePictureUpdateForm,
UpdateProfileForm)
from django.contrib.auth.views import (PasswordResetView,
PasswordChangeView)
from django.http import JsonResponse
from django.contrib.messages.views import SuccessMessageMixin
from django.contrib.auth.mixins import UserPassesTestMixin
from .models import CustomUser
from django.utils.encoding import force_str
from django.utils.http import urlsafe_base64_decode
from .tokens import account_activation_token
from django.contrib import messages
from django.contrib.auth import authenticate, login
from django.utils.http import url_has_allowed_host_and_scheme
from .utils import (send_activation_email,
redirect_to_next_or_home,
)
from at_django_boilerplate.utils.encryption_utils import EncryptionUtils
from django.shortcuts import render, redirect
from .forms import UserSignUpForm
import logging
logger = logging.getLogger(__name__)
class CheckUsernameAvailability(View):
def get(self, request):
username = request.GET.get('username', None)
is_taken = CustomUser.objects.filter_by_email(username).exists()
data = {
'is_taken': is_taken
}
return JsonResponse(data)
class SignupView(FormView):
template_name = 'registration/signup.html'
form_class = UserSignUpForm
success_url = reverse_lazy('home')
def post(self, request, *args, **kwargs):
next_url = request.GET.get('next')
user_form = self.form_class(request.POST)
if user_form.is_valid():
email = user_form.cleaned_data['email'].lower()
contact_number = user_form.cleaned_data.get('contact_number')
email_exists = CustomUser.objects.filter_by_email(email).exists()
number_exists = False
if contact_number:
number_exists = CustomUser.objects.filter_by_contact_number(contact_number).exists()
if email_exists:
user_form.add_error('email', 'Email is already in use.')
elif number_exists:
user_form.add_error('contact_number', 'Contact number is already in use.')
else:
try:
user = user_form.save(commit=False)
user.set_password(user.password)
user.save()
login(request, user)
user.is_active = True
user.save()
send_activation_email(
request=request,
user=user,
to_email=user.get_decrypted_email()
)
# return redirect(next_url or 'verify-email-done')
return redirect('dashboard')
except IntegrityError:
# Rollback if needed
user.delete()
return self.render_to_response(self.get_context_data(form=user_form))
def verify_email(request):
return render(request, 'registration/verify_email.html')
def verify_email_done(request):
return render(request, 'registration/verify_email_done.html')
def verify_email_confirm(request, uidb64, token):
try:
uid = force_str(urlsafe_base64_decode(uidb64))
user = CustomUser.objects.get(pk=uid)
except(TypeError, ValueError, OverflowError, CustomUser.DoesNotExist):
user = None
if user is not None and account_activation_token.check_token(user, token):
user.is_active = True
user.save()
messages.success(request, 'Your email has been verified.')
return redirect('verify-email-complete')
else:
messages.warning(request, 'The link is invalid.')
return render(request, 'registration/verify_email_confirm.html')
def verify_email_complete(request):
return render(request, 'registration/verify_email_complete.html')
class LoginPageView(View):
template_name = 'login.html'
form_class = SigninForm
def get(self, request):
if request.user.is_authenticated:
next_url = request.GET.get('next')
if next_url and url_has_allowed_host_and_scheme(next_url, allowed_hosts={request.get_host()}):
return redirect(next_url)
return redirect('home')
form = self.form_class()
return render(request, self.template_name, {'form': form, 'message': ''})
def post(self, request):
print('Login')
form = self.form_class(request.POST)
message = 'Login failed!'
if form.is_valid():
print('Form Valid')
username = form.cleaned_data['username'].lower()
password = form.cleaned_data['password']
user = authenticate(request, username=username, password=password)
# user.backend = 'accounts.auth_backends.CustomAuthBackend' # <--- key line added
print(f"Authenticated User: {user}")
if user:
if user.is_active:
login(request, user)
return redirect_to_next_or_home(request)
else:
messages.error(request, 'Please verify your account before logging in.')
else:
messages.error(request, 'Invalid username or password.')
else:
messages.error(request, 'Invalid input.')
return render(request, self.template_name, {
'form': form,
'message': message,
})
class LogoutView(View):
def get(self, request):
logout(request)
return redirect('home')
class ResetPasswordView(SuccessMessageMixin, PasswordResetView):
template_name = 'reset/password_reset.html'
email_template_name = 'reset/password_reset_email.html'
html_email_template_name = 'reset/password_reset_email_html.html'
subject_template_name = 'reset/password_reset_subject.txt'
success_message = "We've emailed you instructions for setting your password, " \
"if an account exists with the email you entered. You should receive them shortly." \
" If you don't receive an email, " \
"please make sure you've entered the address you registered with, and check your spam folder."
success_url = reverse_lazy('password_reset_done')
token_generator = default_token_generator
form_class = CustomPasswordResetForm
class ResetPasswordDoneView(View):
template_name = 'reset/password_reset_done.html'
success_message = "We've emailed you instructions for setting your password, " \
"if an account exists with the email you entered. You should receive them shortly." \
" If you don't receive an email, " \
"please make sure you've entered the address you registered with, and check your spam folder."
def get(self, request):
return render(request=request,template_name=self.template_name)
class ChangePasswordView(SuccessMessageMixin, PasswordChangeView):
template_name = 'change_password.html'
success_message = "Successfully Changed Your Password"
success_url = reverse_lazy('home')
class CustomUserListView(ListView):
model = CustomUser
template_name = 'user_list.html'
users = 'CustomUsers'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
users = CustomUser.objects.all()
context['users'] = [{
'username': custom_user.username,
'name': custom_user.get_decrypted_name(),
'email': custom_user.get_decrypted_email(),
'birthday': custom_user.birthday,
'address': custom_user.get_decrypted_address(),
'contact_number': custom_user.get_decrypted_contact_number()
} for custom_user in users]
return context
class ProfileView(DetailView):
model = CustomUser
template_name = 'profile/profile.html'
context_object_name = 'custom_user'
def get_object(self):
return self.request.user
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# Initialize the form with the current user's data
form = ProfilePictureUpdateForm(instance=self.get_object())
context['update_profile_photo_form'] = form
return context
class UpdateProfileView(UserPassesTestMixin, UpdateView):
model = CustomUser
form_class = UpdateProfileForm
template_name = 'profile/update_profile.html'
def test_func(self):
obj = self.get_object()
requested_by_user = self.request.user
return (obj == requested_by_user)
def get(self, request, *args, **kwargs):
self.object = self.get_object()
# Decrypt the data before passing it to the form
encryption_tool = EncryptionUtils()
contact_number = None
if self.object.contact_number:
contact_number = encryption_tool.decrypt(self.object.contact_number)
initial_data = {
'contact_number': contact_number
}
form = self.form_class(instance=self.object, initial=initial_data)
return self.render_to_response(self.get_context_data(form=form))
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form = self.form_class(request.POST, instance=self.object)
if form.is_valid():
obj = form.save(commit=False)
encryption_tool = EncryptionUtils()
# address = form.cleaned_data['address']
# obj.address = encryption_tool.encrypt(address)
contact_number = form.cleaned_data['contact_number']
if all([contact_number!=None,contact_number!='']):
obj.contact_number = encryption_tool.encrypt(contact_number)
obj.save()
return self.form_valid(form)
else:
# Decrypt the data before re-rendering the form with errors
encryption_tool = EncryptionUtils()
contact_number = None
if self.object.contact_number:
contact_number = encryption_tool.decrypt(self.object.contact_number)
initial_data = {
'contact_number': contact_number
}
form = self.form_class(instance=self.object, initial=initial_data)
return self.form_invalid(form)
@login_required
def update_profile_picture(request):
if request.user.pk !=None:
user_profile = request.user
if request.method == 'POST':
form = ProfilePictureUpdateForm(request.POST, request.FILES, instance=user_profile)
if form.is_valid():
form.save()
messages.success(request, 'Your profile picture has been updated successfully!')
return redirect('user_profile')
else:
form = ProfilePictureUpdateForm(instance=user_profile)
return render(request, 'profile/update_profile_picture.html', {'form': form})
from django.core.validators import EmailValidator, ValidationError
from django.core.cache import cache
from .utils import send_otp, verify_otp
from .models import CustomUser
import re
PHONE_REGEX = re.compile(r'^\+?\d{10,15}$')
def is_email(identifier):
validator = EmailValidator()
try:
validator(identifier)
return True
except ValidationError:
return False
def request_otp_view(request):
if request.method == 'POST':
identifier = request.POST.get('identifier', '').strip().lower()
if not identifier:
messages.error(request, "Please enter your email or mobile number.")
return redirect('login') # Assuming OTP modal is on the login page
print(f"Identifier: {identifier}")
user = None
if is_email(identifier):
print(f"Email: {identifier}")
user = CustomUser.objects.filter_by_email(email=identifier).first()
else:
if not PHONE_REGEX.match(identifier):
messages.error(request, "Invalid mobile number format.")
return redirect('login')
print(f"Mobile hash: {identifier}")
user = CustomUser.objects.filter_by_contact_number(contact_number=identifier).first()
print(f"User: {user}")
if not user:
messages.error(request, "No user found with this email or mobile number.")
return redirect('login')
# Rate limiting
cache_key = f"otp_rate_limit_{identifier}"
if cache.get(cache_key):
messages.error(request, "Please wait before requesting another OTP.")
return redirect('login')
# Send OTP
success, _ = send_otp(identifier, purpose='login')
if success:
cache.set(cache_key, True, timeout=60) # 60 seconds
request.session['otp_identifier'] = identifier
messages.success(request, "OTP sent successfully.")
return redirect('verify_otp')
else:
messages.error(request, "Failed to send OTP. Please try again.")
return redirect('login')
return redirect('login')
def verify_otp_view(request):
identifier = request.session.get('otp_identifier')
if not identifier:
messages.error(request, "Session expired. Please request a new OTP.")
return redirect('login')
if request.method == 'POST':
user_otp = request.POST.get('otp', '').strip()
if not user_otp:
messages.error(request, "Please enter the OTP.")
return render(request, 'registration/verify_otp.html')
if verify_otp(identifier, user_otp):
# Match user
if is_email(identifier):
print(f"Email: {identifier}")
user = CustomUser.objects.get_by_email(email=identifier)
else:
user = CustomUser.objects.get_by_contact_number(contact_number=identifier)
# user.backend = 'django.contrib.auth.backends.ModelBackend' # <--- key line added
login(request, user)
messages.success(request, "Logged in successfully.")
request.session.pop('otp_identifier', None)
return redirect('home')
else:
messages.error(request, "Invalid or expired OTP.")
return render(request, 'registration/verify_otp.html')