416 lines
15 KiB
Python
Executable File
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')
|