first commit

This commit is contained in:
bobbyvish
2024-02-26 13:28:32 +05:30
commit 69dbc56374
1010 changed files with 136332 additions and 0 deletions

0
module_auth/__init__.py Normal file
View File

3
module_auth/admin.py Normal file
View File

@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

View File

@@ -0,0 +1,103 @@
from django.contrib.auth.hashers import make_password
from rest_framework import serializers
from module_iam.models import IAmPrincipal
from module_project import constants
from django.contrib.auth import authenticate
# class BasePasswordSerializer(serializers.Serializer):
# confirm_password = serializers.CharField(write_only=True, required=True)
# def validate(self, attrs):
# password = attrs.get("password")
# confirm_password = attrs.pop("confirm_password")
# if password != confirm_password:
# raise serializers.ValidationError({'password': constants.PASSWORD_NOT_MATCH})
# return super().validate(attrs)
# def update(self, instance, validate_data):
# new_password = validate_data.get("password")
# if new_password:
# instance.password = make_password(new_password)
# instance.save()
# return instance
class RegistrationSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True, required=True)
confirm_password = serializers.CharField(write_only=True, required=True)
class Meta:
model = IAmPrincipal
fields = [
"first_name",
"email",
"phone_no",
"password",
"confirm_password",
"player_id",
]
def validate(self, attrs):
email = attrs.get("email")
phone_no = attrs.get("phone_no")
password = attrs.get("password")
confirm_password = attrs.get("confirm_password")
if password != confirm_password:
raise serializers.ValidationError({'password': constants.PASSWORD_NOT_MATCH})
obj = self.Meta.model.objects.filter(email=email).first()
if obj:
raise serializers.ValidationError({"email": constants.EMAIL_EXISTS})
return attrs
def create(self, validated_data):
del validated_data['confirm_password']
validated_data["username"] = validated_data["email"]
validated_data["password"] = make_password(validated_data["password"])
principal = self.Meta.model.objects.create(**validated_data)
return principal
def update(self, instance, validated_data):
# update prinicpal instance fiedls based on the validation data
instance.first_name = validated_data.get("first_name", instance.first_name)
instance.email = validated_data.get("email", instance.email)
instance.username = validated_data.get("email", instance.email)
instance.save()
return instance
class LoginSerializer(serializers.Serializer):
email = serializers.EmailField(write_only=True, required=True)
password = serializers.CharField(write_only=True, required=True)
player_id = serializers.CharField(write_only=True, required=True)
class OtpVerificationSerializer(serializers.Serializer):
email = serializers.EmailField(write_only=True, required=True)
otp = serializers.IntegerField(write_only=True, required=True)
class PasswordResetSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True, required=True)
confirm_password = serializers.CharField(write_only=True, required=True)
class Meta:
model = IAmPrincipal
fields = [
"password",
"confirm_password"
]
def validate(self, attrs):
password = attrs.get("password")
confirm_password = attrs.get("confirm_password")
if password != confirm_password:
raise serializers.ValidationError({'password': constants.PASSWORD_NOT_MATCH})
return super().validate(attrs)
def update(self, instance, validate_data):
new_password = validate_data.get("password")
if new_password:
instance.password = make_password(new_password)
instance.save()
return instance

16
module_auth/api/urls.py Normal file
View File

@@ -0,0 +1,16 @@
from django.urls import path
from . import views
from rest_framework_simplejwt.views import TokenRefreshView
urlpatterns = [
path('token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
path("signup/", views.RegistrationView.as_view()),
path("login/", views.LoginView.as_view()),
path("request-otp/", views.OtpRequestView.as_view()),
path("verify-otp/", views.OTPVerificationView.as_view()),
path("forget-password/", views.ForgetPasswordView.as_view()),
# path("profile/", views.Profile)
]

183
module_auth/api/utils.py Normal file
View File

@@ -0,0 +1,183 @@
from typing import Optional
from module_project import constants
from module_project.utils import ApiResponse
from module_iam.models import IAmPrincipal, IAmPrincipalOtp
from rest_framework_simplejwt.tokens import RefreshToken
from django.core.exceptions import ValidationError
import logging
logger = logging.getLogger(__name__)
def generate_token_and_user_data(principal):
"""
Generate a token and user data based on an 'IAmPrincipal' object.
Args:
principal (IAmPrincipal): The user object.
Returns:
dict: A dictionary containing token data and user information.
"""
refresh = RefreshToken.for_user(principal)
data = {
"access": str(refresh.access_token),
"refresh": str(refresh),
"first_name": principal.first_name,
"phone_no": str(principal.phone_no),
"complete": principal.register_complete,
}
return data
class AuthService:
"""
Provides authentication services for IAmPrincipal users.
"""
def __init__(self, principal_model, otp_model=None):
self.principal_model = principal_model
self.otp_model = otp_model
def authenticate(self, principal_id: str, otp: Optional[str] = None, password: Optional[str] = None) -> None:
"""
Authenticates a principal using OTP and/or password.
Raises:
AuthenticationError: If authentication fails.
"""
try:
principal = self.get_principal(principal_id)
self.validate_principal_status(principal)
self.validate_credentials_provided(otp, password)
if otp:
self.authenticate_with_otp(principal, otp)
else:
self.authenticate_with_password(principal, password)
except ValidationError as e:
raise e
except Exception as e:
logger.error(f"Authentication failed for principal {principal_id}: {str(e)}")
raise ValidationError("Authentication failed") from e
def get_principal(self, principal_id: str) -> IAmPrincipal:
"""Retrieves a principal instance by ID."""
try:
return self.principal_model.objects.get(pk=principal_id)
except self.principal_model.DoesNotExist:
raise ValidationError("Invalid principal ID")
def get_principal_by_email(self, email: str):
"""Retrieves a principal instance by email."""
try:
return self.principal_model.objects.get(email=email)
except self.principal_model.DoesNotExist:
raise ValidationError(constants.EMAIL_NOT_REGISTERED)
def validate_principal_status(self, principal: IAmPrincipal) -> None:
"""Validates that the principal is active."""
if not principal.is_active:
raise ValidationError(constants.ACCOUNT_DEACTIVATED)
def validate_credentials_provided(self, otp: Optional[str], password: Optional[str]) -> None:
"""Ensures that at least one of OTP or password is provided."""
if otp is None and password is None:
raise ValidationError(constants.OTP_OR_PASSWORD_REQUIRED)
def authenticate_with_otp(self, principal: IAmPrincipal, otp: str) -> None:
"""Authenticates using OTP."""
if self.otp_model is None:
raise ValidationError("OTP authentication is not supported")
otp_instance = self.otp_model.objects.filter(principal=principal, otp_code=otp).last()
if not otp_instance:
raise ValidationError(constants.OTP_INVALID)
if otp_instance.is_expired():
raise ValidationError(constants.OTP_EXPIRED)
otp_instance.is_used = True
otp_instance.save()
def authenticate_with_password(self, principal: IAmPrincipal, password: str) -> None:
"""Authenticates using password."""
if not principal.check_password(password):
raise ValidationError(constants.INVALID_PASSWORD)
def authticate_with_otp_and_passsword(principal: IAmPrincipal, otp=None, password=None):
"""
Authenticate a principal using OTP and/or Password.
Parameters:
- principal (User): The principal object to authenticate.
- otp (str, optional): One-Time Password (OTP). Default is None.
- password (str, optional): User's password. Default is None.
Returns:
None: Successful authentication.
Response: Error response if authentication fails.
Example:
```
principal = User.objects.get(phone_no='8987546598')
otp = request.data.get("otp")
password = request.data.get("password")
result = authenticate_with_otp_and_password(principal, otp, password)
if isinstance(result, Response):
return result # Authentication failed, return error response
else:
# Authentication successful, proceed with authorized actions.
```
"""
if not principal.is_active:
return ApiResponse.error(
message=constants.ACCOUNT_DEACTIVATED, errors=constants.ACCOUNT_DEACTIVATED
)
# Ensure that either OTP or password is provided
if otp is None and password is None:
return ApiResponse.error(
message=constants.OTP_OR_PASSWORD_REQUIRED, errors=constants.OTP_OR_PASSWORD_REQUIRED
)
if otp:
otp_instance = IAmPrincipalOtp.objects.filter(
principal=principal, otp_code=otp
).last()
if not otp_instance:
return ApiResponse.error(
message=constants.OTP_INVALID, errors=constants.OTP_INVALID
)
if otp_instance.is_expired():
return ApiResponse.error(
message=constants.OTP_EXPIRED, errors=constants.OTP_EXPIRED
)
otp_instance.is_used = True
otp_instance.save()
elif password:
print(password)
if not principal.check_password(password):
return ApiResponse.error(
message=constants.INVALID_PASSWORD, errors=constants.INVALID_PASSWORD
)
print("after passsowrd", password)
return None
def get_principal_by_email(email: str):
try:
principal = IAmPrincipal.objects.get(email=email)
return principal
except IAmPrincipal.DoesNotExist:
error_response = {
"message": constants.EMAIL_NOT_REGISTERED,
"errors": constants.EMAIL_NOT_REGISTERED,
}
return ApiResponse.error(**error_response)

217
module_auth/api/views.py Normal file
View File

@@ -0,0 +1,217 @@
import datetime
from rest_framework import status
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated
from rest_framework_simplejwt.authentication import JWTAuthentication
from module_project import constants
from module_project.service import SMSService, EmailService
from module_project.utils import ApiResponse
from .utils import AuthService
from module_iam.models import IAmPrincipal, IAmPrincipalOtp
from .serializers import RegistrationSerializer, LoginSerializer, OtpVerificationSerializer, PasswordResetSerializer
from django.conf import settings
from rest_framework.response import Response
from .utils import (
generate_token_and_user_data, get_principal_by_email, authticate_with_otp_and_passsword
)
class RegistrationView(APIView):
authentication_classes = []
permission_classes = []
model = IAmPrincipal
serializer_class = RegistrationSerializer
def post(self, request):
serializer = self.serializer_class(data=request.data)
print(f"request data is {request.data}")
if not serializer.is_valid():
error_response = {
"status": status.HTTP_403_FORBIDDEN,
"message": constants.REGISTRATION_FAIL,
"errors": serializer.errors,
}
return ApiResponse.error(**error_response)
try:
instance = serializer.save()
principal = instance
token_data = generate_token_and_user_data(principal)
except Exception as e:
return ApiResponse.error(
status=status.HTTP_403_FORBIDDEN, message=str(e), errors=str(e)
)
return ApiResponse.success(message=constants.REGISTRATION_SUCCESS, data=token_data)
class LoginView(APIView):
authentication_classes = []
permission_classes = []
model = IAmPrincipal
serializer_class = LoginSerializer
def post(self, request):
serializer = self.serializer_class(data=request.data)
if not serializer.is_valid():
error_response = {
"status": status.HTTP_403_FORBIDDEN,
"message": constants.LOGIN_FAIL,
"errors": serializer.errors,
}
return ApiResponse.error(**error_response)
email = request.data.get("email")
otp = request.data.get("otp")
password = request.data.get("password")
player_id = request.data.get("player_id")
principal = get_principal_by_email(email=email)
if isinstance(principal, Response):
return principal
validation_result = authticate_with_otp_and_passsword(
principal, otp=otp, password=password
)
print("pasword instance ", validation_result)
if isinstance(validation_result, Response):
print("Errror reponse")
return validation_result # Return the error response if validation fails
# auth_service = AuthService(principal_model=IAmPrincipal)
# try:
# principal = self.model.objects.get(email=email)
# except Exception as e:
# error_response = {
# "status": status.HTTP_403_FORBIDDEN,
# "message": constants.INVALID_EMAIL_PASSWORD,
# "errors": constants.INVALID_EMAIL_PASSWORD,
# }
# return ApiResponse.error(**error_response)
# try:
# auth_service.authenticate(principal_id=principal.id, password=password)
# except Exception as e:
# error_response = {
# "status": status.HTTP_403_FORBIDDEN,
# "message": e,
# "errors": e,
# }
# return ApiResponse.error(**error_response)
try:
principal.player_id = player_id
principal.last_login = datetime.datetime.now()
principal.save()
except Exception as e:
error_response = {
"status": status.HTTP_500_INTERNAL_SERVER_ERROR,
"message": constants.INTERNAL_SERVER_ERROR,
"errors": str(e),
}
return ApiResponse.error(**error_response)
token_data = generate_token_and_user_data(principal)
return ApiResponse.success(message=constants.LOGIN_SUCCESS, data=token_data)
class OtpRequestView(APIView):
authentication_classes = []
permission_classes = []
def post(self, request):
if "email" not in request.data:
return ApiResponse.error(message=constants.EMAIL_REQUIRED, errors=constants.EMAIL_REQUIRED)
print(f"email auth username: {settings.EMAIL_HOST_USER}")
email = request.data.get("email")
principal = get_principal_by_email(email=email)
if isinstance(principal, Response):
return principal
try:
# auth_service = AuthService(IAmPrincipal)
# principal = auth_service.get_principal_by_email(request.data.get("email"))
otp_code = SMSService().create_otp(principal=principal, otp_purpose="Forget password")
except Exception as e:
return ApiResponse.error(message=str(e), errors=str(e))
email_service = EmailService(
subject="Forget Password",
to=principal.email,
from_email=settings.EMAIL_HOST_USER
)
# Send the email
try:
email_service.load_template("module_auth/email_template.html", context={"code": otp_code} )
email_service.send()
except Exception as e:
return ApiResponse.error(message=f"Error sending email: {str(e)}", errors=str(e))
return ApiResponse.success(message=constants.SUCCESS)
class OTPVerificationView(APIView):
authentication_classes = []
permission_classes = []
serializer_class = OtpVerificationSerializer
def post(self, request):
serializer = self.serializer_class(data=request.data)
if not serializer.is_valid():
error_response = {
"status": status.HTTP_403_FORBIDDEN,
"message": constants.VALIDATION_ERROR,
"errors": serializer.errors,
}
return ApiResponse.error(**error_response)
email = serializer.validated_data.get("email")
otp = serializer.validated_data.get("otp")
principal = get_principal_by_email(email=email)
if isinstance(principal, Response):
return principal
validation_result = authticate_with_otp_and_passsword(
principal, otp=otp
)
print("pasword instance ", validation_result)
if isinstance(validation_result, Response):
print("Errror reponse")
return validation_result # Return the error response if validation fails
token_data = generate_token_and_user_data(principal)
return ApiResponse.success(message=constants.SUCCESS, data=token_data)
class ForgetPasswordView(APIView):
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
serializer_class = PasswordResetSerializer
def post(self, request):
serializer = self.serializer_class(request.user, data=request.data)
if not serializer.is_valid():
error_response = {
"status": status.HTTP_403_FORBIDDEN,
"message": constants.VALIDATION_ERROR,
"errors": serializer.errors,
}
return ApiResponse.error(**error_response)
try:
serializer.save()
except Exception as e:
return ApiResponse.error(message=str(e), errors=str(e))
return ApiResponse.success(message=constants.SUCCESS)

6
module_auth/apps.py Normal file
View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class ModuleAuthConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'module_auth'

15
module_auth/forms.py Normal file
View File

@@ -0,0 +1,15 @@
from django import forms
from django.core import validators
from module_project import constants
class LoginForm(forms.Form):
email = forms.EmailField(
max_length=254,
widget=forms.TextInput(attrs={"autofocus": True}),
label="Email",
)
password = forms.CharField(
label="Password",
strip=False,
widget=forms.PasswordInput()
)

View File

3
module_auth/models.py Normal file
View File

@@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

3
module_auth/tests.py Normal file
View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

17
module_auth/urls.py Normal file
View File

@@ -0,0 +1,17 @@
from django.urls import path
from . import views
app_name = "module_auth"
urlpatterns = [
path('login/', views.AdminLoginView.as_view(), name="login"),
path('logout/', views.AdminLogoutView.as_view(), name="logout"),
path('password-reset/', views.CustomPasswordResetView.as_view(), name='password_reset'),
path('password-reset/done/', views.CustomPasswordResetDoneView.as_view(), name='password_reset_done'),
path('password-reset-confirm/<uidb64>/<token>/', views.CustomPasswordResetConfirmView.as_view(), name='password_reset_confirm'),
path('password-reset-complete/', views.CustomPasswordResetCompleteView.as_view(), name='password_reset_complete'),
path('users/', views.UserDashView.as_view(), name='users'),
path('users/list/', views.UserListJson.as_view(), name='users_list'),
path('user/view/<int:id>/', views.UserRecordView.as_view(), name='user_view'),
]

203
module_auth/views.py Normal file
View File

@@ -0,0 +1,203 @@
import logging
from django.db.models import Q, Prefetch
from django.contrib import messages
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.views import LogoutView
from django.contrib.auth.forms import PasswordResetForm
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.views import (
LoginView,
PasswordResetCompleteView,
PasswordResetConfirmView,
PasswordResetDoneView,
PasswordResetView,
)
from django.shortcuts import render, redirect, get_object_or_404
from django.urls import reverse_lazy
from django.views import generic
from .forms import LoginForm
from module_iam.models import IAmPrincipal
from module_activity.models import PrincipalHealthData, Intolerance, Symptoms, PastTreatment, ChronicCondition
from django_datatables_view.base_datatable_view import BaseDatatableView
from module_project import constants
logger = logging.getLogger(__name__)
class AdminLoginView(generic.View):
template_name = "module_auth/login.html"
form_class = LoginForm
success_url = reverse_lazy("module_iam:dashboard")
error_url = reverse_lazy("module_auth:login")
success_message = constants.LOGIN_SUCCESS
error_message = constants.INVALID_EMAIL_PASSWORD
def get(self, request):
form = self.form_class()
return render(request, self.template_name, context={"form": form})
def post(self, request):
form = self.form_class(data=request.POST)
context = {"form": form}
if not form.is_valid():
messages.error(request, constants.INVALID_EMAIL_PASSWORD)
return render(request, self.template_name, context=context)
email = form.cleaned_data['email']
password = form.cleaned_data['password']
user = authenticate(request, email=email, password=password)
if user is None:
messages.error(request, constants.INVALID_EMAIL_PASSWORD)
return render(request, self.template_name, context=context)
login(request, user)
logging.info(f"User {user.email} logged in.")
return redirect(self.success_url)
class AdminLogoutView(LogoutView):
next_page = reverse_lazy("module_auth:login")
class CustomPasswordResetView(PasswordResetView):
form_class = PasswordResetForm
template_name = "module_auth/password_reset_form.html"
email_template_name = "module_auth/password_reset_email_template.html"
success_url = reverse_lazy("module_auth:password_reset_done")
class CustomPasswordResetDoneView(PasswordResetDoneView):
template_name = "module_auth/password_reset_done.html"
class UserDashView(LoginRequiredMixin, generic.TemplateView):
page_name = None
resource = None
action = None
template_name = "module_auth/users_list.html"
model = IAmPrincipal
context_objext_name = "obj"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["page_name"] = self.page_name
return context
class UserListJson(BaseDatatableView):
model = IAmPrincipal
columns = ["id", "first_name", "email", "phone_no", "date_of_birth", "is_active"]
order_columns = ["id", "first_name", "email", "phone_no", "date_of_birth", "is_active"]
def filter_queryset(self, qs):
print(f"request is {self.request.GET}")
search_value = self.request.GET.get("search[value]", None)
if search_value:
# print(f"isdiget {search_value.isdigit()}")
# if search_value.isdigit():
# qs = qs.filter(Q(id=search_value))
qs = qs.filter(
Q(id__icontains=search_value)
| Q(first_name__icontains=search_value)
| Q(email__icontains=search_value)
| Q(date_of_birth__icontains=search_value)
| Q(phone_no__icontains=search_value)
)
for column in self.columns:
search_value = self.request.GET.get(f'columns[{self.columns.index(column)}][search][value]', None)
if search_value:
qs = qs.filter(**{f"{column}__icontains": search_value})
return qs
class UserRecordView(LoginRequiredMixin, generic.View):
page_name = None
resource = None
action = None
model = IAmPrincipal
template_name = "module_auth/user_view.html"
def get(self, request, id):
# Retrieve the IAmPrincipal instance
principal_instance = get_object_or_404(IAmPrincipal, id=id)
# Prefetch related Intolerance objects for the principal
intolerance_prefetch = Prefetch(
"intolerance_principal",
queryset=Intolerance.objects.filter(principal=principal_instance),
to_attr="intolerance_data",
)
symptom_prefetch = Prefetch(
"symptoms_principal",
queryset=Symptoms.objects.filter(principal=principal_instance),
to_attr="symptoms_data",
)
pasttreatment_prefetch = Prefetch(
"pasttreatment_principal",
queryset=PastTreatment.objects.filter(principal=principal_instance),
to_attr="pasttreatment_data",
)
chronic_prefetch = Prefetch(
"chronic_principal",
queryset=ChronicCondition.objects.filter(principal=principal_instance),
to_attr="chronic_data",
)
# Now fetch the principal instance with prefetches
obj = IAmPrincipal.objects.prefetch_related(
intolerance_prefetch,
symptom_prefetch,
pasttreatment_prefetch,
chronic_prefetch
).get(id=id)
print(f"prefetch datatas")
for data in obj.chronic_data:
print(f"data is {data.name, data.duration}")
# Render the template with the principal instance and related data
return render(request, self.template_name, {'obj': obj})
class CustomPasswordResetConfirmView(PasswordResetConfirmView):
template_name = "module_auth/password_reset_confirm.html"
success_url = reverse_lazy("module_auth:password_reset_complete")
class CustomPasswordResetCompleteView(PasswordResetCompleteView):
template_name = "module_auth/password_reset_complete.html"