wrong commit

This commit is contained in:
rizwanisready
2024-02-29 13:25:50 +05:30
commit db213d3228
1452 changed files with 193569 additions and 0 deletions

148
.gitignore vendored Normal file
View File

@@ -0,0 +1,148 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
media
logs
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
nifty11appvenv/
ENV/
env.bak/
venv.bak/
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
stripe/
# Cython debug symbols
# cython_debug/
# Footer
# © 2022 GitHub, Inc.
# Footer navigation
# Terms
# Privacy
# Security
# Status
# Docs
# Contact GitHub
# Pricing
# # API
# Training
# Blog
# About

0
accounts/__init__.py Normal file
View File

93
accounts/admin.py Normal file
View File

@@ -0,0 +1,93 @@
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from . import models
# Register your models here.
# class CustomIAmPrincipalAdmin(UserAdmin):
# list_display = (
# "id",
# "principal_type",
# "principal_source",
# "gender",
# "date_of_birth",
# "first_name",
# "last_name",
# "email",
# "phone_no",
# "is_staff",
# "is_superuser",
# "is_active",
# "address_line1",
# "address_line1",
# "city",
# "state",
# "country",
# "post_code",
# "profile_photo",
# "deleted",
# )
# list_filter = ("principal_type",)
# search_fields = ("email", "phone_no")
# fieldsets = (
# (None, {"fields": ("email", "password")}),
# (
# "Personal info",
# {
# "fields": (
# "first_name",
# "last_name",
# "gender",
# "date_of_birth",
# "phone_number",
# "address_line_one",
# "address_line_two",
# "city",
# "state",
# "country",
# "post_code",
# "profile_photo",
# )
# },
# ),
# (
# "Permissions",
# {"fields": ("is_active", "is_staff", "is_superuser", "deleted")},
# ),
# ("Important dates", {"fields": ("last_login",)}),
# )
# add_fieldsets = (
# (
# None,
# {
# "classes": ("wide",),
# "fields": ("email", "password1", "password2"),
# },
# ),
# )
# ordering = ["email"] # Specify the field to be used for ordering
# Now register the new UserModelAdmin...
admin.site.register(models.IAmPrincipal)
admin.site.register(models.IAmPrincipalType)
# admin.site.register(IAmPrincipal)
admin.site.register(models.IAmPrincipalSource)
admin.site.register(models.IAmPrincipalGroup)
admin.site.register(models.IAmAppResource)
admin.site.register(models.IAmRole)
admin.site.register(models.IAmAppAction)
admin.site.register(models.IAmPrincipalGroupLink)
admin.site.register(models.IAmPrincipalOtp)
admin.site.register(models.IAmPrincipalBiometric)
admin.site.register(models.IAmAppResourceActionLink)
admin.site.register(models.IAmPricipalGroupRoleLink)
admin.site.register(models.IAmRoleAppResourceActionLink)

View File

@@ -0,0 +1,42 @@
from django.conf import settings
from django.contrib.auth.hashers import check_password
from rest_framework import exceptions
from rest_framework.authentication import CSRFCheck
from rest_framework_simplejwt.authentication import JWTAuthentication
from accounts.models import IAmPrincipalOtp, IAmPrincipal
from nifty11_project import constants
from nifty11_project.utils import ApiResponse
def enforce_csrf(request):
"""
Enforce CSRF validation for session based authentication.
"""
def dummy_get_response(request): # pragma: no cover
return None
check = CSRFCheck(dummy_get_response)
# populates request.META['CSRF_COOKIE'], which is used in process_view()
check.process_request(request)
reason = check.process_view(request, None, (), {})
if reason:
# CSRF failed, bail with explicit error message
raise exceptions.PermissionDenied("CSRF Failed: %s" % reason)
class CustomAuthentication(JWTAuthentication):
def authenticate(self, request):
header = self.get_header(request)
if header is None:
raw_token = request.COOKIES.get(settings.SIMPLE_JWT["AUTH_COOKIE"]) or None
else:
raw_token = self.get_raw_token(header)
if raw_token is None:
return None
validated_token = self.get_validated_token(raw_token)
enforce_csrf(request)
return self.get_user(validated_token), validated_token

262
accounts/api/serializers.py Normal file
View File

@@ -0,0 +1,262 @@
import re
from django.contrib.auth.hashers import make_password
from rest_framework import serializers
from accounts.models import (
IAmPrincipal,
IAmPrincipalType,
# IAmPrincipalKYCDetails,
)
from manage_referrals.models import (
ReferralCode,
ReferralRecord,
)
from goodtimes import constants, date_utils
def clean_phone(number: str):
"""Validates number starts with +91 or 0, then 10 digits"""
number_pattern = re.compile(r"^(?:\+91|0)\d{10}$")
result = number_pattern.match(number)
if not result:
raise serializers.ValidationError({"phone_no": constants.PHONE_NUMBER_INVALID})
if number.startswith("0"):
return "+91" + number[1:]
return number
# class PhoneSerializer(serializers.Serializer):
# phone_no = serializers.CharField(max_length=15, required=True)
# def validate(self, attrs):
# phone_no = attrs.get("phone_no")
# cleaned_number = clean_phone(phone_no)
# attrs["phone_no"] = cleaned_number
# return super().validate(attrs)
class EmailSerializer(serializers.Serializer):
email = serializers.EmailField(
required=True,
)
principal_type = serializers.CharField(required=True, write_only=True)
def validate_principal_type_name(self, value):
"""
Check that the principal_type_name corresponds to a valid IAmPrincipalType.
"""
if not IAmPrincipalType.objects.filter(name=value).exists():
raise serializers.ValidationError("Invalid principal type name.")
return value
class BasePasswordSerializer(serializers.Serializer):
confirm_password = serializers.CharField(max_length=20, write_only=True)
def validate(self, attrs):
password = attrs.get("password")
confirm_password = attrs.pop("confirm_password", None)
if password != confirm_password:
raise serializers.ValidationError(
{"confirm_password": "Password do not match"}
)
return super().validate(attrs)
def update(self, instance, validated_data):
new_password = validated_data.get("password")
if new_password:
instance.password = make_password(new_password)
# instance.set_password(new_password)
instance.save()
return instance
class RegistrationSerializer(BasePasswordSerializer, serializers.ModelSerializer):
# email = serializers.EmailField(
# required=True,
# )
class Meta:
model = IAmPrincipal
fields = ["first_name", "last_name", "email", "password", "confirm_password"]
extra_kwargs = {
"first_name": {"required": True},
"last_name": {"required": True},
"email": {"required": True},
}
# def validate(self, attrs):
# email = attrs.get("email")
# # Check if the email is already associated with any user
# user_with_email = IAmPrincipal.objects.filter(email=email).first()
# if user_with_email:
# raise serializers.ValidationError({"email": [constants.EMAIL_EXISTS]})
# # # Check if the user has a different phone number
# # if user_with_email.phone_no != phone_no:
# # raise serializers.ValidationError({"email": [constants.EMAIL_EXISTS]})
# return attrs
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.last_name = validated_data.get("last_name", instance.last_name)
instance.email = validated_data.get("email", instance.email)
instance.username = validated_data.get("email", instance.email)
# Set the new password (if provided) correctly using set_password method
if "password" in validated_data:
new_password = validated_data["password"]
print("new_password serializers: ", new_password)
instance.set_password(
new_password
) # Correctly use set_password without assignment
instance.save()
return instance
class RegistrationPasswordSerializer(
BasePasswordSerializer, serializers.ModelSerializer
):
class Meta:
model = IAmPrincipal
fields = ["email", "password", "confirm_password"]
class PasswordResetSerializer(BasePasswordSerializer, serializers.ModelSerializer):
class Meta:
model = IAmPrincipal
fields = ["password", "confirm_password"]
class ProfileSerializer(serializers.ModelSerializer):
profile_photo = serializers.ImageField(required=False)
phone_no = serializers.CharField(read_only=True)
email = serializers.CharField(read_only=True)
invite_count = serializers.SerializerMethodField(read_only=True)
class Meta:
model = IAmPrincipal
fields = [
"profile_photo",
"first_name",
"last_name",
"phone_no",
"email",
"invite_count",
]
def update(self, instance, validated_data):
instance.profile_photo = validated_data.get(
"profile_photo", instance.profile_photo
)
instance.first_name = validated_data.get("first_name", instance.first_name)
instance.last_name = validated_data.get("last_name", instance.last_name)
return super().update(instance, validated_data)
def get_invite_count(self, obj):
if obj:
principal_type = self.context.get(
"principal_type"
) # Retrieve the principal_type from the context
return ReferralRecord.get_invite_count(obj, principal_type)
return 0
def get_image_url(self, obj, field_name, request):
image_field = getattr(obj, field_name)
if image_field:
return request.build_absolute_uri(image_field.url)
return ""
def to_representation(self, instance):
data = super().to_representation(instance)
request = self.context.get("request")
data["profile_photo"] = self.get_image_url(instance, "profile_photo", request)
return data
# class PrincipalKYCDetailsSerializer(serializers.ModelSerializer):
# aadhar_front_image = serializers.ImageField(required=False)
# aadhar_back_image = serializers.ImageField(required=False)
# pan_image = serializers.ImageField(required=False)
# class Meta:
# model = IAmPrincipalKYCDetails
# fields = [
# "aadhar_front_image",
# "aadhar_back_image",
# "aadhar_number",
# "is_aadhar_verified",
# "pan_image",
# "pan_number",
# "is_pan_verified",
# "account_no",
# "bank_name",
# "branch_name",
# "ifsc_code",
# ]
# def create(self, validated_data):
# # Extract and set the principal (user) from the request context
# validated_data["principal"] = self.context.get("request").user
# return super(PrincipalKYCDetailsSerializer, self).create(validated_data)
# def get_image_url(self, obj, field_name, request):
# image_field = getattr(obj, field_name)
# if image_field:
# return request.build_absolute_uri(image_field.url)
# return ""
# def to_representation(self, instance):
# data = super().to_representation(instance)
# request = self.context.get("request")
# if request:
# data["aadhar_front_image"] = self.get_image_url(
# instance, "aadhar_front_image", request
# )
# data["aadhar_back_image"] = self.get_image_url(
# instance, "aadhar_back_image", request
# )
# data["pan_image"] = self.get_image_url(instance, "pan_image", request)
# return data
class ReferralCodeSerializer(serializers.ModelSerializer):
class Meta:
model = ReferralCode
fields = ["referral_code"]
class ReferralRecordSerializer(serializers.ModelSerializer):
name = serializers.SerializerMethodField()
join_at = serializers.SerializerMethodField()
class Meta:
model = ReferralRecord
fields = ["name", "join_at"]
def get_name(self, obj):
# Check if the referred_principal is set (not None) and get the full name
if obj.referred_principal:
return obj.referred_principal.get_full_name()
return None
def get_join_at(self, obj):
return date_utils.format_date_to_string(obj.created_on)
# added 's' to differentiate with Email Serializer
class EmailSerializers(serializers.Serializer):
email = serializers.EmailField(required=True)
# otp = serializers.CharField(required=True)
class ProfilePhotoSerializer(serializers.ModelSerializer):
class Meta:
model = IAmPrincipal
fields = ("email", "profile_photo", "first_name")

35
accounts/api/urls.py Normal file
View File

@@ -0,0 +1,35 @@
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/phone/', views.RegistrationEmailView.as_view(), name='mobile_app_login'),
path('signup/details/', views.RegistrationDetailsView.as_view(), name='mobile_app_login'),
path('signup/password/', views.RegistrationPasswordView.as_view(), name='reset_password'),
path('login/', views.LoginView.as_view(), name='mobile_app_login'),
path('request-otp/', views.OtpRequestView.as_view(), name='send_otp'),
path('verify-otp/', views.OTPVerificationView.as_view(), name='send_otp'),
# path('profile/view/<str:principal_type>/', views.ProfileView.as_view(), name='profile_veiw'),
path('profile/view/', views.ProfileView.as_view(), name='profile_veiw'),
path('profile/edit/', views.ProfileView.as_view(), name='profile_edit'),
path('profile/password-reset/', views.ProfilePasswordResetView.as_view(), name='password_reset'),
# path('profile/kyc/', views.KycDocumentView.as_view(), name='pofile_kyc'),
path('referral-code/<str:principal_type>/', views.ReferralCodeViews.as_view(), name='referral_code'),
path('referral-record/<str:principal_type>/', views.ReferralRecordViews.as_view(), name='referral_record'),
path('google-login/', views.GoogleLoginAPIView.as_view(), name='google-login'),
]

111
accounts/api/utils.py Normal file
View File

@@ -0,0 +1,111 @@
from rest_framework import status
from goodtimes import constants
from goodtimes.utils import ApiResponse
from accounts.models import IAmPrincipal, IAmPrincipalOtp
from django.contrib.auth.hashers import check_password
from rest_framework_simplejwt.tokens import RefreshToken
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),
"first_name": principal.first_name,
"last_name": principal.last_name,
"email": str(principal.email),
"complete": principal.register_complete,
}
return data
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")
print(password)
validate = principal.check_password(password)
if not validate:
return ApiResponse.error(
message=constants.INVALID_PASSWORD, errors=constants.INVALID_PASSWORD
)
print("validate", validate)
print("after passsowrd", password)
# return None
def get_principal_by_email(email):
try:
principal = IAmPrincipal.objects.get(email=email)
return principal
except IAmPrincipal.DoesNotExist:
error_response = {
"status": status.HTTP_404_NOT_FOUND,
"message": constants.EMAIL_NOT_REGISTERED,
"errors": constants.EMAIL_NOT_REGISTERED,
}
return ApiResponse.error(**error_response)

740
accounts/api/views.py Normal file
View File

@@ -0,0 +1,740 @@
from django.db import transaction
from django.utils import timezone
from rest_framework import status
from rest_framework.views import APIView
from rest_framework_simplejwt.tokens import RefreshToken
from django.conf import settings
import requests
# from .authenticate import authticate_with_otp_and_passsword
from accounts.models import (
IAmPrincipal,
IAmPrincipalOtp,
IAmPrincipalType,
# IAmPrincipalKYCDetails,
)
from manage_referrals.models import ReferralCode, ReferralRecord
from goodtimes import constants
from goodtimes.services import SMSError, SMSService, EmailService
# from nifty11_project.services import SMSError, SMSService
from goodtimes.utils import ApiResponse
from accounts.resource_action import (
PRINCIPAL_TYPE_EVENT_USER,
PRINCIPAL_TYPE_EVENT_MANAGER,
PRINCIPAL_TYPE_FREE_USER,
)
from .serializers import (
EmailSerializers,
# RegistrationPasswordSerializer,
# PhoneSerializer,
EmailSerializer,
RegistrationPasswordSerializer,
RegistrationSerializer,
ReferralCodeSerializer,
ReferralRecordSerializer,
ProfileSerializer,
PasswordResetSerializer,
# PrincipalKYCDetailsSerializer,
)
from .utils import (
generate_token_and_user_data,
authticate_with_otp_and_passsword,
get_principal_by_email,
)
from rest_framework.permissions import IsAuthenticated
from rest_framework_simplejwt.authentication import JWTAuthentication
from rest_framework.response import Response
class RegistrationEmailView(APIView):
authentication_classes = []
permission_classes = []
model = IAmPrincipal
@transaction.atomic
def post(self, request):
serializer = EmailSerializer(data=request.data)
if not serializer.is_valid():
error_response = {
"status": status.HTTP_400_BAD_REQUEST,
"message": constants.REGISTRATION_FAIL,
"errors": serializer.errors,
}
return ApiResponse.error(**error_response)
email = serializer.validated_data.get("email")
principal_type = serializer.validated_data.get("principal_type")
print("principal_type: ", principal_type)
principal = None # Declare user variable outside of try-except blocks
# default_email = f"nifty{phone_no[-10:]}@gmail.com"
default_password = f"GTMES{email[::-1]}"
try:
principal = self.model.objects.get(email=email)
principal_type = IAmPrincipalType.get_principal_type(principal_type)
print(f"principal_type in try {principal_type}")
if principal.email_verified:
return ApiResponse.error(
message=constants.EMAIL_EXISTS,
errors=constants.EMAIL_EXISTS,
status=status.HTTP_403_FORBIDDEN,
)
# Update the existing user (unverified) with a default email and password
# principal.email = email
principal.principal_type = principal_type
principal.username = email
principal.set_password(default_password)
principal.save()
except self.model.DoesNotExist:
# Phone number doesn't exist, create a new user
principal = self.model.objects.create(email=email, username=email)
principal_type = IAmPrincipalType.get_principal_type(principal_type)
principal.principal_type = principal_type
principal.set_password(f"GTMES{email[::-1]}")
principal.save()
except Exception as e:
return ApiResponse.error(message=str(e), errors=str(e))
try:
# Send OTP to the user
otp = SMSService().create_otp(
principal=principal, opt_purpose="registration"
)
# Create an instance of the EmailService
email_service = EmailService(
subject="Good Times - OTP",
to=[email],
from_email=settings.EMAIL_HOST_USER,
)
email_service.load_template(
"otp/otp.html", context={"OTP": otp, "action": "Register"}
)
# Send the email
email_service.send()
print("Email sent successfully!")
except SMSError as e:
return ApiResponse.error(message=e.message, errors=e.message)
return ApiResponse.success(
message=constants.OTP_SENT, data=int(otp)
) # this will change
class RegistrationDetailsView(APIView):
authentication_classes = []
permission_classes = []
@transaction.atomic
def post(self, request):
"""
Handle User Registration and Referral.
This view processes user registration, validates data, creates new users, and tracks referrals if a referral code is provided. It returns success or error responses based on the registration outcome.
Parameters:
- request (HttpRequest): HTTP request object with registration data.
Returns:
- ApiResponse: Registration success or error response.
Process:
1. Extract data and validate registration.
2. Create a new user and OTP.
3. Generate referral codes for users.
4. Track referrals if a referral code is provided and not alreay register thorugh referral.
5. Return a response indicating success or error.
"""
# Extract the phone number from the request data
# phone_no = request.data.get("phone_no")
email = request.data.get("email")
print("email: ", email)
referral_code = request.data.get("referral_code")
print("referral_code", referral_code)
# Filter the user instance by phone number through reusable function
principal = get_principal_by_email(email)
if isinstance(principal, Response):
return principal # returning error here as it is error instance
# Validate the incoming data using the serializer
serializer = RegistrationSerializer(principal, data=request.data)
if not serializer.is_valid():
error_response = {
"status": status.HTTP_400_BAD_REQUEST,
"message": constants.VALIDATION_ERROR,
"errors": serializer.errors,
}
return ApiResponse.error(**error_response)
# Save the principal and related OTP
try:
instance = serializer.save()
instance.register_complete = True
instance.save()
except Exception as e:
print("instance: E-", e)
error_response = {
"status": status.HTTP_500_INTERNAL_SERVER_ERROR,
"message": constants.INTERNAL_SERVER_ERROR,
"errors": str(e),
}
return ApiResponse.error(**error_response)
# generating referrall_code of the player and merchant
try:
ReferralCode.create_referral_code_for_user_manager(
principal=principal, principal_type=principal.principal_type
)
except Exception as e:
print("ReferralCode: E-", e)
error_response = {
"status": status.HTTP_400_BAD_REQUEST,
"message": constants.FAILURE,
"errors": str(e),
}
return ApiResponse.error(**error_response)
if referral_code:
already_register_through_referral = ReferralRecord.objects.filter(
referred_principal=instance
).exists()
if not already_register_through_referral:
try:
whos_referral_code = ReferralCode.objects.get(
referral_code=referral_code
)
except Exception as e:
print("whos_referral_code: E-", e)
error_response = {
"status": status.HTTP_404_NOT_FOUND,
"message": constants.RECORD_NOT_FOUND,
"errors": str(e),
}
return ApiResponse.error(**error_response)
# principal_type = IAmPrincipalType.objects
ReferralRecord.objects.create(
referrer_principal=whos_referral_code.principal, # principal id of the User who invited
referred_principal=instance, # principal id of the User who join through invitation
principal_type=whos_referral_code.principal_type, # principal type of the user who invited
is_completed=True,
)
token_data = generate_token_and_user_data(principal)
token_data["type"] = str(principal.principal_type)
return ApiResponse.success(
message=constants.REGISTRATION_SUCCESS, data=token_data
)
class RegistrationPasswordView(APIView):
authentication_classes = []
permission_classes = []
def post(self, request):
email = request.data.get("email")
# type = request.data.get("type")
# Filter the user instance by phone number through reusable function
principal = get_principal_by_email(email)
if isinstance(principal, Response):
return principal # returning error here as it is error instance
serializer = RegistrationPasswordSerializer(principal, data=request.data)
if not serializer.is_valid():
error_response = {
"status": status.HTTP_400_BAD_REQUEST,
"message": constants.VALIDATION_ERROR,
"errors": serializer.errors,
}
return ApiResponse.error(**error_response)
try:
principal = serializer.save()
principal.register_complete = True
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)
token_data["type"] = str(principal.principal_type)
return ApiResponse.success(
message=constants.REGISTRATION_SUCCESS, data=token_data
)
class OtpRequestView(APIView):
authentication_classes = []
permission_classes = []
def post(self, request, *args, **kwargs):
serializer = EmailSerializers(data=request.data)
if not serializer.is_valid():
error_response = {
"status": status.HTTP_400_BAD_REQUEST,
"message": constants.VALIDATION_ERROR,
"errors": serializer.errors,
}
return ApiResponse.error(**error_response)
email = serializer.validated_data.get("email")
# Filter the user instance by phone number through reusable function
principal = get_principal_by_email(email)
if isinstance(principal, Response):
return principal # returning error here as it is error instance
try:
otp = SMSService().create_otp(principal=principal, opt_purpose="login")
# Create an instance of the EmailService
email_service = EmailService(
subject="Good Times - OTP",
to=[email],
from_email=settings.EMAIL_HOST_USER,
)
email_service.load_template(
"otp/otp.html", context={"OTP": otp, "action": "Login"}
)
# Send the email
email_service.send()
except SMSError as e:
return ApiResponse.error(message=e.message, errors=e.message)
return ApiResponse.success(message=constants.OTP_SENT, data=int(otp))
class OTPVerificationView(APIView):
authentication_classes = []
permission_classes = []
def post(self, request):
serializer = EmailSerializers(data=request.data)
if not serializer.is_valid():
error_response = {
"status": status.HTTP_400_BAD_REQUEST,
"message": "Validation error",
"errors": serializer.errors,
}
return ApiResponse.error(**error_response)
email = serializer.validated_data.get("email")
otp = request.data.get("otp")
# Filter the user instance by phon
# ]
# 4\\\\\\\\\\\\\\\\\\\\\\\7e number through reusable function
principal = get_principal_by_email(email)
if isinstance(principal, Response):
return principal # returning error here as it is error instance
validation_result = authticate_with_otp_and_passsword(principal, otp=otp)
if isinstance(validation_result, Response):
return validation_result # Return the error response if validation fails
try:
principal.email_verified = True # set the phone_verified to true
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)
return ApiResponse.success(message=constants.OTP_VERIFIED)
class LoginView(APIView):
authentication_classes = []
permission_classes = []
def post(self, request, *args, **kwargs):
serializer = EmailSerializers(data=request.data)
if not serializer.is_valid():
return Response(
{
"message": "Validation error",
"errors": serializer.errors,
},
status=status.HTTP_400_BAD_REQUEST,
)
email = serializer.validated_data.get("email")
otp = request.data.get("otp")
password = request.data.get("password")
principal = get_principal_by_email(email)
if isinstance(principal, Response):
return principal # If get_principal_by_email returns a Response object, it's an error response.
if not principal.is_active:
return Response(
{
"message": constants.ACCOUNT_DEACTIVATED,
"errors": constants.ACCOUNT_DEACTIVATED,
},
status=status.HTTP_403_FORBIDDEN,
)
if not otp and not password:
return Response(
{
"message": constants.OTP_OR_PASSWORD_REQUIRED,
"errors": constants.OTP_OR_PASSWORD_REQUIRED,
},
status=status.HTTP_400_BAD_REQUEST,
)
if otp:
return self._process_otp_login(principal, otp)
elif password:
return self._process_password_login(principal, password)
return Response(
{"message": constants.LOGIN_FAIL}, status=status.HTTP_400_BAD_REQUEST
)
def _process_otp_login(self, principal, otp):
otp_instance = IAmPrincipalOtp.objects.filter(
principal=principal, otp_code=otp
).last()
if not otp_instance or otp_instance.is_expired():
return Response(
{
"message": (
constants.OTP_INVALID
if not otp_instance
else constants.OTP_EXPIRED
),
"errors": (
constants.OTP_INVALID
if not otp_instance
else constants.OTP_EXPIRED
),
},
status=status.HTTP_400_BAD_REQUEST,
)
otp_instance.is_used = True
otp_instance.save()
return self._login_success(principal)
def _process_password_login(self, principal, password):
if not principal.check_password(password):
return Response(
{
"message": constants.INVALID_PASSWORD,
"errors": constants.INVALID_PASSWORD,
},
status=status.HTTP_400_BAD_REQUEST,
)
return self._login_success(principal)
def _login_success(self, principal):
try:
principal.email_verified = True
principal.last_login = timezone.localtime(timezone.now())
principal.save()
except Exception as e:
return Response(
{
"message": constants.INTERNAL_SERVER_ERROR,
"errors": str(e),
},
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
)
token_data = generate_token_and_user_data(principal)
return Response(
{
"message": constants.LOGIN_SUCCESS,
"data": token_data,
},
status=status.HTTP_200_OK,
)
class ProfileView(APIView):
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
model = IAmPrincipal
serializer = ProfileSerializer
def get_object(self):
return self.request.user
def get(self, request, *args, **kwargs):
instance = self.get_object()
context = {"request": request}
# context = {"principal_type": kwargs.get("principal_type"), "request": request}
serializer = self.serializer(instance, context=context)
success_response = {
"status": status.HTTP_200_OK,
"message": constants.SUCCESS,
"data": serializer.data,
}
return ApiResponse.success(**success_response)
def post(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.serializer(instance, data=request.data)
if not serializer.is_valid():
error_response = {
"status": status.HTTP_400_BAD_REQUEST,
"message": constants.VALIDATION_ERROR,
"errors": serializer.errors,
}
return ApiResponse.error(**error_response)
try:
serializer.save()
except Exception as e:
return ApiResponse.error(
message=constants.INTERNAL_SERVER_ERROR, errors=str(e)
)
return ApiResponse.success(message=constants.SUCCESS)
class ProfilePasswordResetView(APIView):
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
model = IAmPrincipal
serializer = PasswordResetSerializer
def post(self, request, *args, **kwargs):
current_password = request.data.get("current_password")
principal_obj = request.user
print(f"current password is {current_password}")
if current_password is None:
return ApiResponse.error(
message="Current password is required",
errors="Current password is required",
)
if not principal_obj.check_password(current_password):
return ApiResponse.error(
message="Invalid current password.", errors="Invalid current password."
)
serializer = self.serializer(instance=principal_obj, data=request.data)
if not serializer.is_valid():
error_response = {
"status": status.HTTP_400_BAD_REQUEST,
"message": "Validation error",
"errors": serializer.errors,
}
return ApiResponse.error(**error_response)
try:
serializer.save()
except Exception as e:
error_response = {
"status": status.HTTP_400_BAD_REQUEST,
"message": constants.INTERNAL_SERVER_ERROR,
"errors": str(e),
}
return ApiResponse.error(**error_response)
return ApiResponse.success(message=constants.SUCCESS)
# class KycDocumentView(APIView):
# authentication_classes = [JWTAuthentication]
# permission_classes = [IsAuthenticated]
# model = IAmPrincipalKYCDetails
# serializer = PrincipalKYCDetailsSerializer
# def get_object(self):
# try:
# return self.model.objects.get(principal=self.request.user)
# except self.model.DoesNotExist:
# return None
# def get(self, request, *args, **kwargs):
# instance = self.get_object()
# serializer = self.serializer(context={"request": request})
# if instance is not None:
# serializer.instance = instance # Update that instance if record exist
# success_response = {
# "status": status.HTTP_200_OK,
# "message": constants.SUCCESS,
# "data": serializer.data,
# }
# return ApiResponse.success(**success_response)
# def post(self, request, *args, **kwargs):
# instance = self.get_object()
# serializer = self.serializer(
# data=request.data, context={"request": request}
# ) # passing request context to update the principal from serializer
# if instance is not None:
# serializer.instance = instance # Update that instance if record exist
# if not serializer.is_valid():
# error_response = {
# "status": status.HTTP_400_BAD_REQUEST,
# "message": constants.VALIDATION_ERROR,
# "errors": serializer.errors,
# }
# return ApiResponse.error(**error_response)
# try:
# serializer.save()
# except Exception as e:
# return ApiResponse.error(
# message=constants.INTERNAL_SERVER_ERROR, errors=str(e)
# )
# return ApiResponse.success(message=constants.SUCCESS)
class ReferralCodeViews(APIView):
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
model = ReferralCode
serializer = ReferralCodeSerializer
def get(self, request, *args, **kwargs):
referral_obj = self.model.filter_referral_code(
principal=request.user, principal_type=kwargs.get("principal_type")
)
serializer_obj = self.serializer(referral_obj, many=True)
success_message = {
"status": status.HTTP_200_OK,
"message": constants.SUCCESS,
"data": serializer_obj.data,
}
return ApiResponse.success(**success_message)
class ReferralRecordViews(APIView):
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
model = ReferralRecord
serializer = ReferralRecordSerializer
def get(self, request, *args, **kwargs):
referral_obj = self.model.filter_invite_records(
referrer_principal=request.user, principal_type=kwargs.get("principal_type")
)
serializer_obj = self.serializer(referral_obj, many=True)
success_message = {
"status": status.HTTP_200_OK,
"message": constants.SUCCESS,
"data": {"count": referral_obj.count(), "record": serializer_obj.data},
}
return ApiResponse.success(**success_message)
class GoogleLoginAPIView(APIView):
authentication_classes = []
permission_classes = []
def post(self, request, *args, **kwargs):
access_token = request.data.get("access_token")
principal_type = request.data.get("principal_type")
if not access_token or not principal_type:
return Response(
{"error": "Access token & Principal Type is required"},
status=status.HTTP_400_BAD_REQUEST,
)
principal_info = self.get_google_user_data(access_token)
("principal_info: ", principal_info)
if not principal_info:
return Response(
{"error": "Failed to fetch user details or invalid access token"},
status=status.HTTP_400_BAD_REQUEST,
)
email = principal_info.get("email")
if not email:
return Response(
{"error": "Email is required but not provided."},
status=status.HTTP_400_BAD_REQUEST,
)
("email: ", email)
serializer = EmailSerializer(
data={"email": email, "principal_type": principal_type}
)
if serializer.is_valid():
print("Serializer is Validated..")
defaults = {
"first_name": principal_info.get("given_name", ""),
"last_name": principal_info.get("family_name", ""),
"register_complete": True,
"email_verified": True,
# 'principal_type': principal_type, # Uncomment if principal_type should be updated on each login
}
principal, created = IAmPrincipal.objects.update_or_create(
email=email,
defaults=defaults,
)
# If newly created, set password
if created:
print("New Created")
default_password = f"GTMES{email[::-1]}"
principal.set_password(default_password)
principal.principal_type = principal_type # Assuming principal_type should only be set on creation
principal.save()
token_data = generate_token_and_user_data(principal)
token_data["type"] = str(principal.principal_type)
message = "Already Registered and Verified User"
if created:
message = constants.REGISTRATION_SUCCESS
elif not principal.register_complete or not principal.email_verified:
message += ", details updated"
return ApiResponse.success(message=message, data=token_data)
return ApiResponse.error(
status=status.HTTP_400_BAD_REQUEST,
message=constants.FAILURE,
error=serializer.errors,
)
def get_google_user_data(self, access_token):
user_info_endpoint = "https://www.googleapis.com/oauth2/v3/userinfo"
response = requests.get(
user_info_endpoint, params={"access_token": access_token}
)
if response.status_code == 200:
return response.json()
if response.status_code != 200:
try:
error_details = response.json()
except ValueError: # Includes simplejson.decoder.JSONDecodeError
error_details = {
"error": "Failed to decode JSON response from Google API.",
"status": response.status_code,
}
return {
"error": error_details,
"status": response.status_code,
}

6
accounts/apps.py Normal file
View File

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

49
accounts/backend.py Normal file
View File

@@ -0,0 +1,49 @@
from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth.hashers import check_password
from goodtimes import constants
from .models import IAmPrincipalOtp
class EmailBackend(ModelBackend):
"""
Custom Authentication Backend for Email and Password Authentication.
It extends Django's built-in 'ModelBackend'
Methods:
- authenticate(self, request, email=None, password=None, **kwargs): Authenticate a user.
- get_user(self, user_id): Retrive a user by their user ID.
Example:
```
# Authenticate a user using their email and password
user = EmailBackend.authenticate(request, email='user@example.com', password='password123')
if user:
# Authentication successful, user is logged in.
else:
# Authentication failed, user is not logged in.
```
"""
UserModel = get_user_model()
def authenticate(self, email=None, password=None, **kwargs):
# Use a case-insensitive query for the email field
try:
user = self.UserModel.objects.get(email__iexact=email)
except self.UserModel.DoesNotExist:
return None
# Use the user's `check_password` method to verify the password
if user.check_password(password):
return user
return None
def get_user(self, user_id):
try:
return self.UserModel.objects.get(pk=user_id)
except self.UserModel.DoesNotExist:
return None

View File

@@ -0,0 +1,47 @@
from accounts import resource_action
from django.core.cache import cache
CACHE_KEY_RESOURCE_ACTION_CONSTANTS = 'resource_action_constants'
def resource_action_constants(request):
# Add debugging output
print("Resource and action constants context processor is executing.")
# Try to retrieve the constants from the cache
resource_action_constants = cache.get(CACHE_KEY_RESOURCE_ACTION_CONSTANTS)
# print(f"The value of '{CACHE_KEY_RESOURCE_ACTION_CONSTANTS}' in the cache is: {resource_action_constants}")
if resource_action_constants is None:
# Compute the constants if not found in the cache
resource_action_constants = compute_resource_action_constants()
print()
# Store the constants in the cache with a timeout (e.g., 3600 seconds for 1 hour)
cache.set(CACHE_KEY_RESOURCE_ACTION_CONSTANTS, resource_action_constants, 3600)
return {
'resource_context': resource_action_constants,
}
def compute_resource_action_constants():
constants_dict = {
'ACTION_CREATE': resource_action.ACTION_CREATE,
'ACTION_READ': resource_action.ACTION_READ,
'ACTION_UPDATE': resource_action.ACTION_UPDATE,
'ACTION_DELETE': resource_action.ACTION_DELETE,
'RESOURCE_MANAGE_DASHBOARD': resource_action.RESOURCE_MANAGE_DASHBOARD,
'RESOURCE_MANAGE_IAM': resource_action.RESOURCE_MANAGE_IAM,
'RESOURCE_MANAGE_CUSTOMER': resource_action.RESOURCE_MANAGE_CUSTOMER,
'RESOURCE_MANAGE_WALLET': resource_action.RESOURCE_MANAGE_WALLET,
'RESOURCE_MANAGE_PAYMENT': resource_action.RESOURCE_MANAGE_PAYMENT,
'RESOURCE_MANAGE_EVENTS': resource_action.RESOURCE_MANAGE_EVENTS,
'RESOURCE_MANAGE_CONTACT_US': resource_action.RESOURCE_MANAGE_CONTACT_US,
'RESOURCE_MANAGE_CMS': resource_action.RESOURCE_MANAGE_CMS,
'RESOURCE_MANAGE_REPORTS': resource_action.RESOURCE_MANAGE_REPORTS,
'RESOURCE_MANAGE_SUBSCRIPTIONS': resource_action.RESOURCE_MANAGE_SUBSCRIPTIONS,
'RESOURCE_MANAGE_REFERRALS': resource_action.RESOURCE_MANAGE_REFERRALS,
'RESOURCE_MANAGE_FEEDBACK': resource_action.RESOURCE_MANAGE_FEEDBACK,
'RESOURCE_IAM_PRINCIPAL': resource_action.RESOURCE_IAM_PRINCIPAL,
'RESOURCE_IAM_PRINCIPAL_GROUP': resource_action.RESOURCE_IAM_PRINCIPAL_GROUP,
'RESOURCE_IAM_GROUP': resource_action.RESOURCE_IAM_GROUP,
'RESOURCE_IAM_ROLE': resource_action.RESOURCE_IAM_ROLE,
}
return constants_dict

272
accounts/fixture_script.py Normal file
View File

@@ -0,0 +1,272 @@
import json
import os
from accounts.resource_action import (
PRINCIPAL_TYPE_EVENT_USER,
PRINCIPAL_TYPE_EVENT_MANAGER,
PRINCIPAL_TYPE_FREE_USER,
PRINCIPAL_TYPE_ADMIN,
PRINCIPAL_TYPE_SUBADMIN,
ACTION_CREATE,
ACTION_READ,
ACTION_UPDATE,
ACTION_DELETE,
RESOURCE_MANAGE_DASHBOARD,
RESOURCE_MANAGE_IAM,
RESOURCE_MANAGE_CUSTOMER,
RESOURCE_MANAGE_WALLET,
RESOURCE_MANAGE_PAYMENT,
RESOURCE_MANAGE_EVENTS,
RESOURCE_MANAGE_CONTACT_US,
RESOURCE_MANAGE_CMS,
RESOURCE_MANAGE_REPORTS,
RESOURCE_MANAGE_SUBSCRIPTIONS,
RESOURCE_MANAGE_REFERRALS,
RESOURCE_MANAGE_FEEDBACK,
)
# this variable store the data of model principaltype, action, resource
fixture_data = [
{
"model": "accounts.iamprincipaltype",
"pk": 1,
"fields": {
"name": PRINCIPAL_TYPE_EVENT_USER,
"label": PRINCIPAL_TYPE_EVENT_USER,
"slug": PRINCIPAL_TYPE_EVENT_USER,
"created_on": "2023-09-28T15:00:14.520",
"modified_on": "2023-09-28T15:00:14.526",
},
},
{
"model": "accounts.iamprincipaltype",
"pk": 2,
"fields": {
"name": PRINCIPAL_TYPE_ADMIN,
"label": PRINCIPAL_TYPE_ADMIN,
"slug": PRINCIPAL_TYPE_ADMIN,
"created_on": "2023-09-28T15:00:24.555",
"modified_on": "2023-09-28T15:00:24.556",
},
},
{
"model": "accounts.iamprincipaltype",
"pk": 3,
"fields": {
"name": PRINCIPAL_TYPE_SUBADMIN,
"label": PRINCIPAL_TYPE_SUBADMIN,
"slug": PRINCIPAL_TYPE_SUBADMIN,
"created_on": "2023-09-28T15:00:40.908",
"modified_on": "2023-09-28T15:00:40.908",
},
},
{
"model": "accounts.iamprincipaltype",
"pk": 4,
"fields": {
"name": PRINCIPAL_TYPE_EVENT_MANAGER,
"label": PRINCIPAL_TYPE_EVENT_MANAGER,
"slug": PRINCIPAL_TYPE_EVENT_MANAGER,
"created_on": "2023-09-28T15:00:40.908",
"modified_on": "2023-09-28T15:00:40.908",
},
},
{
"model": "accounts.iamprincipaltype",
"pk": 5,
"fields": {
"name": PRINCIPAL_TYPE_FREE_USER,
"label": PRINCIPAL_TYPE_FREE_USER,
"slug": PRINCIPAL_TYPE_FREE_USER,
"created_on": "2023-09-28T15:00:40.908",
"modified_on": "2023-09-28T15:00:40.908",
},
},
{
"model": "accounts.iamappaction",
"pk": 1,
"fields": {
"name": ACTION_CREATE,
"label": ACTION_CREATE,
"slug": ACTION_CREATE,
"created_on": "2023-09-28T16:52:16.756",
"modified_on": "2023-09-28T16:52:16.761",
},
},
{
"model": "accounts.iamappaction",
"pk": 2,
"fields": {
"name": ACTION_READ,
"label": ACTION_READ,
"slug": ACTION_READ,
"created_on": "2023-09-28T16:52:16.764",
"modified_on": "2023-09-28T16:52:16.764",
},
},
{
"model": "accounts.iamappaction",
"pk": 3,
"fields": {
"name": ACTION_UPDATE,
"label": ACTION_UPDATE,
"slug": ACTION_UPDATE,
"created_on": "2023-09-28T16:52:16.768",
"modified_on": "2023-09-28T16:52:16.768",
},
},
{
"model": "accounts.iamappaction",
"pk": 4,
"fields": {
"name": ACTION_DELETE,
"label": ACTION_DELETE,
"slug": ACTION_DELETE,
"created_on": "2023-09-28T16:52:16.770",
"modified_on": "2023-09-28T16:52:16.770",
},
},
{
"model": "accounts.iamappresource",
"pk": 1,
"fields": {
"name": RESOURCE_MANAGE_DASHBOARD,
"label": RESOURCE_MANAGE_DASHBOARD,
"slug": RESOURCE_MANAGE_DASHBOARD,
"created_on": "2023-09-28T16:17:42.783",
"modified_on": "2023-09-28T16:17:42.787",
"action": [1, 2, 3, 4],
},
},
{
"model": "accounts.iamappresource",
"pk": 2,
"fields": {
"name": RESOURCE_MANAGE_CUSTOMER,
"label": RESOURCE_MANAGE_CUSTOMER,
"slug": RESOURCE_MANAGE_CUSTOMER,
"created_on": "2023-09-28T16:17:42.791",
"modified_on": "2023-09-28T16:17:42.792",
"action": [1, 2, 3, 4],
},
},
{
"model": "accounts.iamappresource",
"pk": 3,
"fields": {
"name": RESOURCE_MANAGE_IAM,
"label": RESOURCE_MANAGE_IAM,
"slug": RESOURCE_MANAGE_IAM,
"created_on": "2023-09-28T16:17:42.795",
"modified_on": "2023-09-28T16:17:42.795",
"action": [1, 2, 3, 4],
},
},
{
"model": "accounts.iamappresource",
"pk": 4,
"fields": {
"name": RESOURCE_MANAGE_WALLET,
"label": RESOURCE_MANAGE_WALLET,
"slug": RESOURCE_MANAGE_WALLET,
"created_on": "2023-09-28T16:17:42.797",
"modified_on": "2023-09-28T16:17:42.797",
"action": [1, 2, 3, 4],
},
},
{
"model": "accounts.iamappresource",
"pk": 5,
"fields": {
"name": RESOURCE_MANAGE_PAYMENT,
"label": RESOURCE_MANAGE_PAYMENT,
"slug": RESOURCE_MANAGE_PAYMENT,
"created_on": "2023-09-28T16:17:42.797",
"modified_on": "2023-09-28T16:17:42.797",
"action": [1, 2, 3, 4],
},
},
{
"model": "accounts.iamappresource",
"pk": 6,
"fields": {
"name": RESOURCE_MANAGE_EVENTS,
"label": RESOURCE_MANAGE_EVENTS,
"slug": RESOURCE_MANAGE_EVENTS,
"created_on": "2023-09-28T16:17:42.801",
"modified_on": "2023-09-28T16:17:42.801",
"action": [1, 2, 3, 4],
},
},
{
"model": "accounts.iamappresource",
"pk": 7,
"fields": {
"name": RESOURCE_MANAGE_CONTACT_US,
"label": RESOURCE_MANAGE_CONTACT_US,
"slug": RESOURCE_MANAGE_CONTACT_US,
"created_on": "2023-09-28T16:17:42.804",
"modified_on": "2023-09-28T16:17:42.804",
"action": [1, 2, 3, 4],
},
},
{
"model": "accounts.iamappresource",
"pk": 8,
"fields": {
"name": RESOURCE_MANAGE_CMS,
"label": RESOURCE_MANAGE_CMS,
"slug": RESOURCE_MANAGE_CMS,
"created_on": "2023-09-28T16:17:42.806",
"modified_on": "2023-09-28T16:17:42.806",
"action": [1, 2, 3, 4],
},
},
{
"model": "accounts.iamappresource",
"pk": 9,
"fields": {
"name": RESOURCE_MANAGE_REPORTS,
"label": RESOURCE_MANAGE_REPORTS,
"slug": RESOURCE_MANAGE_REPORTS,
"created_on": "2023-09-28T16:17:42.809",
"modified_on": "2023-09-28T16:17:42.809",
"action": [1, 2, 3, 4],
},
},
{
"model": "accounts.iamappresource",
"pk": 10,
"fields": {
"name": RESOURCE_MANAGE_SUBSCRIPTIONS,
"label": RESOURCE_MANAGE_SUBSCRIPTIONS,
"slug": RESOURCE_MANAGE_SUBSCRIPTIONS,
"created_on": "2023-09-28T16:17:42.812",
"modified_on": "2023-09-28T16:17:42.812",
"action": [1, 2, 3, 4],
},
},
{
"model": "accounts.iamappresource",
"pk": 11,
"fields": {
"name": RESOURCE_MANAGE_FEEDBACK,
"label": RESOURCE_MANAGE_FEEDBACK,
"slug": RESOURCE_MANAGE_FEEDBACK,
"created_on": "2023-09-28T16:17:42.815",
"modified_on": "2023-09-28T16:17:42.815",
"action": [1, 2, 3, 4],
},
},
{
"model": "accounts.iamappresource",
"pk": 12,
"fields": {
"name": RESOURCE_MANAGE_REFERRALS,
"label": RESOURCE_MANAGE_REFERRALS,
"slug": RESOURCE_MANAGE_REFERRALS,
"created_on": "2023-09-28T16:17:42.815",
"modified_on": "2023-09-28T16:17:42.815",
"action": [1, 2, 3, 4],
},
},
]

View File

@@ -0,0 +1,305 @@
[
{
"model": "accounts.iamprincipaltype",
"pk": 1,
"fields": {
"name": "event_user",
"label": "event_user",
"slug": "event_user",
"created_on": "2023-09-28T15:00:14.520",
"modified_on": "2023-09-28T15:00:14.526"
}
},
{
"model": "accounts.iamprincipaltype",
"pk": 2,
"fields": {
"name": "admin",
"label": "admin",
"slug": "admin",
"created_on": "2023-09-28T15:00:24.555",
"modified_on": "2023-09-28T15:00:24.556"
}
},
{
"model": "accounts.iamprincipaltype",
"pk": 3,
"fields": {
"name": "subadmin",
"label": "subadmin",
"slug": "subadmin",
"created_on": "2023-09-28T15:00:40.908",
"modified_on": "2023-09-28T15:00:40.908"
}
},
{
"model": "accounts.iamprincipaltype",
"pk": 4,
"fields": {
"name": "event_manager",
"label": "event_manager",
"slug": "event_manager",
"created_on": "2023-09-28T15:00:40.908",
"modified_on": "2023-09-28T15:00:40.908"
}
},
{
"model": "accounts.iamprincipaltype",
"pk": 5,
"fields": {
"name": "free_user",
"label": "free_user",
"slug": "free_user",
"created_on": "2023-09-28T15:00:40.908",
"modified_on": "2023-09-28T15:00:40.908"
}
},
{
"model": "accounts.iamappaction",
"pk": 1,
"fields": {
"name": "create",
"label": "create",
"slug": "create",
"created_on": "2023-09-28T16:52:16.756",
"modified_on": "2023-09-28T16:52:16.761"
}
},
{
"model": "accounts.iamappaction",
"pk": 2,
"fields": {
"name": "read",
"label": "read",
"slug": "read",
"created_on": "2023-09-28T16:52:16.764",
"modified_on": "2023-09-28T16:52:16.764"
}
},
{
"model": "accounts.iamappaction",
"pk": 3,
"fields": {
"name": "update",
"label": "update",
"slug": "update",
"created_on": "2023-09-28T16:52:16.768",
"modified_on": "2023-09-28T16:52:16.768"
}
},
{
"model": "accounts.iamappaction",
"pk": 4,
"fields": {
"name": "delete",
"label": "delete",
"slug": "delete",
"created_on": "2023-09-28T16:52:16.770",
"modified_on": "2023-09-28T16:52:16.770"
}
},
{
"model": "accounts.iamappresource",
"pk": 1,
"fields": {
"name": "manage_dashboard",
"label": "manage_dashboard",
"slug": "manage_dashboard",
"created_on": "2023-09-28T16:17:42.783",
"modified_on": "2023-09-28T16:17:42.787",
"action": [
1,
2,
3,
4
]
}
},
{
"model": "accounts.iamappresource",
"pk": 2,
"fields": {
"name": "manage_customer",
"label": "manage_customer",
"slug": "manage_customer",
"created_on": "2023-09-28T16:17:42.791",
"modified_on": "2023-09-28T16:17:42.792",
"action": [
1,
2,
3,
4
]
}
},
{
"model": "accounts.iamappresource",
"pk": 3,
"fields": {
"name": "manage_iam",
"label": "manage_iam",
"slug": "manage_iam",
"created_on": "2023-09-28T16:17:42.795",
"modified_on": "2023-09-28T16:17:42.795",
"action": [
1,
2,
3,
4
]
}
},
{
"model": "accounts.iamappresource",
"pk": 4,
"fields": {
"name": "manage_wallet",
"label": "manage_wallet",
"slug": "manage_wallet",
"created_on": "2023-09-28T16:17:42.797",
"modified_on": "2023-09-28T16:17:42.797",
"action": [
1,
2,
3,
4
]
}
},
{
"model": "accounts.iamappresource",
"pk": 5,
"fields": {
"name": "manage_payment",
"label": "manage_payment",
"slug": "manage_payment",
"created_on": "2023-09-28T16:17:42.797",
"modified_on": "2023-09-28T16:17:42.797",
"action": [
1,
2,
3,
4
]
}
},
{
"model": "accounts.iamappresource",
"pk": 6,
"fields": {
"name": "manage_events",
"label": "manage_events",
"slug": "manage_events",
"created_on": "2023-09-28T16:17:42.801",
"modified_on": "2023-09-28T16:17:42.801",
"action": [
1,
2,
3,
4
]
}
},
{
"model": "accounts.iamappresource",
"pk": 7,
"fields": {
"name": "manage_contact_us",
"label": "manage_contact_us",
"slug": "manage_contact_us",
"created_on": "2023-09-28T16:17:42.804",
"modified_on": "2023-09-28T16:17:42.804",
"action": [
1,
2,
3,
4
]
}
},
{
"model": "accounts.iamappresource",
"pk": 8,
"fields": {
"name": "manage_cms",
"label": "manage_cms",
"slug": "manage_cms",
"created_on": "2023-09-28T16:17:42.806",
"modified_on": "2023-09-28T16:17:42.806",
"action": [
1,
2,
3,
4
]
}
},
{
"model": "accounts.iamappresource",
"pk": 9,
"fields": {
"name": "manage_reports",
"label": "manage_reports",
"slug": "manage_reports",
"created_on": "2023-09-28T16:17:42.809",
"modified_on": "2023-09-28T16:17:42.809",
"action": [
1,
2,
3,
4
]
}
},
{
"model": "accounts.iamappresource",
"pk": 10,
"fields": {
"name": "manage_subscriptions",
"label": "manage_subscriptions",
"slug": "manage_subscriptions",
"created_on": "2023-09-28T16:17:42.812",
"modified_on": "2023-09-28T16:17:42.812",
"action": [
1,
2,
3,
4
]
}
},
{
"model": "accounts.iamappresource",
"pk": 11,
"fields": {
"name": "manage_feedback",
"label": "manage_feedback",
"slug": "manage_feedback",
"created_on": "2023-09-28T16:17:42.815",
"modified_on": "2023-09-28T16:17:42.815",
"action": [
1,
2,
3,
4
]
}
},
{
"model": "accounts.iamappresource",
"pk": 12,
"fields": {
"name": "manage_referrals",
"label": "manage_referrals",
"slug": "manage_referrals",
"created_on": "2023-09-28T16:17:42.815",
"modified_on": "2023-09-28T16:17:42.815",
"action": [
1,
2,
3,
4
]
}
}
]

321
accounts/forms.py Normal file
View File

@@ -0,0 +1,321 @@
from typing import Any
from django import forms
from django.core.exceptions import ValidationError
from django.core import validators
from django.utils.translation import gettext_lazy as _
from goodtimes import constants
from . import models
# from .backend import EmailBackend
from phonenumber_field.formfields import PhoneNumberField
from accounts.resource_action import PRINCIPAL_TYPE_ADMIN, PRINCIPAL_TYPE_SUBADMIN
from django.contrib.auth import authenticate
class CustomAuthenticationForm(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(attrs={"autocomplete": "current-password"}),
)
def clean(self):
email = self.cleaned_data.get("email")
password = self.cleaned_data.get("password")
self.user = None
if email and password:
user = authenticate(email=email, password=password)
if user is None:
raise ValidationError({"__all__": [constants.INVALID_EMAIL_PASSWORD]})
elif not user.is_active:
raise ValidationError({"__all__": [constants.ACCOUNT_DEACTIVATED]})
self.user = user
return self.cleaned_data
class IAmPrincipalForm(forms.ModelForm):
password = forms.CharField(
widget=forms.PasswordInput(attrs={"autocomplete": "off"}),
validators=[
validators.MinLengthValidator(
limit_value=6, message="Password must be at least 6 characters long. "
)
],
)
confirm_password = forms.CharField(
widget=forms.PasswordInput(attrs={"autocomplete": "off"})
)
is_active = forms.BooleanField(
label="Active",
initial=True,
help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",
required=False,
)
class Meta:
model = models.IAmPrincipal
fields = [
"principal_type",
"first_name",
"last_name",
"email",
"password",
"confirm_password",
"is_active",
]
def __init__(self, *args, **kwargs):
instance = kwargs.get("instance")
super().__init__(*args, **kwargs)
self.fields["principal_type"].queryset = models.IAmPrincipalType.objects.filter(
active=True, deleted=False
)
# If it's a create action, exclude 'is_active' field
if instance is None:
self.fields.pop("is_active", None)
else:
# Exclude 'password' and 'confirm_password' fields for updates
self.fields.pop("password", None)
self.fields.pop("confirm_password", None)
# Make the 'email' field read-only
self.fields["email"].widget.attrs["readonly"] = True
def clean_email(self):
email = self.cleaned_data.get("email")
# Skip uniqueness validation if it's an update action (instance exists)
if self.instance and self.instance.email == email:
return email
if models.IAmPrincipal.objects.filter(email=email).exists():
raise forms.ValidationError(constants.EMAIL_EXISTS)
return email
def save(self, commit=True):
instance = super().save(commit=False)
# Check if it's a new object (create action) or an existing one (update action)
if not instance.pk: # pk is None for new objects
instance.username = self.cleaned_data["email"]
instance.set_password(self.cleaned_data["password"])
principal_type = self.cleaned_data.get("principal_type")
if principal_type is not None:
# Set is_superuser and is_staff based on principal_type
if principal_type == models.IAmPrincipalType.objects.get(name=PRINCIPAL_TYPE_ADMIN):
instance.is_superuser = True
elif principal_type == models.IAmPrincipalType.objects.get(name=PRINCIPAL_TYPE_SUBADMIN):
instance.is_staff = True
if commit:
instance.save()
return instance
class IAmPrincipalProfileForm(forms.ModelForm):
GENDER_CHOICES = (
("male", "Male"),
("female", "Female"),
("other", "Other"),
)
first_name = forms.CharField(required=True)
last_name = forms.CharField(required=True)
email = forms.EmailField(required=True)
password = forms.CharField(
widget=forms.PasswordInput(attrs={"autocomplete": "off"})
)
confirm_password = forms.CharField(
widget=forms.PasswordInput(attrs={"autocomplete": "off"})
)
# date_of_birth = forms.CharField(widget=forms.DateInput(attrs={'type': 'date'}))
phone_number = PhoneNumberField(
widget=forms.TextInput(),
)
# is_staff = forms.BooleanField(
# label="Staff Status",
# label_suffix="",
# initial=True,
# required=False,
# help_text="Check this box to designate that this user will be assigned permissions in the future.",
# )
# is_superuser = forms.BooleanField(
# label="SuperAdmin Status",
# label_suffix="",
# required=False,
# help_text="Check this box to designates that this user has all permissions without explicitly assigning them.",
# )
# gender = forms.ChoiceField(choices=GENDER_CHOICES)
class Meta:
model = models.IAmPrincipal
fields = [
"principal_type",
"first_name",
"last_name",
"email",
"password",
"confirm_password",
# 'gender',
# 'date_of_birth',
"phone_number",
# 'address_line1',
# 'address_line2',
# 'city',
# 'state',
# 'country',
# 'post_code',
# 'profile_photo',
# "is_staff",
# "is_superuser",
]
def __init__(self, *args, **kwargs):
instance = kwargs.get("instance")
super().__init__(*args, **kwargs)
self.fields["principal_type"].queryset = models.IAmPrincipalType.objects.filter(
active=True, deleted=False
)
# self.fields['principal_source'].queryset = models.IAmPrincipalSource.objects.filter(active=True, deleted=False)
# Check if an instance is provided and customize the form fields accordingly
if instance is not None:
# Exclude the 'password' and 'confirm_password' fields
self.fields.pop("password", None)
self.fields.pop("confirm_password", None)
# Make the 'email' field read-only
self.fields["email"].widget.attrs["readonly"] = True
# Modify the 'is_superuser' field to be not required
# self.fields["is_superuser"].required = False
# self.fields["is_staff"].required = False
# Add or modify the 'is_active' field
self.fields["is_active"] = forms.BooleanField(
label="Active",
initial=instance.is_active,
help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",
required=False,
)
def clean(self):
cleaned_data = super().clean()
password = cleaned_data.get("password")
confirm_password = cleaned_data.get("confirm_password")
if password and confirm_password and password != confirm_password:
self.add_error("confirm_password", "Password does not match")
return cleaned_data
def save(self, commit=True):
user = super().save(commit=False)
user.set_password(self.cleaned_data["password"])
if commit:
user.save()
return user
class ProfileEditForm(forms.ModelForm):
profile_photo = forms.ImageField(required=False)
class Meta:
model = models.IAmPrincipal
fields = [
"profile_photo",
"first_name",
"last_name"
]
class IAmPrincipalGroupLinkForm(forms.ModelForm):
class Meta:
model = models.IAmPrincipal
fields = [
# "principal_type",
"email",
"principal_group",
]
# principal_type = forms.ModelChoiceField(
# label="Principal Type",
# queryset=models.IAmPrincipalType.objects.filter(active=True, deleted=False),
# widget=forms.widgets.TextInput(attrs={"readonly": True}),
# )
principal_group = forms.ModelMultipleChoiceField(
label="Groups",
queryset=models.IAmPrincipalGroup.objects.filter(active=True, deleted=False),
required=False,
widget=forms.widgets.SelectMultiple(
attrs={"class": "form_select js-example-basic-multiple"}
),
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Make the 'email' field read-only
# self.fields['principal_type'].widget.attrs['disabled'] = True
self.fields['email'].widget.attrs['readonly'] = True
class IAmPrincipalTypeForm(forms.ModelForm):
class Meta:
model = models.IAmPrincipalType
fields = ["name", "active"]
def __init__(self, *args, **kwargs):
instance = kwargs.get("instance")
super().__init__(*args, **kwargs)
if instance is None:
self.fields.pop("active")
class IAmPrincipalGroupRoleLinkForm(forms.ModelForm):
class Meta:
model = models.IAmPrincipalGroup
fields = ["name", "role", "active"]
role = forms.ModelMultipleChoiceField(
queryset=models.IAmRole.objects.filter(active=True, deleted=False),
required=False,
widget=forms.widgets.SelectMultiple(
attrs={"class": "form-select js-example-basic-multiple"}
),
)
def __init__(self, *args, **kwargs):
instance = kwargs.get("instance")
# data = kwargs.get('data')
super().__init__(*args, **kwargs)
if instance is None:
# This is an add operation, exclude the 'active' field
self.fields.pop("active")
class IAmPrincipalRoleAppResourceActionLinkForm(forms.ModelForm):
class Meta:
model = models.IAmRole
fields = ["name", "active", "app_resource_action"]
required = {"app_resource_action": False}
app_resource_action = forms.ModelMultipleChoiceField(
queryset=models.IAmAppResourceActionLink.objects.all(),
widget=forms.CheckboxSelectMultiple,
required=False,
)
def __init__(self, *args, **kwargs):
instance = kwargs.get("instance")
super().__init__(*args, **kwargs)
if instance is None:
self.fields.pop("active")

86
accounts/google_login.py Normal file
View File

@@ -0,0 +1,86 @@
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from attrs import define
from random import SystemRandom
from urllib.parse import urlencode
from django.urls import reverse_lazy
from oauthlib.common import UNICODE_ASCII_CHARACTER_SET
@define
class GoogleRawLoginCredentials:
client_id: str
client_secret: str
project_id: str
def google_raw_login_get_credentials() -> GoogleRawLoginCredentials:
client_id = settings.GOOGLE_OAUTH2_CLIENT_ID
client_secret = settings.GOOGLE_OAUTH2_CLIENT_SECRET
project_id = settings.GOOGLE_OAUTH2_PROJECT_ID
if not client_id:
raise ImproperlyConfigured("GOOGLE_OAUTH2_CLIENT_ID missing in env.")
if not client_secret:
raise ImproperlyConfigured("GOOGLE_OAUTH2_CLIENT_SECRET missing in env.")
if not project_id:
raise ImproperlyConfigured("GOOGLE_OAUTH2_PROJECT_ID missing in env.")
credentials = GoogleRawLoginCredentials(
client_id=client_id, client_secret=client_secret, project_id=project_id
)
return credentials
class GoogleRawLoginFlowService:
API_URI = reverse_lazy("api:google-oauth2:login-raw:callback-raw")
GOOGLE_AUTH_URL = "https://accounts.google.com/o/oauth2/auth"
GOOGLE_ACCESS_TOKEN_OBTAIN_URL = "https://oauth2.googleapis.com/token"
GOOGLE_USER_INFO_URL = "https://www.googleapis.com/oauth2/v3/userinfo"
SCOPES = [
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/userinfo.profile",
"openid",
]
def __init__(self):
self._credentials = google_raw_login_get_credentials()
@staticmethod
def _generate_state_session_token(length=30, chars=UNICODE_ASCII_CHARACTER_SET):
# This is how it's implemented in the official SDK
rand = SystemRandom()
state = "".join(rand.choice(chars) for _ in range(length))
return state
def _get_redirect_uri(self):
domain = settings.BASE_BACKEND_URL
api_uri = self.API_URI
redirect_uri = f"{domain}{api_uri}"
return redirect_uri
def get_authorization_url(self):
redirect_uri = self._get_redirect_uri()
state = self._generate_state_session_token()
params = {
"response_type": "code",
"client_id": self._credentials.client_id,
"redirect_uri": redirect_uri,
"scope": " ".join(self.SCOPES),
"state": state,
"access_type": "offline",
"include_granted_scopes": "true",
"prompt": "select_account",
}
query_params = urlencode(params)
authorization_url = f"{self.GOOGLE_AUTH_URL}?{query_params}"
return authorization_url, state

View File

@@ -0,0 +1,52 @@
import json
import os
from typing import Any
import subprocess
from django.core.management.base import BaseCommand, CommandError
from tqdm import tqdm
from accounts.fixture_script import fixture_data
class Command(BaseCommand):
help = "Load custom fixture data."
def handle(self, *args, **options):
try:
self.stdout.write(self.style.SUCCESS("Fixture data loading started..."))
app_name = "accounts"
fixtures_directory = os.path.join(app_name, "fixtures")
fixture_filename = os.path.join(fixtures_directory, "resource_action_fixture.json")
fixture_data_list = [] # Create an empty list to hold fixture data
with tqdm(total=len(fixture_data)) as pbar:
for item in fixture_data:
fixture_data_list.append(item) # Append each data item to the list
pbar.update(1)
# Dump the entire fixture data list as a JSON array
with open(fixture_filename, "w") as fixture_file:
json.dump(fixture_data_list, fixture_file, indent=4)
self.stdout.write(
self.style.SUCCESS(f"Fixture data has been loaded successfully. Fixture file location: {fixture_filename}")
)
# Validate the generated JSON
try:
with open(fixture_filename, "r") as f:
json.load(f)
except json.JSONDecodeError as e:
raise CommandError(f"Invalid JSON in fixture file: {str(e)}")
# Run the loaddata command to load the created fixture
loaddata_command = f"python manage.py loaddata {fixture_filename}"
subprocess.run(loaddata_command, shell=True)
except Exception as e:
# Handle exceptions here
self.stderr.write(
self.style.ERROR(f"Fixture data loading failed: {str(e)}")
)

16
accounts/middleware.py Normal file
View File

@@ -0,0 +1,16 @@
from accounts.utils import UserContext
class UserContextMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
if request.user.is_authenticated:
UserContext.set_user(request.user)
else:
UserContext.set_user(None)
response = self.get_response(request)
return response

View File

@@ -0,0 +1,760 @@
# Generated by Django 5.0.2 on 2024-02-29 07:47
import django.contrib.auth.validators
import django.db.models.deletion
import django.utils.timezone
import phonenumber_field.modelfields
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
("auth", "0012_alter_user_first_name_max_length"),
]
operations = [
migrations.CreateModel(
name="IAmAppAction",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=255)),
("label", models.CharField(blank=True, max_length=255, null=True)),
("slug", models.SlugField(blank=True, max_length=255, null=True)),
("sort_order", models.IntegerField(blank=True, null=True)),
(
"small_image_url",
models.ImageField(blank=True, null=True, upload_to=""),
),
(
"large_image_url",
models.ImageField(blank=True, null=True, upload_to=""),
),
("active", models.BooleanField(default=True)),
("deleted", models.BooleanField(default=False)),
("created_by", models.SmallIntegerField(blank=True, null=True)),
("created_on", models.DateTimeField(auto_now_add=True)),
("modified_by", models.SmallIntegerField(blank=True, null=True)),
("modified_on", models.DateTimeField(auto_now=True)),
],
options={
"db_table": "iam_app_action",
},
),
migrations.CreateModel(
name="IAmAppResource",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=255)),
("label", models.CharField(blank=True, max_length=255, null=True)),
("slug", models.SlugField(blank=True, max_length=255, null=True)),
("sort_order", models.IntegerField(blank=True, null=True)),
(
"small_image_url",
models.ImageField(blank=True, null=True, upload_to=""),
),
(
"large_image_url",
models.ImageField(blank=True, null=True, upload_to=""),
),
("active", models.BooleanField(default=True)),
("deleted", models.BooleanField(default=False)),
("created_by", models.SmallIntegerField(blank=True, null=True)),
("created_on", models.DateTimeField(auto_now_add=True)),
("modified_by", models.SmallIntegerField(blank=True, null=True)),
("modified_on", models.DateTimeField(auto_now=True)),
],
options={
"db_table": "iam_app_resource",
},
),
migrations.CreateModel(
name="IAmPrincipalGroup",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=255)),
("label", models.CharField(blank=True, max_length=255, null=True)),
("slug", models.SlugField(blank=True, max_length=255, null=True)),
("sort_order", models.IntegerField(blank=True, null=True)),
(
"small_image_url",
models.ImageField(blank=True, null=True, upload_to=""),
),
(
"large_image_url",
models.ImageField(blank=True, null=True, upload_to=""),
),
("active", models.BooleanField(default=True)),
("deleted", models.BooleanField(default=False)),
("created_by", models.SmallIntegerField(blank=True, null=True)),
("created_on", models.DateTimeField(auto_now_add=True)),
("modified_by", models.SmallIntegerField(blank=True, null=True)),
("modified_on", models.DateTimeField(auto_now=True)),
],
options={
"db_table": "iam_principal_group",
},
),
migrations.CreateModel(
name="IAmPrincipalSource",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=255)),
("label", models.CharField(blank=True, max_length=255, null=True)),
("slug", models.SlugField(blank=True, max_length=255, null=True)),
("sort_order", models.IntegerField(blank=True, null=True)),
(
"small_image_url",
models.ImageField(blank=True, null=True, upload_to=""),
),
(
"large_image_url",
models.ImageField(blank=True, null=True, upload_to=""),
),
("active", models.BooleanField(default=True)),
("deleted", models.BooleanField(default=False)),
("created_by", models.SmallIntegerField(blank=True, null=True)),
("created_on", models.DateTimeField(auto_now_add=True)),
("modified_by", models.SmallIntegerField(blank=True, null=True)),
("modified_on", models.DateTimeField(auto_now=True)),
],
options={
"db_table": "iam_principal_source",
},
),
migrations.CreateModel(
name="IAmPrincipalType",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=255)),
("label", models.CharField(blank=True, max_length=255, null=True)),
("slug", models.SlugField(blank=True, max_length=255, null=True)),
("sort_order", models.IntegerField(blank=True, null=True)),
(
"small_image_url",
models.ImageField(blank=True, null=True, upload_to=""),
),
(
"large_image_url",
models.ImageField(blank=True, null=True, upload_to=""),
),
("active", models.BooleanField(default=True)),
("deleted", models.BooleanField(default=False)),
("created_by", models.SmallIntegerField(blank=True, null=True)),
("created_on", models.DateTimeField(auto_now_add=True)),
("modified_by", models.SmallIntegerField(blank=True, null=True)),
("modified_on", models.DateTimeField(auto_now=True)),
],
options={
"db_table": "iam_principal_type",
},
),
migrations.CreateModel(
name="IAmRole",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=255)),
("label", models.CharField(blank=True, max_length=255, null=True)),
("slug", models.SlugField(blank=True, max_length=255, null=True)),
("sort_order", models.IntegerField(blank=True, null=True)),
(
"small_image_url",
models.ImageField(blank=True, null=True, upload_to=""),
),
(
"large_image_url",
models.ImageField(blank=True, null=True, upload_to=""),
),
("active", models.BooleanField(default=True)),
("deleted", models.BooleanField(default=False)),
("created_by", models.SmallIntegerField(blank=True, null=True)),
("created_on", models.DateTimeField(auto_now_add=True)),
("modified_by", models.SmallIntegerField(blank=True, null=True)),
("modified_on", models.DateTimeField(auto_now=True)),
],
options={
"db_table": "iam_role",
},
),
migrations.CreateModel(
name="IAmPrincipal",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("password", models.CharField(max_length=128, verbose_name="password")),
(
"last_login",
models.DateTimeField(
blank=True, null=True, verbose_name="last login"
),
),
(
"is_superuser",
models.BooleanField(
default=False,
help_text="Designates that this user has all permissions without explicitly assigning them.",
verbose_name="superuser status",
),
),
(
"username",
models.CharField(
error_messages={
"unique": "A user with that username already exists."
},
help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
max_length=150,
unique=True,
validators=[
django.contrib.auth.validators.UnicodeUsernameValidator()
],
verbose_name="username",
),
),
(
"first_name",
models.CharField(
blank=True, max_length=150, verbose_name="first name"
),
),
(
"last_name",
models.CharField(
blank=True, max_length=150, verbose_name="last name"
),
),
(
"is_staff",
models.BooleanField(
default=False,
help_text="Designates whether the user can log into this admin site.",
verbose_name="staff status",
),
),
(
"is_active",
models.BooleanField(
default=True,
help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",
verbose_name="active",
),
),
(
"date_joined",
models.DateTimeField(
default=django.utils.timezone.now, verbose_name="date joined"
),
),
("email", models.EmailField(max_length=254, unique=True)),
("gender", models.CharField(blank=True, max_length=5, null=True)),
("date_of_birth", models.DateField(blank=True, null=True)),
(
"phone_no",
phonenumber_field.modelfields.PhoneNumberField(
blank=True, max_length=128, null=True, region=None
),
),
("address_line1", models.TextField(blank=True, null=True)),
("address_line2", models.TextField(blank=True, null=True)),
("city", models.CharField(blank=True, max_length=100, null=True)),
("state", models.CharField(blank=True, max_length=100, null=True)),
("country", models.CharField(blank=True, max_length=100, null=True)),
("post_code", models.CharField(blank=True, max_length=100, null=True)),
(
"profile_photo",
models.ImageField(blank=True, null=True, upload_to="profile"),
),
("phone_verified", models.BooleanField(default=False)),
("email_verified", models.BooleanField(default=False)),
(
"referral_code",
models.CharField(blank=True, max_length=50, null=True),
),
("created_on", models.DateTimeField(auto_now_add=True)),
("modified_on", models.DateTimeField(auto_now=True)),
("deleted", models.BooleanField(default=False)),
("register_complete", models.BooleanField(default=False)),
(
"created_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="creations",
to=settings.AUTH_USER_MODEL,
),
),
(
"groups",
models.ManyToManyField(
blank=True,
help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
related_name="user_set",
related_query_name="user",
to="auth.group",
verbose_name="groups",
),
),
(
"modified_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="modifications",
to=settings.AUTH_USER_MODEL,
),
),
(
"referred_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="referrals",
to=settings.AUTH_USER_MODEL,
),
),
(
"user_permissions",
models.ManyToManyField(
blank=True,
help_text="Specific permissions for this user.",
related_name="user_set",
related_query_name="user",
to="auth.permission",
verbose_name="user permissions",
),
),
],
options={
"db_table": "iam_principal",
},
),
migrations.CreateModel(
name="IAmAppResourceActionLink",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"app_action",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="resource_action_link_app_action",
to="accounts.iamappaction",
),
),
(
"app_resource",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="resource_action_link_app_resource",
to="accounts.iamappresource",
),
),
],
options={
"db_table": "iam_app_resource_action_link",
},
),
migrations.AddField(
model_name="iamappresource",
name="action",
field=models.ManyToManyField(
related_name="app_resource_action",
through="accounts.IAmAppResourceActionLink",
to="accounts.iamappaction",
),
),
migrations.CreateModel(
name="IAmPrincipalBiometric",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("active", models.BooleanField(default=True)),
("deleted", models.BooleanField(default=False)),
("created_on", models.DateTimeField(auto_now_add=True)),
("modified_on", models.DateTimeField(auto_now=True)),
("biometric_type", models.CharField(max_length=100)),
("biometric_data", models.CharField(max_length=255)),
(
"created_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="%(class)s_created",
to=settings.AUTH_USER_MODEL,
),
),
(
"modified_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="%(class)s_modified",
to=settings.AUTH_USER_MODEL,
),
),
(
"principal",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="principal_biometric",
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"db_table": "iam_principal_biometric",
},
),
migrations.CreateModel(
name="IAmPricipalGroupRoleLink",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"principal_group",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="role_link_principal_group",
to="accounts.iamprincipalgroup",
),
),
(
"role",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="role_link_role",
to="accounts.iamrole",
),
),
],
options={
"db_table": "iam_principal_group_role_link",
},
),
migrations.CreateModel(
name="IAmPrincipalGroupLink",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"principal",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="principal_group_link_principal",
to=settings.AUTH_USER_MODEL,
),
),
(
"principal_group",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="principal_group_link_group",
to="accounts.iamprincipalgroup",
),
),
],
options={
"db_table": "iam_principal_principal_group_link",
},
),
migrations.AddField(
model_name="iamprincipal",
name="principal_group",
field=models.ManyToManyField(
related_name="principal_groups",
through="accounts.IAmPrincipalGroupLink",
to="accounts.iamprincipalgroup",
),
),
migrations.CreateModel(
name="IAmPrincipalLocation",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("active", models.BooleanField(default=True)),
("deleted", models.BooleanField(default=False)),
("created_on", models.DateTimeField(auto_now_add=True)),
("modified_on", models.DateTimeField(auto_now=True)),
("latitude", models.DecimalField(decimal_places=8, max_digits=14)),
("longitude", models.DecimalField(decimal_places=8, max_digits=14)),
(
"created_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="%(class)s_created",
to=settings.AUTH_USER_MODEL,
),
),
(
"modified_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="%(class)s_modified",
to=settings.AUTH_USER_MODEL,
),
),
(
"principal",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="principal_location",
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"db_table": "iam_principal_location",
},
),
migrations.CreateModel(
name="IAmPrincipalMerchant",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("active", models.BooleanField(default=True)),
("deleted", models.BooleanField(default=False)),
("created_on", models.DateTimeField(auto_now_add=True)),
("modified_on", models.DateTimeField(auto_now=True)),
(
"created_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="%(class)s_created",
to=settings.AUTH_USER_MODEL,
),
),
(
"modified_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="%(class)s_modified",
to=settings.AUTH_USER_MODEL,
),
),
(
"principal",
models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
related_name="principal_merchant",
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"db_table": "iam_principal_merchant",
},
),
migrations.CreateModel(
name="IAmPrincipalOtp",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("otp_code", models.CharField(max_length=4)),
("otp_purpose", models.CharField(blank=True, max_length=50, null=True)),
("valid_till", models.DateTimeField()),
("is_used", models.BooleanField(default=False)),
(
"principal",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="principal_otp",
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"db_table": "iam_principal_otp",
},
),
migrations.AddField(
model_name="iamprincipal",
name="principal_source",
field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="principals_source",
to="accounts.iamprincipalsource",
),
),
migrations.AddField(
model_name="iamprincipal",
name="principal_type",
field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="principals_type",
to="accounts.iamprincipaltype",
),
),
migrations.AddField(
model_name="iamprincipalgroup",
name="role",
field=models.ManyToManyField(
related_name="principal_group_role",
through="accounts.IAmPricipalGroupRoleLink",
to="accounts.iamrole",
),
),
migrations.CreateModel(
name="IAmRoleAppResourceActionLink",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"app_resource_action",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="role_app_resource_action_link_app_resource_action",
to="accounts.iamappresourceactionlink",
),
),
(
"role",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="role_app_resource_action_link_role",
to="accounts.iamrole",
),
),
],
options={
"db_table": "iam_role_app_resource_action_link",
},
),
migrations.AddField(
model_name="iamrole",
name="app_resource_action",
field=models.ManyToManyField(
related_name="role_app_resource_action",
through="accounts.IAmRoleAppResourceActionLink",
to="accounts.iamappresourceactionlink",
),
),
]

View File

448
accounts/models.py Normal file
View File

@@ -0,0 +1,448 @@
from collections.abc import Iterable
import datetime
import random
import string
# from manage_wallets.models import Wallet, Transaction, TransactionStatus, TransactionType
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.models import AbstractUser, BaseUserManager
from django.db import models
from django.utils import timezone
from django.utils.text import slugify
from phonenumber_field.modelfields import PhoneNumberField
# from manage_subscriptions.models import Subscription
from goodtimes.utils import RandomGenerator
from .resource_action import (
PRINCIPAL_TYPE_EVENT_USER,
PRINCIPAL_TYPE_EVENT_MANAGER,
PRINCIPAL_TYPE_FREE_USER,
)
from .utils import UserContext
from django.core.validators import MaxValueValidator, MinValueValidator, RegexValidator
class BaseModel(models.Model):
active = models.BooleanField(default=True)
deleted = models.BooleanField(default=False)
created_by = models.ForeignKey(
settings.AUTH_USER_MODEL,
related_name="%(class)s_created",
on_delete=models.CASCADE,
blank=True,
null=True,
)
created_on = models.DateTimeField(auto_now_add=True)
modified_by = models.ForeignKey(
settings.AUTH_USER_MODEL,
related_name="%(class)s_modified",
on_delete=models.CASCADE,
blank=True,
null=True,
)
modified_on = models.DateTimeField(auto_now=True)
class Meta:
abstract = True
class MasterModel(models.Model):
name = models.CharField(max_length=255)
label = models.CharField(max_length=255, null=True, blank=True)
slug = models.SlugField(max_length=255, null=True, blank=True)
sort_order = models.IntegerField(blank=True, null=True)
small_image_url = models.ImageField(blank=True, null=True)
large_image_url = models.ImageField(blank=True, null=True)
active = models.BooleanField(default=True)
deleted = models.BooleanField(default=False)
created_by = models.SmallIntegerField(blank=True, null=True)
created_on = models.DateTimeField(auto_now_add=True)
modified_by = models.SmallIntegerField(blank=True, null=True)
modified_on = models.DateTimeField(auto_now=True)
class Meta:
abstract = True
def __str__(self):
return f"{self.name}"
def save(self, *args, **kwargs):
# Generate a slug from the name field
self.slug = slugify(self.name)
return super().save(*args, **kwargs)
class IAmPrincipalType(MasterModel):
class Meta:
db_table = "iam_principal_type"
@classmethod
def get_principal_type(cls, type):
return cls.objects.filter(name=type).first()
class IAmPrincipalSource(MasterModel):
class Meta:
db_table = "iam_principal_source"
class IAmAppAction(MasterModel):
class Meta:
db_table = "iam_app_action"
class IAmAppResource(MasterModel):
action = models.ManyToManyField(
IAmAppAction,
through="IAmAppResourceActionLink",
related_name="app_resource_action",
)
class Meta:
db_table = "iam_app_resource"
class IAmRoleAppResourceActionLinkManager(models.Manager):
def generate_app_resource_action_data(self):
"""
Generate a dictionary mapping resource names to associated actions.
Returns:
dict: A dictionary with resource names as keys and nested dictionaries
where action IDs are keys and action names are values.
Example:
{
"res1": {1: "a1", 2: "a2"},
"res2": {3: "a1", 4: "a2"}
}
"""
app_resource_action = self.select_related("app_resource", "app_action").all()
resource_action_link = {}
for item in app_resource_action:
resource = item.app_resource.name
action = item.app_action.name
id = item.id
if resource in resource_action_link:
resource_action_link[resource][id] = action
else:
resource_action_link[resource] = {id: action}
# print(resource_action_link)
return resource_action_link
class IAmAppResourceActionLink(models.Model):
app_resource = models.ForeignKey(
IAmAppResource,
related_name="resource_action_link_app_resource",
on_delete=models.CASCADE,
)
app_action = models.ForeignKey(
IAmAppAction,
related_name="resource_action_link_app_action",
on_delete=models.CASCADE,
)
objects = IAmRoleAppResourceActionLinkManager()
class Meta:
db_table = "iam_app_resource_action_link"
def __str__(self):
return f"{self.app_resource.name}: {self.app_action.name}"
class IAmRole(MasterModel):
app_resource_action = models.ManyToManyField(
IAmAppResourceActionLink,
through="IAmRoleAppResourceActionLink",
related_name="role_app_resource_action",
)
class Meta:
db_table = "iam_role"
class IAmRoleAppResourceActionLink(models.Model):
role = models.ForeignKey(
IAmRole,
related_name="role_app_resource_action_link_role",
on_delete=models.CASCADE,
)
app_resource_action = models.ForeignKey(
IAmAppResourceActionLink,
related_name="role_app_resource_action_link_app_resource_action",
on_delete=models.CASCADE,
)
class Meta:
db_table = "iam_role_app_resource_action_link"
class IAmPrincipalGroup(MasterModel):
role = models.ManyToManyField(
IAmRole, through="IAmPricipalGroupRoleLink", related_name="principal_group_role"
)
class Meta:
db_table = "iam_principal_group"
class IAmPricipalGroupRoleLink(models.Model):
principal_group = models.ForeignKey(
IAmPrincipalGroup,
related_name="role_link_principal_group",
on_delete=models.CASCADE,
)
role = models.ForeignKey(
IAmRole, related_name="role_link_role", on_delete=models.CASCADE
)
class Meta:
db_table = "iam_principal_group_role_link"
class IAmPrincipalManager(BaseUserManager):
def create_user(self, email, password=None, **extra_fields):
if not email:
raise ValueError("The Email field must be set")
email = self.normalize_email(email)
user = self.model(email=email, **extra_fields)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, password=None, **extra_fields):
extra_fields.setdefault("username", email)
extra_fields.setdefault("is_staff", True)
extra_fields.setdefault("is_superuser", True)
# extra_fields.setdefault("phone_no", "+919978895465")
extra_fields.setdefault("gender", "M")
extra_fields.setdefault("referral_code", f"admin_{random.randint(10, 100000)}")
extra_fields.setdefault("date_of_birth", timezone.now())
extra_fields.setdefault("created_by", None)
extra_fields.setdefault("created_on", timezone.now())
extra_fields.setdefault("modified_by", None)
extra_fields.setdefault("modified_on", timezone.now())
return self.create_user(email, password, **extra_fields)
class IAmPrincipal(AbstractUser):
principal_type = models.ForeignKey(
IAmPrincipalType,
related_name="principals_type",
null=True,
on_delete=models.PROTECT,
)
principal_source = models.ForeignKey(
IAmPrincipalSource,
related_name="principals_source",
on_delete=models.CASCADE,
null=True,
)
email = models.EmailField(unique=True)
gender = models.CharField(max_length=5, blank=True, null=True)
date_of_birth = models.DateField(blank=True, null=True)
phone_no = PhoneNumberField(blank=True, null=True)
address_line1 = models.TextField(blank=True, null=True)
address_line2 = models.TextField(blank=True, null=True)
city = models.CharField(max_length=100, blank=True, null=True)
state = models.CharField(max_length=100, blank=True, null=True)
country = models.CharField(max_length=100, blank=True, null=True)
post_code = models.CharField(max_length=100, blank=True, null=True)
profile_photo = models.ImageField(upload_to="profile", blank=True, null=True)
phone_verified = models.BooleanField(default=False)
email_verified = models.BooleanField(default=False)
referral_code = models.CharField(max_length=50, null=True, blank=True)
referred_by = models.ForeignKey(
"self",
null=True,
blank=True,
related_name="referrals",
on_delete=models.SET_NULL,
)
created_by = models.ForeignKey(
"self",
null=True,
blank=True,
related_name="creations",
on_delete=models.SET_NULL,
)
created_on = models.DateTimeField(auto_now_add=True)
modified_by = models.ForeignKey(
"self",
null=True,
blank=True,
related_name="modifications",
on_delete=models.SET_NULL,
)
modified_on = models.DateTimeField(auto_now=True)
deleted = models.BooleanField(default=False)
principal_group = models.ManyToManyField(
IAmPrincipalGroup,
through="IAmPrincipalGroupLink",
related_name="principal_groups",
)
register_complete = models.BooleanField(default=False)
USERNAME_FIELD = "email"
REQUIRED_FIELDS = []
objects = IAmPrincipalManager()
class Meta:
db_table = "iam_principal"
def __str__(self):
return f"{self.email}"
class IAmPrincipalGroupLink(models.Model):
principal = models.ForeignKey(
IAmPrincipal,
related_name="principal_group_link_principal",
on_delete=models.CASCADE,
)
principal_group = models.ForeignKey(
IAmPrincipalGroup,
related_name="principal_group_link_group",
on_delete=models.CASCADE,
)
class Meta:
db_table = "iam_principal_principal_group_link"
class IAmPrincipalMerchant(BaseModel):
principal = models.OneToOneField(
IAmPrincipal, related_name="principal_merchant", on_delete=models.CASCADE
)
def __str__(self):
return self.principal.email
class Meta:
db_table = "iam_principal_merchant"
class IAmPrincipalOtp(models.Model):
principal = models.ForeignKey(
IAmPrincipal, related_name="principal_otp", on_delete=models.CASCADE
)
otp_code = models.CharField(max_length=4)
otp_purpose = models.CharField(max_length=50, null=True, blank=True)
valid_till = models.DateTimeField()
is_used = models.BooleanField(default=False)
class Meta:
db_table = "iam_principal_otp"
def __str__(self):
return f"{self.principal.phone_no}:{self.otp_code} : {self.otp_purpose}"
def save(self, *args, **kwargs):
if not self.pk:
self.otp_code = RandomGenerator.random_otp()
self.valid_till = timezone.now() + timezone.timedelta(
minutes=settings.OTP_EXPIRE_TIME
)
super(IAmPrincipalOtp, self).save(*args, **kwargs)
def is_expired(self):
return timezone.now() >= self.valid_till
class IAmPrincipalBiometric(BaseModel):
principal = models.ForeignKey(
IAmPrincipal, related_name="principal_biometric", on_delete=models.CASCADE
)
biometric_type = models.CharField(max_length=100)
biometric_data = models.CharField(max_length=255)
class Meta:
db_table = "iam_principal_biometric"
def __str__(self):
return f"{self.principal.first_name}:{self.biometric_type}"
class IAmPrincipalLocation(BaseModel):
principal = models.ForeignKey(
IAmPrincipal, related_name="principal_location", on_delete=models.CASCADE
)
latitude = models.DecimalField(max_digits=14, decimal_places=8)
longitude = models.DecimalField(max_digits=14, decimal_places=8)
class Meta:
db_table = "iam_principal_location"
def __str__(self):
return f"{self.principal.first_name}:{self.latitude}, {self.longitude}"
# Excluded in migrations
# class IAmPrincipalKYCDetails(models.Model):
# # the below is the table structure from Hritik Dhanawde for KYC
# kid =
# status =
# customer_identifier =
# reference_id =
# customer_name =
# reference_id =
# customer_name =
# workflow_name =
# template_id =
# kyc_created_at =
# access_token =
# # Regex pattern for Aadhar number with exactly 12 digits
# AADHAR_REGEX = r"^\d{12}$"
# # Regex pattern for PAN number in the format AAAAB1234C
# PAN_REGEX = r"^[A-Z]{5}[0-9]{4}[A-Z]$"
# # Regex pattern for IFSC code (11 alphanumeric characters)
# IFSC_REGEX = r"^[A-Za-z]{4}\d{7}$"
# principal = models.OneToOneField(
# IAmPrincipal, on_delete=models.CASCADE
# ) # Assuming IAmPrincipal is the user model.
# aadhar_front_image = models.ImageField(upload_to="kyc/", blank=True, null=True)
# aadhar_back_image = models.ImageField(upload_to="kyc/", blank=True, null=True)
# aadhar_number = models.CharField(
# max_length=12,
# blank=True,
# null=True,
# validators=[
# RegexValidator(AADHAR_REGEX, message="Aadhar number must be 12 digits.")
# ],
# )
# pan_image = models.ImageField(upload_to="kyc/", blank=True, null=True)
# pan_number = models.CharField(
# max_length=10,
# blank=True,
# null=True,
# validators=[
# RegexValidator(
# PAN_REGEX, message="PAN number must be in the format AAAAB1234C."
# )
# ],
# )
# is_aadhar_verified = models.BooleanField(default=False)
# is_pan_verified = models.BooleanField(default=False)
# account_no = models.CharField(max_length=20, blank=True, null=True)
# bank_name = models.CharField(max_length=100, blank=True, null=True)
# branch_name = models.CharField(max_length=100, blank=True, null=True)
# ifsc_code = models.CharField(
# max_length=11,
# blank=True,
# null=True,
# validators=[
# RegexValidator(
# IFSC_REGEX, message="IFSC code must be 11 alphanumeric characters."
# )
# ],
# )
# class Meta:
# db_table = "iam_principal_kyc_details"
# def __str__(self):
# return f"KYC Information for {self.principal.email}"

64
accounts/permission.py Normal file
View File

@@ -0,0 +1,64 @@
from functools import wraps
from django.core.exceptions import PermissionDenied
from . import models
from django.db.models import Q
from rest_framework import permissions
# import logging
# logger = logging.getLogger(__name__)
class CustomPermissionRequiredMixin:
resource = None
action = None
def has_custom_permission(self, user, resource, action):
if not self.resource or not self.action:
raise AttributeError("Resource and action attributes must be defined in the view")
# if not request.user.is_authenticated:
# return self.handle_no_permission()
if user.is_superuser: # will chagne to principal type for admin
return True
permission_query = Q(
principal_group__role__app_resource_action__app_resource__name=resource,
principal_group__role__app_resource_action__app_action__name=action
)
return models.IAmPrincipal.objects.filter(permission_query, id=user.id).exists()
def dispatch(self, request, *args, **kwargs):
if not self.has_custom_permission(request.user, self.resource, self.action):
# logger.warning(f"Permission denied for user {request.user} accessing {self.resource}:{self.action}")
raise PermissionDenied("You do not have permission to access this resource.")
return super().dispatch(request, *args, **kwargs)
@classmethod
def as_decorator(cls, resource, action):
def decorator(view_func):
@wraps(view_func)
def _wrapped_view(request, *args, **kwargs):
instance = cls()
instance.resource = resource
instance.action = action
if not instance.has_custom_permission(request.user, instance.resource, instance.action):
raise PermissionDenied("You do not have permission to access this resource.")
return view_func(request, *args, **kwargs)
return _wrapped_view
return decorator
class IsOwnerOrReadOnly(permissions.BasePermission):
"""
Custom permission to only allow owners of an object to edit it.
"""
def has_object_permission(self, request, view, obj):
# Read permissions are allowed to any request,
# so we'll always allow GET, HEAD or OPTIONS requests.
if request.method in permissions.SAFE_METHODS:
return True
# Write permissions are only allowed to the owner of the object.
return obj.created_by == request.user

View File

@@ -0,0 +1,34 @@
PRINCIPAL_TYPE_EVENT_USER = "event_user"
PRINCIPAL_TYPE_EVENT_MANAGER = "event_manager"
PRINCIPAL_TYPE_FREE_USER = "free_user"
PRINCIPAL_TYPE_ADMIN = "admin"
PRINCIPAL_TYPE_SUBADMIN = "subadmin"
ACTION_CREATE = "create"
ACTION_READ = "read"
ACTION_UPDATE = "update"
ACTION_DELETE = "delete"
RESOURCE_MANAGE_DASHBOARD = "manage_dashboard"
RESOURCE_MANAGE_IAM = "manage_iam"
RESOURCE_MANAGE_CUSTOMER = "manage_customer"
RESOURCE_MANAGE_WALLET = "manage_wallet"
RESOURCE_MANAGE_PAYMENT = "manage_payment"
RESOURCE_MANAGE_EVENTS = "manage_events"
RESOURCE_MANAGE_CONTACT_US = "manage_contact_us"
RESOURCE_MANAGE_TICKET = "manage_ticket"
RESOURCE_MANAGE_CMS = "manage_cms"
RESOURCE_MANAGE_REPORTS = "manage_reports"
RESOURCE_MANAGE_SUBSCRIPTIONS = "manage_subscriptions"
RESOURCE_MANAGE_FEEDBACK = "manage_feedback"
RESOURCE_MANAGE_REFERRALS = "manage_referrals"
# These constants are used solely for managing the active and inactive state of pages
# and should not be considered as resources in the typical sense.
# They are used for page management purposes only.
RESOURCE_IAM_PRINCIPAL = "iam_principal"
RESOURCE_IAM_PRINCIPAL_GROUP = "iam_principal_group"
RESOURCE_IAM_GROUP = "iam_group"
RESOURCE_IAM_ROLE = "iam_role"

View File

@@ -0,0 +1,27 @@
from django import template
from accounts.permission import CustomPermissionRequiredMixin
register = template.Library()
@register.filter(name='has_resource_permission')
def has_resource_permission(user, resource_action):
"""
Check if a user has a specific resource and action permission.
Args:
user (User): The user to check for permission.
resource_action (str): The resource and action string (e.g., "resource_name.action_name").
Returns:
bool: True if the user has the specified permission, False otherwise.
Example usage in a template:
{% if user|has_resource_permission:"article.add" %}
<!-- Render content for users with permission -->
{% else %}
<!-- Render content for users without permission -->
{% endif %}
"""
resource, action = resource_action.split(".")
return CustomPermissionRequiredMixin().has_custom_permission(user, resource, action)

3
accounts/tests.py Normal file
View File

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

53
accounts/urls.py Normal file
View File

@@ -0,0 +1,53 @@
from django.urls import path
from . import views
from django.views.generic import TemplateView
app_name = 'accounts'
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('add/user/', views.PrinicpalCreateView.as_view(), name='register'),
path('manage-customer/', TemplateView.as_view(template_name='manage_customer/manage_customer.html'), name='manage_customer'),
path('manage-customer/edit/', TemplateView.as_view(template_name='manage_customer/edit_customer.html'), name='edit_customer'),
path('manage-customer/view/', TemplateView.as_view(template_name='manage_customer/view_customer.html'), name='view_customer'),
path('principal/', views.PrincipalListView.as_view(), name="principal_list"),
path('principal/add/', views.PrincipalCreateOrUpdateView.as_view(), name="principal_add"),
path('principal/edit/<int:pk>', views.PrincipalCreateOrUpdateView.as_view(), name="principal_edit"),
# path('principal/delete/<int:pk>', views.PrincipalDeleteView.as_view(), name="principal_delete"),
path('principal/group/link/', views.PrincipalGroupLinkListView.as_view(), name="principal_group_link_list"),
path('principal/group/link/edit/<int:pk>/', views.PrincipalGroupLinkEditView.as_view(), name="principal_group_link_edit"),
path('principal/group/', views.PrincipalGroupListView.as_view(), name="principal_group_list"),
path('principal/group/add/', views.PrincipalGroupCreateOrUpdateView.as_view(), name="principal_group_add"),
path('principal/group/edit/<int:pk>/', views.PrincipalGroupCreateOrUpdateView.as_view(), name="principal_group_edit"),
path('principal/group/delete/<int:pk>/', views.PrincipalGroupDeleteView.as_view(), name="principal_group_delete"),
path('principal/role/', views.AppRoleListView.as_view(), name="role_list"),
path('principal/role/add/', views.AppRoleCreateOrUpdateView.as_view(), name="role_add"),
path('principal/role/edit/<int:pk>/', views.AppRoleCreateOrUpdateView.as_view(), name="role_edit"),
path('principal/role/delete/<int:pk>/', views.AppRoleDeleteView.as_view(), name="role_delete"),
path('customer/', views.CustomerListView.as_view(), name="customer_list"),
# ignore this to path this for example setup
path('principal/example/', TemplateView.as_view(template_name="accounts/iam_module/example_form.html"), name="example_add"),
path('datatable/', views.DatatableListView.as_view(), name="serverside_list"),
# ignore end
path('profile/', views.PrincipalProfileView.as_view(), name="profile_details"),
path('profile/edit/', views.PrincipalProfileEditView.as_view(), name="profile_details_edit"),
]

13
accounts/utils.py Normal file
View File

@@ -0,0 +1,13 @@
import threading
class UserContext:
_thread_local_data = threading.local()
@classmethod
def set_user(cls, user):
cls._thread_local_data.current_user = user
@classmethod
def get_user(cls):
return getattr(cls._thread_local_data, "current_user", None)

633
accounts/views.py Normal file
View File

@@ -0,0 +1,633 @@
import logging
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.core.exceptions import ValidationError
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse_lazy
from django.views import generic
from django.db import models, transaction, IntegrityError
from goodtimes import constants
from . import resource_action
from .forms import (
CustomAuthenticationForm,
IAmPrincipalForm,
IAmPrincipalGroupRoleLinkForm,
IAmPrincipalRoleAppResourceActionLinkForm,
IAmPrincipalGroupLinkForm,
ProfileEditForm,
)
from .models import (
IAmPrincipal,
IAmPrincipalType,
IAmAppResourceActionLink,
IAmPrincipalGroup,
IAmRole,
)
logger = logging.getLogger(__name__)
class AdminLoginView(generic.View):
form_class = CustomAuthenticationForm
template_name = "accounts/authentication/login.html"
success_url = reverse_lazy("dashboard:main_dashboard")
error_url = reverse_lazy("accounts:login")
success_message = constants.LOGIN_SUCCESS
error_message = "Login failed, Invalid email or 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)
if not form.is_valid():
error_message = form.errors.get("__all__") or ["Invalid email or password."]
messages.error(
request, error_message[0]
) # Display the form-level error or fallback message
return redirect(self.error_url)
# Uncomment this block if you implement the first-time login logic
# if not user.last_login:
# messages.info(request, "Welcome! Since this is your first login, please change your password.")
# return redirect(reverse_lazy('accounts:change_password'))
login(request, form.user)
return redirect(self.success_url)
class AdminLogoutView(LogoutView):
next_page = reverse_lazy("accounts:login")
class CustomPasswordResetView(PasswordResetView):
form_class = PasswordResetForm
template_name = "accounts/authentication/password_reset_form.html"
email_template_name = "accounts/authentication/password_reset_email_template.html"
success_url = reverse_lazy("accounts:password_reset_done")
class CustomPasswordResetDoneView(PasswordResetDoneView):
template_name = "accounts/authentication/password_reset_done.html"
class CustomPasswordResetConfirmView(PasswordResetConfirmView):
template_name = "accounts/authentication/password_reset_confirm.html"
success_url = reverse_lazy("accounts:password_reset_complete")
class CustomPasswordResetCompleteView(PasswordResetCompleteView):
template_name = "accounts/authentication/password_reset_complete.html"
# class PrinicpalCreateView(generic.View):
# model = IAmPrincipal
# form_class = RegistrationForm
# template_name = "registration/form.html"
# title = "Add Sub admin"
# success_message = constants.RECORD_CREATED
# error_message = constants.ERROR_OCCURR
# success_url = reverse_lazy("accounts:register")
# def get_context_data(self, **kwargs):
# context = {"title": self.title, "operation": "Add"}
# context.update(kwargs)
# return context
# def get(self, *args, **kwargs):
# form = self.form_class()
# context = self.get_context_data(form=form)
# return render(self.request, self.template_name, context=context)
# def post(self, *args, **kwargs):
# form = self.form_class(self.request.POST)
# if not form.is_valid():
# messages.error(self.request, self.error_message)
# context = self.get_context_data(form=form)
# return render(self.request, self.template_name, context=context)
# form.save()
# messages.success(self.request, self.success_message)
# return redirect(self.success_url)
# template_name = "registration/password_reset_complete.html"
class AdminDashboard(generic.View):
template_name = "dashboard/index.html"
def get(self, request):
return render(request, self.template_name)
"""I Am Principal"""
class PrincipalListView(LoginRequiredMixin, generic.ListView):
page_name = resource_action.RESOURCE_IAM_PRINCIPAL
# resource = resource_action.RESOURCE_IAM_PRINCIPAL
# action = resource_action.ACTION_READ
model = IAmPrincipal
template_name = "accounts/iam_module/iam_principal_list.html"
context_object_name = "data_obj"
def get_queryset(self):
return (
super()
.get_queryset()
.select_related("principal_type", "principal_source")
.exclude(
models.Q(
principal_type__name=resource_action.PRINCIPAL_TYPE_EVENT_MANAGER
)
| models.Q(
principal_type__name=resource_action.PRINCIPAL_TYPE_EVENT_USER
)
| models.Q(
principal_type__name=resource_action.PRINCIPAL_TYPE_FREE_USER
)
)
)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["page_name"] = self.page_name
return context
import datetime
class PrincipalCreateOrUpdateView(LoginRequiredMixin, generic.View):
page_name = resource_action.RESOURCE_IAM_PRINCIPAL
model = IAmPrincipal
form_class = IAmPrincipalForm
template_name = "accounts/iam_module/iam_principal_add.html"
success_url = reverse_lazy("accounts:principal_list")
success_message = "Saved Successfully"
error_message = "An error occurred while saving the data."
def get_object(self):
pk = self.kwargs.get("pk")
return get_object_or_404(self.model, pk=pk) if pk else None
def get_context_data(self, **kwargs):
context = {
"page_name": self.page_name,
"operation": "Edit" if self.object else "Add",
}
context.update(kwargs) # Include any additional context data passed to the view
return context
def get(self, request, *args, **kwargs):
self.object = self.get_object()
form = self.form_class(instance=self.object)
context = self.get_context_data(form=form)
return render(request, self.template_name, context=context)
@transaction.atomic
def post(self, request, *args, **kwargs):
print(request.POST)
self.object = self.get_object()
form = self.form_class(request.POST, instance=self.object)
try:
if form.is_valid():
principal = form.save(commit=False)
# Check if it's a new object (create action) or an existing one (update action)
if not principal.pk: # pk is None for new objects
principal.created_by = request.user
principal.modified_by = request.user
principal.modified_on = datetime.datetime.now()
# Save the object
principal.save()
messages.success(request, "Form submitted successfully")
return redirect(self.success_url)
except Exception as e:
self.error_message = constants.ERROR_OCCURR.format(str(e))
print(self.error_message)
messages.error(request, self.error_message)
context = self.get_context_data(form=form)
return render(request, template_name=self.template_name, context=context)
"""Principal Group Link"""
class PrincipalGroupLinkListView(LoginRequiredMixin, generic.ListView):
page_name = resource_action.RESOURCE_IAM_PRINCIPAL_GROUP
model = IAmPrincipal
template_name = "accounts/iam_module/iam_principal_group_link_list.html"
context_object_name = "data_obj"
def get_queryset(self):
return (
super()
.get_queryset()
.select_related("principal_type", "principal_source")
.prefetch_related("principal_group")
)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["page_name"] = self.page_name
context["admin_principal"] = self.get_queryset().filter(
principal_type__name=resource_action.PRINCIPAL_TYPE_ADMIN, is_active=True
)
context["subadmin_principal"] = self.get_queryset().filter(
principal_type__name=resource_action.PRINCIPAL_TYPE_SUBADMIN, is_active=True
)
print(context["subadmin_principal"])
return context
class PrincipalGroupLinkEditView(LoginRequiredMixin, generic.View):
page_name = resource_action.RESOURCE_IAM_PRINCIPAL_GROUP
model = IAmPrincipal
template_name = "accounts/iam_module/iam_principal_group_link_edit.html"
form_class = IAmPrincipalGroupLinkForm
success_url = reverse_lazy("accounts:principal_group_link_list")
success_message = "Record Updated Successfully"
error_message = "An error occurred while saving the data"
def get_object(self):
pk = self.kwargs.get("pk")
return get_object_or_404(self.model, pk=pk) if pk else None
def get_context_data(self, **kwargs):
context = {
"page_name": self.page_name,
"operation": "Edit",
}
context.update(kwargs) # Include any additional context data passed to the view
return context
def get(self, request, *args, **kwargs):
self.object = self.get_object()
form = self.form_class(instance=self.object)
context = self.get_context_data(form=form)
return render(request, self.template_name, context=context)
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form = self.form_class(request.POST, instance=self.object)
if not form.is_valid():
context = self.get_context_data(form=form)
return render(request, self.template_name, context=context)
form.save()
messages.success(request, self.success_message)
return redirect(self.success_url)
"""Principal Group"""
class PrincipalGroupListView(LoginRequiredMixin, generic.ListView):
page_name = resource_action.RESOURCE_IAM_GROUP
model = IAmPrincipalGroup
template_name = "accounts/iam_module/iam_group_list.html"
context_object_name = "data_obj"
def get_queryset(self):
return super().get_queryset().prefetch_related("role").filter(deleted=False)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["page_name"] = self.page_name
return context
class PrincipalGroupCreateOrUpdateView(LoginRequiredMixin, generic.View):
page_name = resource_action.RESOURCE_IAM_GROUP
page_title = "Principal Group"
model = IAmPrincipalGroup
template_name = "accounts/iam_module/iam_group_add.html"
form_class = IAmPrincipalGroupRoleLinkForm
success_url = reverse_lazy("accounts:principal_group_list")
error_message = "An error occurred while saving the data."
def get_success_message(self):
self.success_message = (
constants.RECORD_CREATED if not self.object else constants.RECORD_UPDATED
)
return self.success_message
def get_object(self):
pk = self.kwargs.get("pk")
return get_object_or_404(self.model, pk=pk) if pk else None
def get_context_data(self, **kwargs):
context = {
"page_name": self.page_name,
"operation": "Add" if not self.object else "Edit",
}
context.update(kwargs) # Include any additional context data passed to the view
return context
def get(self, request, *args, **kwargs):
self.object = self.get_object()
form = self.form_class(instance=self.object)
context = self.get_context_data(form=form)
return render(request, self.template_name, context=context)
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form = self.form_class(request.POST, instance=self.object)
if not form.is_valid():
print(form.errors)
context = self.get_context_data(form=form)
return render(request, self.template_name, context=context)
form.save()
messages.success(self.request, self.get_success_message())
return redirect(self.success_url)
class PrincipalGroupDeleteView(LoginRequiredMixin, generic.View):
page_name = resource_action.RESOURCE_IAM_GROUP
model = IAmPrincipalGroup
success_url = reverse_lazy("accounts:principal_group_list")
success_message = constants.RECORD_DELETED
error_message = constants.RECORD_NOT_FOUND
def get(self, request, pk):
try:
type_obj = self.model.objects.get(id=pk)
principal = IAmPrincipal.objects.filter(principal_group=type_obj).exists()
if principal:
messages.success(
request,
"You can't delete this record as it's assigned to principals.",
)
else:
type_obj.deleted = True
type_obj.save()
messages.success(request, self.success_message)
except self.model.DoesNotExist:
messages.success(request, self.error_message)
return redirect(self.success_url)
""" Role"""
class AppRoleListView(LoginRequiredMixin, generic.ListView):
page_name = resource_action.RESOURCE_IAM_ROLE
model = IAmRole
template_name = "accounts/iam_module/iam_role_list.html"
context_object_name = "data_obj"
def get_queryset(self):
return (
super()
.get_queryset()
.prefetch_related(
"app_resource_action",
"app_resource_action__app_resource",
"app_resource_action__app_action",
)
.filter(deleted=False)
)
def generate_role_data(self):
roles = self.get_queryset()
role_data = []
for role in roles:
role_info = {
"id": role.id,
"name": role.name,
"active": role.active,
"resources": {},
}
for link in role.app_resource_action.all():
resource = link.app_resource.name
action = link.app_action.name
if resource in role_info["resources"]:
role_info["resources"][resource].append(action)
else:
role_info["resources"][resource] = [action]
role_data.append(role_info)
return role_data
def get_context_data(self, **kwargs):
context = {"page_name": self.page_name, "roles": self.generate_role_data()}
context.update(kwargs)
return context
class AppRoleCreateOrUpdateView(LoginRequiredMixin, generic.View):
page_name = resource_action.RESOURCE_IAM_ROLE
model = IAmRole
template_name = "accounts/iam_module/iam_role_add.html"
form_class = IAmPrincipalRoleAppResourceActionLinkForm
success_url = reverse_lazy("accounts:role_list")
success_message = "Saved Successfully"
error_message = "An error occurred while saving the data."
def get_success_message(self):
self.success_message = (
f"Record {'Created' if not self.object else 'Updated'} Successfully"
)
return self.success_message
def get_object(self):
pk = self.kwargs.get("pk")
return get_object_or_404(self.model, pk=pk) if pk else None
def get_context_data(self, **kwargs):
context = {
"page_name": self.page_name,
"operation": "Add" if not self.object else "Edit",
"app_resource_action": IAmAppResourceActionLink.objects.generate_app_resource_action_data(),
}
context.update(kwargs) # Include any additional context data passed to the view
return context
def get(self, request, *args, **kwargs):
try:
self.object = self.get_object()
form = self.form_class(instance=self.object)
context = self.get_context_data(form=form)
return render(request, self.template_name, context=context)
except Exception as e:
messages.error(request, str(e))
return redirect(self.success_url)
def post(self, request, *args, **kwargs):
try:
self.object = self.get_object()
form = self.form_class(request.POST, instance=self.object)
if not form.is_valid():
print(form.errors)
context = self.get_context_data(form=form)
return render(request, self.template_name, context=context)
form.save()
messages.success(self.request, self.get_success_message())
return redirect(self.success_url)
except Exception as e:
messages.error(self.request, str(e))
return redirect(self.success_url)
class AppRoleDeleteView(LoginRequiredMixin, generic.View):
page_name = resource_action.RESOURCE_IAM_ROLE
model = IAmRole
success_url = reverse_lazy("accounts:role_list")
success_message = constants.RECORD_DELETED
error_message = constants.RECORD_NOT_FOUND
def get(self, request, pk):
try:
type_obj = self.model.objects.get(id=pk)
principal = IAmPrincipalGroup.objects.filter(role=type_obj).exists()
if principal:
messages.success(
request, "You can't delete this record as it's assigned to groups."
)
else:
type_obj.deleted = True
type_obj.save()
messages.success(request, self.success_message)
except self.model.DoesNotExist:
messages.success(request, self.error_message)
return redirect(self.success_url)
"""Customer"""
class CustomerListView(LoginRequiredMixin, generic.ListView):
page_name = resource_action.RESOURCE_MANAGE_CUSTOMER
resource = resource_action.RESOURCE_MANAGE_CUSTOMER
action = resource_action.ACTION_READ
model = IAmPrincipal
template_name = "accounts/customer/customer_list.html"
context_object_name = "data_obj"
def get_queryset(self):
user_types = [
resource_action.PRINCIPAL_TYPE_EVENT_USER,
resource_action.PRINCIPAL_TYPE_EVENT_MANAGER,
resource_action.PRINCIPAL_TYPE_FREE_USER,
]
return (
super()
.get_queryset()
.select_related("principal_type", "principal_source")
.filter(
models.Q(
principal_type__name=resource_action.PRINCIPAL_TYPE_EVENT_MANAGER
)
| models.Q(
principal_type__name=resource_action.PRINCIPAL_TYPE_EVENT_USER
)
| models.Q(
principal_type__name=resource_action.PRINCIPAL_TYPE_FREE_USER
),
# principal_type__name=resource_action.PRINCIPAL_TYPE_PLAYER,
# principal_type__in=user_types,
deleted=False,
)
)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["page_name"] = self.page_name
return context
class DatatableListView(LoginRequiredMixin, generic.ListView):
pass
class PrincipalProfileView(LoginRequiredMixin, generic.ListView):
page_name = resource_action.RESOURCE_MANAGE_DASHBOARD
model = IAmPrincipal
template_name = "accounts/iam_module/profile_details.html"
context_object_name = "data_obj"
def get_queryset(self):
return (
super()
.get_queryset()
.select_related("principal_type", "principal_source")
.get(id=self.request.user.id)
)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["page_name"] = self.page_name
return context
class PrincipalProfileEditView(LoginRequiredMixin, generic.View):
page_name = resource_action.RESOURCE_MANAGE_DASHBOARD
model = IAmPrincipal
template_name = "accounts/iam_module/profile_details_edit.html"
form_class = ProfileEditForm
success_url = reverse_lazy("accounts:profile_details")
success_message = "Saved Successfully"
error_message = "An error occurred while saving the data."
def get_success_message(self):
self.success_message = (
f"Record {'Created' if not self.object else 'Updated'} Successfully"
)
return self.success_message
def get_object(self):
return self.request.user
def get_context_data(self, **kwargs):
context = {
# "page_name": self.page_name,
"operation": "Edit",
"page_name": self.page_name,
}
context.update(kwargs) # Include any additional context data passed to the view
return context
def get(self, request, *args, **kwargs):
# try:
self.object = self.get_object()
form = self.form_class(instance=self.object)
context = self.get_context_data(form=form)
return render(request, self.template_name, context=context)
# except Exception as e:
# print("error in project ", str)
# messages.error(request, str(e))
# return redirect(self.success_url)
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form = self.form_class(request.POST, request.FILES, instance=self.object)
if not form.is_valid():
print(form.errors)
context = self.get_context_data(form=form)
return render(request, self.template_name, context=context)
form.save()
messages.success(self.request, self.get_success_message())
return redirect(self.success_url)

0
chat/__init__.py Normal file
View File

15
chat/admin.py Normal file
View File

@@ -0,0 +1,15 @@
from django.contrib import admin
from . models import ChatGroup, ChatMessage
# Register your models here.
class ChatMessageAdmin(admin.ModelAdmin):
list_display = ('id', 'user', 'group', 'message', 'timestamp')
admin.site.register(ChatMessage, ChatMessageAdmin)
class ChatGroupAdmin(admin.ModelAdmin):
list_display = ('id', 'name')
admin.site.register(ChatGroup, ChatGroupAdmin)

23
chat/api/serializers.py Normal file
View File

@@ -0,0 +1,23 @@
from rest_framework import serializers
from accounts.models import IAmPrincipal
from goodtimes import constants
from chat import models
from accounts.api.serializers import ProfilePhotoSerializer
class ChatGroupSerializer(serializers.ModelSerializer):
class Meta:
model = models.ChatGroup
fields = ("id", "name")
class ChatMessageSerializer(serializers.ModelSerializer):
user = ProfilePhotoSerializer()
class Meta:
model = models.ChatMessage
fields = ("id", "group", "message", "timestamp", "user")
class TeamCheckSerializer(serializers.Serializer):
has_team = serializers.BooleanField()

17
chat/api/urls.py Normal file
View File

@@ -0,0 +1,17 @@
from django.urls import path
from . import views
app_name = "chat_api"
urlpatterns = [
path("<str:room_name>/", views.EnterRoomApi.as_view(), name="enter_room"),
path(
"chat_group/<int:game_id>/", views.ChatGroupAPIView.as_view(), name="chat_group"
),
path(
"chat_messages/<str:game_name>/",
views.ChatMessageAPIView.as_view(),
name="chat_messages",
),
# path('team_check/<int:game_id>/', views.TeamCheckAPIView.as_view(), name='team_check'),
]

108
chat/api/views.py Normal file
View File

@@ -0,0 +1,108 @@
from django.shortcuts import get_object_or_404
from rest_framework import status
from rest_framework.response import Response
from rest_framework.views import APIView
from accounts.models import IAmPrincipal
from goodtimes import services, constants
from rest_framework import generics, pagination
from chat import models
# from stock.models import Team
from django.conf import settings
from . import serializers
from goodtimes.utils import ApiResponse
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework_simplejwt.authentication import JWTAuthentication
class EnterRoomApi(APIView):
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
def post(self, request, room_name):
principal_id = request.user.id
return Response({"room_name": room_name})
class ChatGroupAPIView(APIView):
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
def get(self, request, game_id):
group_name = f"game_{game_id}"
chat_group, created = models.ChatGroup.objects.get_or_create(name=group_name)
serializer = serializers.ChatGroupSerializer(chat_group)
response_data = {
"status": status.HTTP_200_OK,
"message": constants.SUCCESS,
"data": serializer.data,
}
return ApiResponse.success(**response_data)
class CustomPagination(pagination.PageNumberPagination):
page_size = 30
page_size_query_param = "page_size"
max_page_size = 100
class ChatMessageAPIView(generics.ListAPIView):
serializer_class = serializers.ChatMessageSerializer
pagination_class = CustomPagination
def get_queryset(self):
game_name = f"game_{self.kwargs['game_name']}"
return models.ChatMessage.objects.filter(group__name=game_name).order_by(
"-timestamp"
)
def get(self, request, *args, **kwargs):
queryset = self.get_queryset()
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
response_data = {
"status": status.HTTP_200_OK,
"message": constants.SUCCESS,
"data": serializer.data,
}
return ApiResponse.success(**response_data)
response_empty = {
"status": status.HTTP_200_OK,
"message": constants.SUCCESS,
"data": serializer.data,
}
return ApiResponse.success(**response_empty)
# class TeamCheckAPIView(generics.RetrieveAPIView):
# serializer_class = serializers.TeamCheckSerializer
# def get(self, request, *args, **kwargs):
# user = request.user
# game_id = kwargs.get("game_id")
# if not user or not game_id:
# response_error = {
# "status": status.HTTP_404_NOT_FOUND,
# "message": constants.FAILURE,
# "errors": "User and Game ID are required parameters.",
# }
# return ApiResponse.error(**response_error)
# teams = Team.objects.filter(game_id=game_id, principal=request.user)
# has_team = teams.exists()
# data = {"has_team": has_team}
# serializer = serializers.TeamCheckSerializer(data=data)
# serializer.is_valid(raise_exception=True) # Ensure serializer is valid
# response_data = {
# "status": status.HTTP_200_OK,
# "message": constants.SUCCESS,
# "data": serializer.validated_data,
# }
# return ApiResponse.success(**response_data)

6
chat/apps.py Normal file
View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class ChatConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "chat"

127
chat/consumers.py Normal file
View File

@@ -0,0 +1,127 @@
from channels.generic.websocket import AsyncWebsocketConsumer, WebsocketConsumer
import json
import django
django.setup()
from accounts.models import IAmPrincipal
from channels.exceptions import StopConsumer
from asgiref.sync import async_to_sync, sync_to_async
from django.utils import timezone
from chat.models import ChatGroup, ChatMessage
from channels.db import database_sync_to_async
from django.db import close_old_connections
from rest_framework_simplejwt.tokens import AccessToken
from rest_framework_simplejwt.authentication import JWTAuthentication
import threading
class ChatConsumer(AsyncWebsocketConsumer):
async def connect(self):
print("self.scope: ", self.scope)
self.room_name = (
self.scope.get("url_route", {}).get("kwargs", {}).get("room_name")
)
print("self.room_name: ", self.room_name)
token_key = self.scope["url_route"]["kwargs"]["user"]
print("token_key: ", token_key)
self.user = await self.get_user_async(token_key)
print("self.user: ", self.user)
# Start the thread to get the user object
# user_thread = threading.Thread(target=self.get_user_async, args=(token_key,))
# user_thread.start()
# # Wait for the thread to finish and assign the user object to the scope
# user_thread.join()
# self.scope["user"] = self.user
# print("User: ", self.scope["user"])
# print("self.user: ", self.user)
# Join room group
await self.channel_layer.group_add(self.room_name, self.channel_name)
print("Time: ", timezone.now())
await self.accept()
async def disconnect(self, close_code):
# Leave room group
print("Time: ", timezone.now())
await self.channel_layer.group_discard(self.room_name, self.channel_name)
raise StopConsumer()
# await self.disconnect(close_code)
# Receive message from WebSocket
async def receive(self, text_data):
print("text_data: ", text_data)
print("self.user: ", self.user)
try:
text_data_json = json.loads(text_data)
message = text_data_json["message"]
except json.JSONDecodeError:
# Handle non-JSON message
message = text_data
print("message: ", message)
group = await self.get_chat_group(self.room_name)
print("group: ", group)
await self.create_chat_message(group, message, self.user)
# await self.create_chat_message(group, text_data_json, user)
# Send message to room group
await self.channel_layer.group_send(
# self.room_name, {"type": "chat.message", "message": message}
self.room_name,
{
"type": "chat.message",
"message": message,
"timestamp": str(timezone.now()),
"user": self.user.email,
"first_name": self.user.first_name,
"profile_photo": self.user.profile_photo.url
if self.user.profile_photo and self.user.profile_photo.url
else None,
},
)
@database_sync_to_async
def create_chat_message(self, room_name, message, user):
# principal = IAmPrincipal.objects.get(id=self.user)
return ChatMessage.objects.create(
group=room_name,
timestamp=timezone.now(),
message=message,
user=self.user,
)
@database_sync_to_async
def get_chat_group(self, room_name):
return ChatGroup.objects.filter(name=room_name).first()
# Receive message from room group
async def chat_message(self, event):
print("event: ", event)
message = event["message"]
user = event["user"]
first_name = event["first_name"]
profile_photo = event["profile_photo"]
timestamp = event["timestamp"]
# new_message = str(user) + " :- " + message
# Send message to WebSocket
await self.send(
text_data=json.dumps(
{
"message": message,
"user": user,
"first_name": first_name,
"profile_photo": profile_photo,
"timestamp": timestamp,
}
)
)
@database_sync_to_async
def get_user_async(self, token):
try:
decoded_token = JWTAuthentication().get_validated_token(token)
user = JWTAuthentication().get_user(decoded_token)
self.user = user
return user
except Exception as e:
self.user = None

View File

@@ -0,0 +1,62 @@
# Generated by Django 5.0.2 on 2024-02-29 07:47
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name="ChatGroup",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=255, unique=True)),
],
),
migrations.CreateModel(
name="ChatMessage",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("message", models.TextField()),
("timestamp", models.DateTimeField(auto_now_add=True)),
(
"group",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to="chat.chatgroup"
),
),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="PrincipalChat",
to=settings.AUTH_USER_MODEL,
),
),
],
),
]

View File

31
chat/models.py Normal file
View File

@@ -0,0 +1,31 @@
from django.db import models
from accounts.models import IAmPrincipal
# Create your models here.
class ChatGroupManager(models.Manager):
def get_or_create_group(self, game_id):
group_name = f"game_{game_id}"
chat_group, created = self.get_or_create(name=group_name)
return chat_group
class ChatGroup(models.Model):
name = models.CharField(max_length=255, unique=True)
objects = ChatGroupManager()
def __str__(self):
return self.name
class ChatMessage(models.Model):
group = models.ForeignKey(ChatGroup, on_delete=models.CASCADE)
message = models.TextField()
timestamp = models.DateTimeField(auto_now_add=True)
user = models.ForeignKey(
IAmPrincipal, related_name="PrincipalChat", on_delete=models.CASCADE
)
def __str__(self):
return self.group

8
chat/routing.py Normal file
View File

@@ -0,0 +1,8 @@
from django.urls import re_path, path
from . import consumers
websocket_urlpatterns = [
path("ws/chat/<str:room_name>/<str:user>", consumers.ChatConsumer.as_asgi()),
# path("ws/chat/<str:room_name>", consumers.ChatConsumer.as_asgi()),
]

3
chat/tests.py Normal file
View File

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

9
chat/urls.py Normal file
View File

@@ -0,0 +1,9 @@
from django.urls import path
from . import views
app_name = "chat"
urlpatterns = [
]

3
chat/views.py Normal file
View File

@@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.

0
dashboard/__init__.py Normal file
View File

3
dashboard/admin.py Normal file
View File

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

6
dashboard/apps.py Normal file
View File

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

View File

3
dashboard/models.py Normal file
View File

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

3
dashboard/tests.py Normal file
View File

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

12
dashboard/urls.py Normal file
View File

@@ -0,0 +1,12 @@
from django.urls import path
from . import views
from django.views.generic import TemplateView
from accounts import resource_action
app_name = 'dashboard'
urlpatterns = [
path('main-dashboard/', views.DashboardView.as_view(), name='main_dashboard'),
]

16
dashboard/views.py Normal file
View File

@@ -0,0 +1,16 @@
from django.shortcuts import render
from django.contrib.auth.mixins import LoginRequiredMixin
from accounts import resource_action
from django.views import generic
# Create your views here.
class DashboardView(LoginRequiredMixin, generic.TemplateView):
page_name = resource_action.RESOURCE_MANAGE_DASHBOARD
template_name = "dashboard/main-dashboard.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["page_name"] = self.page_name
return context

0
goodtimes/__init__.py Normal file
View File

16
goodtimes/asgi.py Normal file
View File

@@ -0,0 +1,16 @@
"""
ASGI config for goodtimes project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/5.0/howto/deployment/asgi/
"""
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "goodtimes.settings")
application = get_asgi_application()

71
goodtimes/constants.py Normal file
View File

@@ -0,0 +1,71 @@
VALIDATION_ERROR = "Validation Error"
# CRUD Related Constants
SUCCESS = "Operation successful."
FAILURE = "Operation failed."
RECORD_CREATED = "Record created successfully."
RECORD_UPDATED = "Record updated successfully."
RECORD_NOT_FOUND = "Record not found."
RECORD_DELETED = "Record deleted successfully."
ERROR_OCCURR = "An error occurred: {}"
SOMETHING_WRONG = "Something went wrong"
DATA_SAVED = "Data saved successfully."
DATA_UPDATED = "Data updated successfully."
DATA_DELETED = "Data deleted successfully."
DATA_IMPORT_SUCCESS = "Data import successful."
DATA_EXPORT_SUCCESS = "Data export successful."
DATA_INTEGRITY_ERROR = "Data integrity error. Please contact support."
INTERNAL_SERVER_ERROR = "Internal server error"
# File Related Constants
FILE_NOT_FOUND = "The requested file was not found."
FILE_UPLOAD_ERROR = "An error occurred while uploading files."
FILE_UPLOAD_SUCCESS = "Files uploaded successfully."
# Registration and Authentication Related Constants
REGISTRATION_INCOMPLETE = "Registration incomplete."
REGISTRATION_SUCCESS = "Registration successful."
REGISTRATION_FAIL = "Registration failed."
LOGIN_REQUIRED = "Login required to perform this action."
LOGIN_SUCCESS = "Login successful."
LOGIN_FAIL = "Login failed."
LOGOUT_SUCCESS = "Logout successful."
SESSION_EXPIRED = "Your session has expired. Please log in again."
ACCOUNT_DEACTIVATED = "Your account is inactive. Please contact support."
EMAIL_EXISTS = "This email address is already in use. Please use a different email."
INVALID_EMAIL_PASSWORD = "Invalid email or password."
INVALID_PASSWORD = "Invalid password."
INVALID_OPERATION = "Invalid operation requested."
PASSWORD_RESET_SUCCESS = "Password reset successful. You can now log in with your new password."
EMAIL_VERIFICATION_SUCCESS = "Email verification successful. You can now log in."
PASSWORD_CHANGE_SUCCESS = "Password change successful. Your password has been updated."
PASSWORD_CHANGE_FAILURE = "Password change failed. Please try again later."
# Mobile OTP Related Constants
PHONE_NUMBER_EXISTS = "This phone number is already in use."
EMAIL_EXISTS = "This email is already in use."
PHONE_NUMBER_NOT_REGISTERED = "This phone number is not registered."
EMAIL_NOT_REGISTERED = "This phone number is not registered."
PHONE_NUMBER_NOT_FOUND = "Phone number not found."
PHONE_FIELD_IS_REQUIRED = "Phone field is required."
PHONE_NUMBER_INVALID = 'Invalid phone number.'
PHONE_NUMBER_VERIFICATION_SUCCESS = "Phone number verification successful."
PHONE_NUMBER_VERIFICATION_FAILED = "Phone number verification failed."
OTP_INVALID = "Invalid OTP."
OTP_VERIFIED = "OTP verified successful"
OTP_SENT = "OTP sent successfully."
OTP_FIELD_IS_REQUIRED = "OTP is required."
OTP_OR_PASSWORD_REQUIRED = "OTP or Password is required"
OTP_EXPIRED = "OTP has expired."
# Email Related Constants
EMAIL_SENT = "Email sent successfully."
EMAIL_NOT_SENT = "Email could not be sent. Please try again later."
# Payment and Transaction Related Constants
PAYMENT_SUCCESS = "Payment successful. Thank you for your purchase!"
PAYMENT_FAILED = "Payment failed. Please check your payment details and try again."
TRANSACTION_PENDING = "Your transaction is currently pending processing."
WITHDRAWAL_FAILED = "Withdraw failed. Please check your wallet details."
WITHDRAWAL_SUCCESS = "Withdraw successful. Happy Earnings!"

64
goodtimes/date_utils.py Normal file
View File

@@ -0,0 +1,64 @@
from datetime import datetime, timedelta
# Format date
def format_date_to_string(date, format='%d %b, %Y'):
return date.strftime(format)
# Format date or time
def format_datetime_to_sting(dt, format='%d %b, %Y %H:%M:%S'):
return dt.strftime(format)
# Get current date
def get_current_date():
return datetime.now()
# Get current time
def get_current_time():
return datetime.now().time()
# Get current date in a specific timezone
from pytz import timezone
def get_current_date_in_timezone(timezone_str='UTC'):
tz = timezone(timezone_str)
return datetime.now(tz)
# Convert string to datetime
def string_to_date(date_string, format='%Y-%m-%d'):
return datetime.strptime(date_string, format)
# Convert string to datetime
def string_to_datetime(datetime_string, format='%Y-%m-%d %H:%M:%S'):
return datetime.strptime(datetime_string, format)
# Get difference between two dates
def get_date_difference(date1, date2):
return abs(date2 - date1)
# Get difference between two datetimes
def get_datetime_difference(datetime1, datetime2):
return abs(datetime2 - datetime1)
# Add days to a given date
def add_days_to_date(date, days):
return date + timedelta(days=days)
# Subtract days from a given date
def subtract_days_from_date(date, days):
return date - timedelta(days=days)
# Check if a year is a leap year
def is_leap_year(year):
return (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0)
# Get the last day of a given month and year
def last_day_of_month(year, month):
if month == 12:
year += 1
month = 1
else:
month += 1
return datetime(year, month, 1) - timedelta(days=1)
# Check if a date is within a specific range
def is_date_within_range(date, start_date, end_date):
return start_date <= date <= end_date

15
goodtimes/renderers.py Normal file
View File

@@ -0,0 +1,15 @@
from rest_framework import renderers
import json
class UserRenderer(renderers.JSONRenderer):
charset = "utf-8"
def render(self, data, accepted_media_type=None, renderer_context=None):
response = ""
if "ErrorDetail" in str(data):
response = json.dumps({"errors": data})
else:
response = json.dumps(data)
return response

849
goodtimes/services.py Normal file
View File

@@ -0,0 +1,849 @@
import random
from django.conf import settings
from django.core.files.uploadedfile import UploadedFile
from django.core.mail import EmailMessage
from django.utils.html import strip_tags
from django.template.loader import render_to_string
from django.shortcuts import get_object_or_404
from smtplib import SMTPException
from accounts.models import IAmPrincipal, IAmPrincipalOtp, IAmPrincipalType
from manage_wallets.models import Wallet, Transaction
from goodtimes.utils import CapacityError, RandomGenerator
from manage_events.models import Event, EventPrincipalInteraction
# from twilio.rest import Client
from django.db.models import Q, Count
import phonenumbers
from decimal import Decimal
from django.db.models import Subquery, OuterRef, F, Sum, Window
from django.db import transaction
from datetime import timedelta, time, datetime
from django.utils import timezone
import logging
logger = logging.getLogger(__name__)
class EmailService:
email = None
body = None
subject = None
to = None
from_email = None
content_subtype = "html"
def __init__(self, subject=None, to=None, from_email=None):
self.subject = subject
# self.to = (to,)
self.to = to if isinstance(to, list) else [to]
self.from_email = from_email
def set_to(self, to):
# self.to = to
self.to = to if isinstance(to, list) else [to]
def set_subject(self, subject):
self.subject = subject
def set_from_email(self, from_email):
self.from_email = from_email
def set_text_body(self, body):
self.body = strip_tags(body)
def set_html_body(self, html_body):
self.body = html_body
def load_template(self, path=None, context={}):
if path is None:
raise Exception("Email temaplate path is not provided.")
self.content_subtype = "html"
html_body = render_to_string(path, context=context)
self.body = html_body
def attach(self, file_path):
self.email.attach_file(file_path)
def send(self):
try:
self.email = EmailMessage(
subject=self.subject,
body=self.body,
to=self.to,
from_email=self.from_email,
)
self.email.content_subtype = self.content_subtype
self.email.send()
except SMTPException as e:
logger.error(str(e))
class SMSError(Exception):
def __init__(self, message, payload=None):
self.message = message
self.payload = payload
def __str__(self):
return str(self.message)
class SMSService:
def send(self, to: list, text: str):
"""
Sends text sms to the given user(s).
Parameters:
to (list): list of phone numbers
text (str): a text message.
Return:
True or False
"""
# if settings.TESTING_ENV:
# logger.info(f"TESTING ENV SMS LOG : {text}")
# return True
account_sid = settings.TWILIO_ACCOUNT_SID
auth_token = settings.TWILIO_AUTH_TOKEN
my_twilio_number = settings.MY_TWILIO_NUMBER
client = Client(account_sid, auth_token)
try:
for number in to:
logger.info("SENDING SMS TO " + str(number))
message = client.messages.create(
from_=my_twilio_number, body=text, to=number
)
except Exception as e:
logger.error(str(e))
raise SMSError(message=str(e))
def create_otp(self, principal: IAmPrincipal, opt_purpose: str):
otp = IAmPrincipalOtp.objects.create(
principal=principal, otp_purpose=opt_purpose
)
otp.save()
return otp.otp_code
def send_otp(self, principal: IAmPrincipal, otp_purpose: str):
"""
Sends otp to the given user.
Parameters:
user (User): User object
otp_purpose (str) : a text that describe otp purpose
Return:
True or False
"""
if not isinstance(principal, IAmPrincipal):
raise Exception(
f"parameter 'principal' required type of User object, Given {type(principal)} type object"
)
otp_code = self.create_otp(principal=principal, otp_purpose=otp_purpose)
# below working will change as it is temporary purpose
body = f"Your Nifty11 OTP is {otp_code}."
print(body)
phone_numbers = []
try:
parsed_number = phonenumbers.parse(str(principal.phone_no), None)
if phonenumbers.is_valid_number(parsed_number):
formatted_number = phonenumbers.format_number(
parsed_number, phonenumbers.PhoneNumberFormat.E164
)
phone_numbers.append(formatted_number)
else:
raise ValueError("Invalid phone number")
except Exception as e:
logger.warning(f"{e}")
raise ValueError("Invalid phone number")
if not phone_numbers:
raise ValueError("Invalid phone number")
print(f"phone number {type(phone_numbers)} {phone_numbers}")
# self.send(phone_numbers, body)
return otp_code
# Handling Transactions and Wallet Service
class WalletManager:
def __init__(self, principal, principal_type):
self.principal = principal
self.principal_type = get_object_or_404(IAmPrincipalType, name=principal_type)
self.wallet, created = Wallet.objects.get_or_create(principal=principal)
def _update_balance(self, amount, field_name):
wallet_field = getattr(self.wallet, field_name)
wallet_field += amount
setattr(self.wallet, field_name, wallet_field)
def _create_transaction(self, transaction_type, transaction_status, amount):
return Transaction.objects.create(
principal=self.principal,
principal_type=self.principal_type,
transaction_type=transaction_type,
transaction_status=transaction_status,
amount=amount,
)
def _initiate_transaction_deposit(self, amount):
return self._create_transaction("dp", "in", amount)
@transaction.atomic
def deposit(self, amount, field_name):
# amount_decimal = Decimal(amount) / Decimal(100) #if the payment gateway deals in fils
amount_decimal = Decimal(amount)
self._update_balance(amount_decimal, field_name)
transaction = self._create_transaction("dp", "sc", amount_decimal)
self.wallet.save()
return transaction
@transaction.atomic
def withdraw(self, amount, field_name):
# amount_decimal = Decimal(amount) / Decimal(100) #if the payment gateway deals in fils
amount_decimal = Decimal(amount)
if getattr(self.wallet, field_name) >= amount_decimal:
print(True)
self._update_balance(
-amount_decimal, field_name
) # Subtract from the specified field
transaction = self._create_transaction("wd", "sc", amount_decimal)
self.wallet.save()
return transaction
else:
raise Exception(f"Insufficient funds in {field_name}")
class InteractionCalculator:
def __init__(self, event):
self.event = event
def get_going_percentage(self):
if self.event.venue_capacity > 0:
going_count = EventPrincipalInteraction.objects.filter(
event=self.event, status="going"
).count()
return (going_count / self.event.venue_capacity) * 100
else:
# Raise an exception if venue_capacity is 0
raise CapacityError()
def get_interested_percentage(self):
if self.event.venue_capacity > 0:
interested_count = EventPrincipalInteraction.objects.filter(
event=self.event, status="interested"
).count()
return (interested_count / self.event.venue_capacity) * 100
else:
# Raise an exception if venue_capacity is 0
raise CapacityError()
def calculate(self):
going_percentage = self.get_going_percentage()
interested_percentage = self.get_interested_percentage()
interaction = {"going": "None", "interested": "None"}
if going_percentage >= 70:
interaction["going"] = "Exclusive"
elif going_percentage >= 50:
interaction["going"] = "Fire"
if interested_percentage >= 90:
interaction["interested"] = "Red Flames"
elif interested_percentage >= 70:
interaction["interested"] = "Orange Flames"
elif interested_percentage >= 50:
interaction["interested"] = "Blue Flames"
return interaction
class DashboardGamesHelper:
def __init__(self, game_type, game_index, live_time_end):
self.games = Game.objects.filter(deleted=False, draft=False)
self.game_type = game_type
self.game_index = game_index
self.live_time_end = live_time_end
self.current_datetime = timezone.localtime(timezone.now())
self.current_date = self.current_datetime.date()
self.current_time = self.current_datetime.time()
# self.current_datetime = datetime(2023, 12, 19, 1, 29)
# self.current_date = self.current_datetime.date()
# self.current_time = self.current_datetime.time()
def get_live_date(self, game_type):
if game_type == "Nifty":
return self._get_live_date_nifty()
elif game_type in ["Dow", "Nasdaq"]:
return self._get_live_date_dow_nasdaq()
else:
# Handle unknown game_type or raise an exception
return None
def get_live_date_new(self, game_index):
if game_index == "Nifty":
print("Nifty Entered")
return self._is_cutoff_passed_nifty()
elif game_index in ["Dow", "Nasdaq"]:
print("Dow, Nasdaq Entered")
return self._is_cutoff_passed_nasdaq_dow()
else:
# Handle unknown game_type or raise an exception
return None
def _get_updated_date(self, max_days, updated_date):
days_counter = 0
while days_counter < max_days:
updated_date += timedelta(days=1)
print("updated_date: ", updated_date)
queryset = self.games.filter(
deleted=False,
draft=False,
open_pool=True,
game_type=self.game_type,
game_index=StockIndexType.objects.filter(title=self.game_index).first(),
live_date=updated_date,
)
print("services.py queryset: ", queryset)
if queryset.exists():
print("queryset: ", queryset)
return queryset
days_counter += 1
return None
def _is_cutoff_passed_nifty(self):
if self.current_time >= self.live_time_end:
return self._get_updated_date(10, updated_date=self.current_date)
else:
selected_game = self.games.filter(
deleted=False,
draft=False,
open_pool=True,
game_type=self.game_type,
game_index=StockIndexType.objects.filter(title=self.game_index).first(),
live_date=self.current_date,
)
print("selected_game: ", selected_game)
if selected_game:
return selected_game
return self._get_updated_date(10, updated_date=self.current_date)
def _is_cutoff_passed_nasdaq_dow(self):
if self.current_time >= self.live_time_end:
return self._get_updated_date(
10, updated_date=self.current_date - timedelta(days=1)
)
else:
selected_game = self.games.filter(
deleted=False,
draft=False,
open_pool=True,
game_type=self.game_type,
game_index=StockIndexType.objects.filter(title=self.game_index).first(),
live_date=self.current_date,
)
print("selected_game: ", selected_game)
if selected_game:
return selected_game
return self._get_updated_date(
10, updated_date=self.current_date - timedelta(days=1)
)
def _get_live_date_nifty(self):
next_day = self.current_date + timedelta(days=1)
if (
self.current_date.weekday() == 4 and self.current_time >= self.live_time_end
) or self.current_date.weekday() in {5, 6}:
# Find the next Monday
days_until_monday = 7 - self.current_date.weekday()
live_date = self.current_date + timedelta(days=days_until_monday)
else:
live_date = (
self.current_date
if self.current_time < self.live_time_end
else next_day
)
return live_date
def _get_live_date_dow_nasdaq(self):
if (
self.current_date.weekday() == 5 and self.current_time >= self.live_time_end
) or self.current_date.weekday() in {6, 7}:
# Find the next Monday
days_until_monday = 7 - self.current_date.weekday()
live_date = self.current_date + timedelta(days=days_until_monday)
else:
live_date = (
self.current_date - timedelta(days=1)
if self.current_time < self.live_time_end
else self.current_date
)
return live_date
class GameStatusManager:
def __init__(self):
self.current_date = timezone.localtime(
timezone.now()
).date() # More descriptive variable name
def filter_eligible_games(self, game_index):
eligible_games = (
Game.objects.filter(
status__in=("Upcoming", "Participate"),
# status=("Live"),
live_date=self.current_date,
deleted=False,
draft=False,
game_index=self.get_stock_index(game_index),
# min_no_of_team__isnull=False # Ensure min_no_of_team is set
)
# .values("min_no_of_team", "game_team") # Include min_no_of_team
.annotate(teams_joined=Count("game_team")).filter(
teams_joined__gte=F("min_no_of_team")
)
)
print("eligible_games: ", eligible_games)
eligible_games.update(status=Game.LIVE)
def filter_live_games_by_index(self, game_index):
live_games = Game.objects.filter(
status=Game.LIVE, # Filter only live games
game_index=self.get_stock_index(game_index),
deleted=False,
draft=False,
)
return live_games
def get_stock_index(self, game_index):
try:
return StockIndexType.objects.get(title=game_index)
except StockIndexType.DoesNotExist:
raise ValueError(f"StockIndexType with title '{game_index}' not found.")
def manage_game_status_after_live(self, game):
game.status = "Completed"
game.save()
class ScoreCalculationManager:
def __init__(self):
self.current_datetime = timezone.localtime(timezone.now())
def get_completed_games(self, game_index):
try:
index = StockIndexType.objects.get(title=game_index)
except StockIndexType.DoesNotExist:
raise ValueError(f"StockIndexType with title '{game_index}' not found.")
return Game.objects.filter(
status="Completed",
game_index=index,
)
def get_live_games(self, game_index):
try:
index = StockIndexType.objects.get(title=game_index)
except StockIndexType.DoesNotExist:
raise ValueError(f"StockIndexType with title '{game_index}' not found.")
return Game.objects.filter(
status="Live",
game_index=index,
)
def calculate_team_score(self, game):
"""Calculates team scores from team stocks' stock_scores."""
# Subquery to get the total stock score for each team
total_stock_score_subquery = (
TeamStock.objects.filter(team=OuterRef("pk"))
.values("team")
.annotate(total_stock_score=Sum("stock_score"))
.values("total_stock_score")[:1]
)
# Update team scores using the subquery
Team.objects.filter(game=game).annotate(
total_stock_score=Subquery(total_stock_score_subquery)
).update(team_score=F("total_stock_score"))
# call this function to update the last closing before 10 min of game live time start
def update_closing(self, game, close_price_field_name):
"""Updates the closing prices for team stocks in a given game."""
try:
closing_field = TeamStock._meta.get_field(close_price_field_name)
except Exception as e:
raise ValueError(
f"Field '{close_price_field_name}' not found in StockPrice Model."
)
with transaction.atomic():
if close_price_field_name == "current_closing":
# Generate a random adjustment between -100 and 100
random_adjustment = random.randint(-100, 100)
# Use a separate queryset for random adjustment
random_adjusted_prices = (
StockPrice.objects.filter(stock=OuterRef("stock"))
.order_by("-timestamp")
.values("close_price")[:1]
.annotate(adjusted_price=F("close_price") + random_adjustment)
)
# Apply random adjustment only for 'current_closing'
team_stocks = TeamStock.objects.filter(team__game=game)
team_stocks.update(
**{
close_price_field_name: Subquery(
random_adjusted_prices.values("adjusted_price")
)
}
)
else:
# Normal update for other fields
latest_prices = (
StockPrice.objects.filter(stock=OuterRef("stock"))
.order_by("-timestamp")
.values("close_price")[:1]
)
team_stocks = TeamStock.objects.filter(team__game=game)
team_stocks.update(**{close_price_field_name: Subquery(latest_prices)})
def calculate_team_stock_scores(self, game):
"""Call this function to update the stock score and current closing percentage
after 1 min of game live time end."""
team_stocks = TeamStock.objects.filter(team__game=game)
for team_stock in team_stocks:
(
team_stock.stock_score,
team_stock.current_closing_percentage,
) = self.calculate_stock_score(
team_stock.position,
team_stock.last_closing,
team_stock.current_closing,
team_stock.operation,
team_stock.quantity,
)
team_stock.save()
def calculate_stock_score(
self, position, last_closing, current_closing, operation, quantity
):
"""Calculates the stock score based on position and operation."""
last_closing = Decimal(str(last_closing)) # Convert to Decimal
current_closing = Decimal(str(current_closing)) # Convert to Decimal
quantity = Decimal(str(quantity))
if operation == "Buy":
stock_score = (
((current_closing - last_closing) / last_closing) * 100 * quantity
)
elif operation == "Sell":
stock_score = (
((last_closing - current_closing) / last_closing) * 100 * quantity
)
else:
raise ValueError("Invalid operation")
if position == "Captain":
stock_score *= Decimal("2")
elif position == "Vice Captain":
stock_score *= Decimal("1.5")
current_closing_percentage = current_closing * 100 / last_closing
return stock_score, current_closing_percentage
def prepare_leaderboard(self, game):
"""Prepares a leaderboard based on specified winner type.
Args:
game (Game): The game for which to create the leaderboard.
winner_type (str): The type of winner determination (One Winner, Three Winner, fifty Percent).
Returns:
list: A list of winning teams, or an empty list if no winners.
"""
# game = Game.objects.get(pk=game)
# teams = Team.objects.filter(game=game).order_by("-team_score", "id")
teams = Team.objects.filter(game=game).order_by("team_rank")
if game.winner_type == Game.ONE_DIST:
return teams[:1]
elif game.winner_type == Game.THREE_DIST:
return teams[:3]
elif game.winner_type == Game.FIFTY_DIST:
winners_count = round(teams.count() / 2)
print("winners_count: ", winners_count)
return teams[:winners_count]
elif game.winner_type == Game.BASIC_DIST:
winners_count = round(teams.count() / 2)
return teams[:winners_count]
elif game.winner_type == Game.PRO_DIST:
winners_count = round(teams.count() / 2)
return teams[:winners_count]
else:
raise ValueError(f"Invalid winner_type: {game.winner_type}")
def calculate_team_rank(self, game):
"""Calculate and update the rank for each team based on team_score."""
print("Calculate and update the rank for each team based on team_score.")
teams = Team.objects.filter(game=game).order_by("-team_score", "id")
# Iterate through the teams and update the team_rank field
for i, team in enumerate(teams, start=1):
team.team_rank = i
team.save()
def calculate_prize_amount(self, game):
"""Calculates and Returns the prize amount for a game.
Args:
game (Game): The game for which to distribute prizes.
Returns:
float: The prize amount awarded to the winner.
Raises:
ValueError: If there are no teams in the game or invalid prize parameters.
"""
team_count = Team.objects.filter(game=game).count()
if team_count == 0:
raise ValueError("Cannot distribute prizes with no teams in the game.")
try:
gross_amount = team_count * game.entry_fee
prize_amount = (team_count * game.entry_fee) - (
team_count * game.entry_fee * game.commission_percentage / 100
)
game.gross_amount = gross_amount
game.distribution_amount = prize_amount
game.save()
return prize_amount
except TypeError:
raise ValueError("Invalid prize parameters.")
class CreatePrivateGamesHelper:
def __init__(self, game_index):
self.current_datetime = timezone.localtime(timezone.now())
self.game_index = game_index
self.live_time_start = (
StockIndexType.objects.filter(title=game_index).first().live_time_start
)
self.nifty_holiday_2024 = [
"26-01-2024",
"08-03-2024",
"25-03-2024",
"29-03-2024",
"11-04-2024",
"17-04-2024",
"01-05-2024",
"17-06-2024",
"17-07-2024",
"15-08-2024",
"02-10-2024",
"01-11-2024",
"15-11-2024",
"25-12-2024",
]
self.nasdaq_dow_holiday_2024 = [
"01-01-2024",
"15-01-2024",
"19-02-2024",
"29-03-2024",
"27-05-2024",
"19-06-2024",
"03-07-2024",
"04-07-2024",
"02-09-2024",
"28-11-2024",
"29-11-2024",
"24-12-2024",
"25-12-2024",
]
def is_cutoff_passed(self):
time_start = datetime.combine(
self.current_datetime.date(), self.live_time_start
)
cutoff_datetime = timezone.make_aware(time_start) - timedelta(
minutes=15
) # Make cutoff_datetime timezone-aware
print("cutoff_datetime: ", cutoff_datetime)
return self.current_datetime >= cutoff_datetime
def find_valid_date(self):
"""
Finds a date that is not a Saturday, Sunday, or in the relevant holiday list, starting from today or tomorrow.
"""
# Start with today or tomorrow based on cutoff time
target_date = (
datetime.today()
if not self.is_cutoff_passed()
else datetime.today() + timedelta(days=1)
)
counter, max_counter = 0, 7 # Limit the search to a maximum of 7 days
while counter < max_counter:
weekday = target_date.weekday()
date_str = target_date.strftime("%Y-%m-%d")
# Select the correct holiday list based on game_index
holiday_list = (
self.nifty_holiday_2024
if self.game_index == "Nifty"
else self.nasdaq_dow_holiday_2024
)
# Increment the counter for each iteration
counter += 1
# Check if the date is a Saturday, Sunday, or a holiday
if weekday in (5, 6) or date_str in holiday_list:
target_date += timedelta(days=1)
else:
# A valid date is found
break
return target_date
class PrizeDistributionChecker:
def __init__(self, game):
self.game = game
self.team_count = self.get_team_count()
self.gross_amount = 0
self.prize_amount = 0
def get_team_count(self):
"""Retrieve the count of teams participating in the game."""
return Team.objects.filter(game=self.game).count()
def calculate_prize_amount(self):
"""Calculate and set the prize amount for the game.
Raises:
ValueError: If there are no teams in the game or invalid prize parameters.
"""
if self.team_count == 0:
raise ValueError("Cannot distribute prizes with no teams in the game.")
try:
self.gross_amount = self.team_count * self.game.entry_fee
self.prize_amount = self.gross_amount - (
self.gross_amount * self.game.commission_percentage / 100
)
self.update_game()
except TypeError:
raise ValueError("Invalid prize parameters.")
return self.prize_amount
def update_game(self):
"""Update the game instance with calculated amounts and persist changes."""
self.game.gross_amount = self.gross_amount
self.game.distribution_amount = self.prize_amount
self.game.save()
class BasicWinnerCalculator:
"""
Calculates the prize money for a given position in a competition based on the number of teams, ticket amount, gross amount, and price amount.
Attributes:
fixed_winners_share (list): A list of fixed winners' share percentages for positions 5 to 10.
fixed_teams_percentage (list): A list of fixed teams percentages for positions 5 to 10.
first_four_teams (list): A list of fixed number of teams for positions 1 to 4.
first_four_share (list): A list of fixed winners' share percentages for positions 1 to 4.
Methods:
calculate_prize_money(self, position, number_of_teams, ticket_amount, gross_amount, price_amount):
Calculates the prize money, winners' share, and number of teams for the given position.
"""
def __init__(self):
self.fixed_winners_share = [2.5, 2, 1.5, 1.25, 1, 0.5]
self.fixed_teams_percentage = [1, 1, 5, 10, 20, 35]
self.first_four_teams = [1, 1, 1, 3]
self.first_four_percentage = [1, 0.75, 0.5, 0.75]
def calculate_prize_money(self, position, number_of_teams, ticket_amount):
"""
Calculates the prize money, winners' share, and number of teams for the given position.
"""
if not 1 <= position <= 10:
return None, None, None
# For positions 5-10, use the fixed winners' share and teams percentage
if position >= 5:
index = position - 5
winners_share = self.fixed_winners_share[index] * ticket_amount
percentage_of_teams = self.fixed_teams_percentage[index] / 100
rounded_teams = round(number_of_teams * percentage_of_teams)
# For positions 1-4, calculate based on first four configurations
else:
index = position - 1
winners_share = number_of_teams * (
self.first_four_percentage[index] / self.first_four_teams[index]
)
rounded_teams = self.first_four_teams[index]
prize_money = winners_share * rounded_teams
return prize_money, winners_share, rounded_teams
def calculate_all_prizes(self, number_of_teams, ticket_amount):
all_prizes = []
cumulative_rounding_error = 0.0
for position in range(1, 11):
prize, rounding_error = self.calculate_prize_for_position(
position, number_of_teams, ticket_amount
)
all_prizes.append(prize)
cumulative_rounding_error += rounding_error
self.adjust_prizes(all_prizes, cumulative_rounding_error)
return all_prizes
def calculate_prize_for_position(self, position, number_of_teams, ticket_amount):
# Your prize calculation logic here, returning both the rounded prize and the rounding error
# return prize, rounding_error
pass
def adjust_prizes(self, all_prizes, rounding_error):
# Logic to adjust the prizes based on the cumulative rounding error
pass

123
goodtimes/settings.py Normal file
View File

@@ -0,0 +1,123 @@
"""
Django settings for goodtimes project.
Generated by 'django-admin startproject' using Django 5.0.2.
For more information on this file, see
https://docs.djangoproject.com/en/5.0/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/5.0/ref/settings/
"""
from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = "django-insecure-6c@e6u4h677duuoa2v3*m1ke&f+txh7s-q27e#=j_=a+9l6txi"
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
]
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
ROOT_URLCONF = "goodtimes.urls"
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.debug",
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
],
},
},
]
WSGI_APPLICATION = "goodtimes.wsgi.application"
# Database
# https://docs.djangoproject.com/en/5.0/ref/settings/#databases
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": BASE_DIR / "db.sqlite3",
}
}
# Password validation
# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
},
{
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
},
{
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
},
{
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
},
]
# Internationalization
# https://docs.djangoproject.com/en/5.0/topics/i18n/
LANGUAGE_CODE = "en-us"
TIME_ZONE = "UTC"
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.0/howto/static-files/
STATIC_URL = "static/"
# Default primary key field type
# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"

View File

@@ -0,0 +1,24 @@
import environ
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent.parent
env = environ.Env()
READ_DOT_ENV_FILE = env.bool("DJANGO_READ_DOT_ENV_FILE", default=True)
if READ_DOT_ENV_FILE:
# OS environment variables take precedence over variables from .env
env.read_env(str(BASE_DIR / ".env"))
# Access the "ENV_NAME" environment variable using env
env_name = env("ENV_NAME")
if env_name == 'Production':
from .production import * # noqa
elif env_name == 'Staging':
from .staging import * # noqa
elif env_name == 'Development':
from .development import * # noqa
else:
raise ValueError("Invalid or missing ENV_NAME environment variable")

350
goodtimes/settings/base.py Normal file
View File

@@ -0,0 +1,350 @@
"""
Django settings for nifty11_project project.
Generated by 'django-admin startproject' using Django 4.2.4.
For more information on this file, see
https://docs.djangoproject.com/en/4.2/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.2/ref/settings/
"""
from pathlib import Path
import os
from django.contrib.messages import constants as messages
# from decouple import config
import colorlog
import datetime
import environ
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent.parent
env = environ.Env()
READ_DOT_ENV_FILE = env.bool("DJANGO_READ_DOT_ENV_FILE", default=True)
if READ_DOT_ENV_FILE:
# OS environment variables take precedence over variables from .env
env.read_env(str(BASE_DIR / ".env"))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
# SECRET_KEY = 'django-insecure-=7*xxmlhr=01o5^qfbtf_(b6b4udf5^!6g(7jt9*pxf4ng(k58'
SECRET_KEY = env.str("SECRET_KEY")
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = env.bool("DJANGO_DEBUG", True)
# DEBUG = True
# Application definition
DJANGO_APPS = [
"daphne",
"channels",
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
]
LOCAL_APPS = [
"dashboard",
"accounts",
"manage_wallets",
"manage_subscriptions",
"manage_events",
"manage_referrals",
"manage_cms",
"manage_communications", # for contact us, and feedback
"chat",
]
THIRD_PARTY_APPS = [
"rest_framework",
"widget_tweaks",
"rest_framework_simplejwt",
"phonenumber_field",
"taggit",
"django_quill",
"corsheaders",
"allauth",
"allauth.account",
"allauth.socialaccount",
"allauth.socialaccount.providers.apple",
"allauth.socialaccount.providers.google",
# "django_crontab",
# "django_celery_results",
# "django_celery_beat",
]
# https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS
MIDDLEWARE = [
"corsheaders.middleware.CorsMiddleware",
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"allauth.account.middleware.AccountMiddleware",
]
ROOT_URLCONF = "goodtimes.urls"
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [BASE_DIR.joinpath("templates")],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.debug",
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
"accounts.context_processors.resource_action_constants", # resource action context processor
],
},
},
]
# WSGI_APPLICATION = "goodtimes.wsgi.application"
ASGI_APPLICATION = "goodtimes.asgi.application"
# Database
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
DATABASES = {
"default": {
"ENGINE": "django.db.backends.mysql",
"NAME": env.str("DB_DATABASE"),
"HOST": env.str("DB_HOST"),
"USER": env.str("DB_USERNAME"),
"PASSWORD": env.str("DB_PASSWORD"),
"PORT": env.str("DB_PORT"),
}
}
DATABASES["default"]["ATOMIC_REQUESTS"] = True
# Password validation
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
},
{
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
},
{
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
},
{
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
},
]
APPEND_SLASH = True
LOGIN_REDIRECT_URL = "/dashboard/main-dashboard/"
LOGIN_URL = "/account/login/"
LOGOUT_REDIRECT_URL = "/account/login/"
# https://docs.djangoproject.com/en/4.2/topics/auth/customizing/#substituting-a-custom-user-model
AUTH_USER_MODEL = "accounts.IAmPrincipal"
# https://docs.djangoproject.com/en/4.2/topics/auth/customizing/
AUTHENTICATION_BACKENDS = [
# 'accounts.backend.EmailBackend',
"django.contrib.auth.backends.ModelBackend",
"allauth.account.auth_backends.AuthenticationBackend",
]
# rest framework permission and authentication settings
# https://www.django-rest-framework.org/api-guide/permissions/#setting-the-permission-policy
REST_FRAMEWORK = {
# Use Django's standard `django.contrib.auth` permissions,
# or allow read-only access for unauthenticated users.
"DEFAULT_PERMISSION_CLASSES": [
"rest_framework.permissions.IsAuthenticated",
],
"DEFAULT_AUTHENTICATION_CLASSES": (
"rest_framework_simplejwt.authentication.JWTAuthentication",
# "accounts.api.authenticate.CustomAuthentication",
),
}
# Internationalization
# https://docs.djangoproject.com/en/4.2/topics/i18n/
LANGUAGE_CODE = "en-us"
TIME_ZONE = "Asia/Kolkata"
USE_I18N = True
USE_L10N = True
USE_TZ = True
SHORT_DATETIME_FORMAT = "d-m-Y H:i:s"
SHORT_DATE_FORMAT = "d-m-Y"
TIME_FORMAT = "H:i p"
# otp expire time limit
OTP_EXPIRE_TIME = 10 # mins
# Default primary key field type
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
MESSAGE_TAGS = {
messages.DEBUG: "alert-info",
messages.INFO: "alert-info",
messages.SUCCESS: "alert-success",
messages.WARNING: "alert-warning",
messages.ERROR: "alert-danger",
}
# smtp email settings
# https://docs.djangoproject.com/en/4.2/topics/email/#smtp-backend
EMAIL_BACKEND = env.str(
"EMAIL_BACKEND", default="django.core.mail.backends.smtp.EmailBackend"
)
EMAIL_HOST = env.str("EMAIL_HOST")
EMAIL_HOST_USER = env.str("EMAIL_HOST_USER")
EMAIL_HOST_PASSWORD = env.str("EMAIL_HOST_PASSWORD")
EMAIL_PORT = env.str("EMAIL_PORT")
EMAIL_USE_TLS = True
# LOGGING
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/4.2/topics/logging/#logging
# https://docs.djangoproject.com/en/dev/ref/settings/#logging
# See https://docs.djangoproject.com/en/dev/topics/logging for
# more details on how to customize your logging configuration.
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"verbose": {
"()": colorlog.ColoredFormatter,
"format": "%(cyan)s%(asctime)s%(reset)s | %(red)s[%(levelname)8s]%(reset)s | [ %(yellow)s%(name)s.%(module)s:%(white)s%(lineno)d%(reset)s - %(green)s%(funcName)10s()%(reset)s ] --> %(message)s",
"datefmt": "%Y-%m-%d %H:%M:%S",
"log_colors": {
"DEBUG": "white",
"INFO": "green",
"WARNING": "yellow",
"ERROR": "red",
"CRITICAL": "bold_red",
},
"secondary_log_colors": {},
"style": "%",
}
},
"handlers": {
"console": {
"level": "DEBUG",
"class": "logging.StreamHandler",
"formatter": "verbose",
}
},
"root": {"level": "INFO", "handlers": ["console"]},
}
# jwt configuration
# https://django-rest-framework-simplejwt.readthedocs.io/en/latest/settings.html#settings
SIMPLE_JWT = {
"ACCESS_TOKEN_LIFETIME": datetime.timedelta(days=10),
"REFRESH_TOKEN_LIFETIME": datetime.timedelta(days=15),
"ROTATE_REFRESH_TOKENS": False,
"BLACKLIST_AFTER_ROTATION": False,
"UPDATE_LAST_LOGIN": False,
"ALGORITHM": "HS256",
"SIGNING_KEY": SECRET_KEY,
"VERIFYING_KEY": "",
"AUDIENCE": None,
"ISSUER": None,
"JSON_ENCODER": None,
"JWK_URL": None,
"LEEWAY": 0,
"AUTH_HEADER_TYPES": ("Bearer",),
"AUTH_HEADER_NAME": "HTTP_AUTHORIZATION",
"USER_ID_FIELD": "id",
"USER_ID_CLAIM": "user_id",
"USER_AUTHENTICATION_RULE": "rest_framework_simplejwt.authentication.default_user_authentication_rule",
"AUTH_TOKEN_CLASSES": ("rest_framework_simplejwt.tokens.AccessToken",),
"TOKEN_TYPE_CLAIM": "token_type",
"TOKEN_USER_CLASS": "rest_framework_simplejwt.models.TokenUser",
"JTI_CLAIM": "jti",
}
STRIPE_SECRET_KEY = env.str("STRIPE_SECRET_KEY")
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": {
# "hosts": [("192.168.29.219", 6379)],
"hosts": [("127.0.0.1", 6379)],
},
},
}
# Celery Setting
# CELERY_TIMEZONE = "Asia/Kolkata"
# CELERY_BROKER_URL = "redis://localhost:6379"
# CELERY_RESULT_BACKEND = "redis://localhost:6379"
# CELERY_RESULT_BACKEND = "django-db"
# CELERY_RESULT_EXTENDED = True
# CELERY_BEAT_SCHEDULER = "django_celery_beat.schedulers:DatabaseScheduler"
WEBSOCKET_TIMEOUT = 30
CRONJOBS = [
# ("0 9 * * 1-5", "manage_games.cron.update_game_status_live"),
]
GOOGLE_OAUTH2_CLIENT_ID = env.str("DJANGO_GOOGLE_OAUTH2_CLIENT_ID", default="")
GOOGLE_OAUTH2_CLIENT_SECRET = env.str("DJANGO_GOOGLE_OAUTH2_CLIENT_SECRET", default="")
GOOGLE_OAUTH2_PROJECT_ID = env.str("DJANGO_GOOGLE_OAUTH2_PROJECT_ID", default="")
GOOGLE_MAPS_API_KEY = env.str("GOOGLE_MAPS_API_KEY")
SOCIALACCOUNT_PROVIDERS = {
"google": {
"APP": {
"client_id": GOOGLE_OAUTH2_CLIENT_ID, # replace me
"secret": GOOGLE_OAUTH2_CLIENT_SECRET, # replace me
"key": "", # leave empty
},
"SCOPE": [
"profile",
"email",
],
"AUTH_PARAMS": {
"access_type": "online",
},
"VERIFIED_EMAIL": True,
},
}

View File

@@ -0,0 +1,55 @@
from .base import * # noqa
import os
DEBUG = True
ALLOWED_HOSTS = [
"*",
"192.168.1.24",
"192.168.1.13",
"192.168.1.11",
"192.168.1.25",
"192.168.1.26",
"192.168.29.211",
"ngrok.io",
]
# CORS
CORS_ALLOWED_ORIGINS = [
"http://127.0.0.1:3000",
"http://localhost:3000",
]
CORS_ORIGIN_ALLOW_ALL = True
CORS_ORIGIN_WHITELIST = ("http://localhost:3000",)
if DEBUG:
MIDDLEWARE += [
"debug_toolbar.middleware.DebugToolbarMiddleware",
]
INSTALLED_APPS += [
"debug_toolbar",
"django_extensions",
]
INTERNAL_IPS = [
"127.0.0.1",
]
BASE_DOMAIN = "http://192.168.29.219:8000"
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.2/howto/static-files/
MEDIA_URL = "/media/"
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
# STATIC_ROOT = os.path.join(BASE_DIR, "static")
STATIC_URL = "/static/"
STATICFILES_DIRS = [BASE_DIR.joinpath("static")]

View File

@@ -0,0 +1,77 @@
from .base import * # noqa
from .base import env, BASE_DIR
import os
import colorlog
from logging.handlers import TimedRotatingFileHandler
DEBUG = False
ALLOWED_HOSTS = ["goodtimes.betadelivery.com"]
LOGGING_DIR = os.path.join(
BASE_DIR, "logs"
) # Define the directory where log files will be stored
# Ensure the directory exists; create it if it doesn't
if not os.path.exists(LOGGING_DIR):
os.makedirs(LOGGING_DIR)
LOGGING_LEVEL = env.str(
"LOG_LEVEL", "INFO"
) # Set your desired log level (e.g., DEBUG, INFO, WARNING, ERROR)
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"verbose": {
"()": colorlog.ColoredFormatter,
"format": "%(cyan)s%(asctime)s%(reset)s | %(red)s[%(levelname)8s]%(reset)s | [ %(yellow)s%(name)s.%(module)s:%(white)s%(lineno)d%(reset)s - %(green)s%(funcName)10s()%(reset)s ] --> %(message)s",
"datefmt": "%Y-%m-%d %H:%M:%S",
"log_colors": {
"DEBUG": "white",
"INFO": "green",
"WARNING": "yellow",
"ERROR": "red",
"CRITICAL": "bold_red",
},
"secondary_log_colors": {},
"style": "%",
}
},
"handlers": {
"logfile": {
"level": LOGGING_LEVEL,
"class": "logging.handlers.RotatingFileHandler",
"filename": os.path.join(LOGGING_DIR, "godtimes_pro_error.log"),
'maxBytes': 5242880, # 5*1024*1024 bytes (5MB)
"backupCount": 10, # Number of log files to keep (15 days' worth of logs)
"formatter": "verbose",
},
},
"loggers": {
"django": {
"handlers": ["logfile"],
"level": LOGGING_LEVEL,
"propagate": False,
},
},
}
BASE_DOMAIN = "https://goodtimes.betadelivery.com"
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.2/howto/static-files/
MEDIA_URL = "/media/"
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
# STATIC_ROOT = os.path.join(BASE_DIR, "static")
STATIC_URL = "/static/"
STATICFILES_DIRS = [BASE_DIR.joinpath("static")]

View File

@@ -0,0 +1,75 @@
from .base import * # noqa
from .base import env, BASE_DIR
import os
import colorlog
from logging.handlers import TimedRotatingFileHandler
DEBUG = False
ALLOWED_HOSTS = ["goodtimes.betadelivery.com"]
LOGGING_DIR = os.path.join(
BASE_DIR, "logs"
) # Define the directory where log files will be stored
# Ensure the directory exists; create it if it doesn't
if not os.path.exists(LOGGING_DIR):
os.makedirs(LOGGING_DIR)
LOGGING_LEVEL = env.str(
"LOG_LEVEL", "INFO"
) # Set your desired log level (e.g., DEBUG, INFO, WARNING, ERROR) in the env file
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"verbose": {
"()": colorlog.ColoredFormatter,
"format": "%(cyan)s%(asctime)s%(reset)s | %(red)s[%(levelname)8s]%(reset)s | [ %(yellow)s%(name)s.%(module)s:%(white)s%(lineno)d%(reset)s - %(green)s%(funcName)10s()%(reset)s ] --> %(message)s",
"datefmt": "%Y-%m-%d %H:%M:%S",
"log_colors": {
"DEBUG": "white",
"INFO": "green",
"WARNING": "yellow",
"ERROR": "red",
"CRITICAL": "bold_red",
},
"secondary_log_colors": {},
"style": "%",
}
},
"handlers": {
"logfile": {
"level": LOGGING_LEVEL,
"class": "logging.handlers.RotatingFileHandler",
"filename": os.path.join(LOGGING_DIR, "goodtimes_staging_error.log"),
'maxBytes': 5242880, # 5*1024*1024 bytes (5MB)
"backupCount": 10, # Number of log files to keep (15 days' worth of logs)
"formatter": "verbose",
},
},
"loggers": {
"django": {
"handlers": ["logfile"],
"level": LOGGING_LEVEL,
"propagate": False,
},
},
}
BASE_DOMAIN = "https://goodtimes.betadelivery.com"
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.2/howto/static-files/
MEDIA_URL = "/media/"
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
# STATIC_ROOT = os.path.join(BASE_DIR, "static")
STATIC_URL = "/static/"
STATICFILES_DIRS = [BASE_DIR.joinpath("static")]

65
goodtimes/urls.py Normal file
View File

@@ -0,0 +1,65 @@
"""
URL configuration for goodtimes project.
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/5.0/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import include, path
from django.conf import settings
from django.conf.urls.static import static
from django.views.generic import RedirectView
urlpatterns = [
path("admin/", admin.site.urls),
path('accounts/', include('allauth.urls')),
path('', RedirectView.as_view(url='/account/login/'), name='root-redirect'),
path('account/', include('accounts.urls')),
path('api/account/', include("accounts.api.urls")),
path('dashboard/', include('dashboard.urls')),
# path('api/account', include("accounts.api.urls")),
path('events/', include('manage_events.urls')),
path('api/events/', include("manage_events.api.urls")),
# path('report/', include('manage_reports.urls')),
# path('api/report', include("manage_reports.api.urls")),
path('cms/', include('manage_cms.urls')),
path('api/cms/', include("manage_cms.api.urls")),
path('wallet/', include('manage_wallets.urls')),
# path('api/wallet/', include("manage_wallets.api.urls")),
path('communications/', include('manage_communications.urls')),
# path('api/communications/', include("manage_communications.api.urls")),
# path('chat/', include('chat.urls')),
# path('api/chat/', include("chat.api.urls")),
path('subscriptions/', include('manage_subscriptions.urls')),
path('api/subscriptions/', include("manage_subscriptions.api.urls")),
# path('', include('manage_notifications.urls')),
# path('api/', include("accounts.api.urls")),
]
if settings.DEBUG:
import debug_toolbar
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
urlpatterns += [path('__debug__/', include(debug_toolbar.urls))]

66
goodtimes/utils.py Normal file
View File

@@ -0,0 +1,66 @@
from rest_framework.response import Response
from rest_framework import status
import string
import random
import logging
import os
import stat
class GroupWriteRotatingFileHandler(logging.handlers.RotatingFileHandler):
def doRollover(self):
"""
Overrides base class method to make the new log file group writable.
"""
# Rotate the file first.
logging.handlers.RotatingFileHandler.doRollover(self)
# Add group write to the current permissions.
currMode = os.stat(self.baseFilename).st_mode
os.chmod(self.baseFilename, currMode | stat.S_IWGRP)
class ApiResponse:
@staticmethod
def success(message, data=None, status=status.HTTP_200_OK):
response_data = {"success": True, "status": status, "message": message}
if data is not None:
response_data["data"] = data
return Response(response_data, status=status)
@staticmethod
def error(message, errors=None, status=status.HTTP_400_BAD_REQUEST):
response_data = {"success": False, "status": status, "message": message}
if errors is not None:
response_data["errors"] = errors
return Response(response_data, status=status)
# @staticmethod
# def validation_error(errors, status_code=status.HTTP_422_UNPROCESSABLE_ENTITY):
# return ApiResponse.error("Validation error", errors, status_code)
class RandomGenerator:
@staticmethod
def number(start, end):
# import random
return random.randint(start, end)
@staticmethod
def password_reset_code():
return RandomGenerator.number(10000000, 99999999)
def random_otp():
return RandomGenerator.number(1000, 9999)
@staticmethod
def random_alphnum(length):
return "".join(
random.choice(string.ascii_uppercase + string.digits) for _ in range(length)
)
class CapacityError(Exception):
"""Exception raised for errors in the capacity of an event."""
def __init__(self, message="Venue capacity must be greater than 0"):
self.message = message
super().__init__(self.message)

16
goodtimes/wsgi.py Normal file
View File

@@ -0,0 +1,16 @@
"""
WSGI config for goodtimes project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/5.0/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "goodtimes.settings")
application = get_wsgi_application()

22
manage.py Normal file
View File

@@ -0,0 +1,22 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
"""Run administrative tasks."""
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "goodtimes.settings")
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == "__main__":
main()

0
manage_cms/__init__.py Normal file
View File

34
manage_cms/admin.py Normal file
View File

@@ -0,0 +1,34 @@
from django.contrib import admin
from . models import Organization
# Register your models here.
class OrganizationAdmin(admin.ModelAdmin):
list_display = ('id', 'title', 'contact_us_email', 'website_url')
list_filter = ('title',) # You can add more fields for filtering
search_fields = ('title', 'contact_us_email', 'website_url')
list_per_page = 20 # Number of items displayed per page in the admin list view
fieldsets = (
('Basic Information', {
'fields': ('title', 'contact_us_email', 'website_url')
}),
('Social Media', {
'fields': ('instagram_handle', 'facebook_handle', 'linkedin_handle')
}),
('Images', {
'fields': ('logo_image', 'favicon_image')
}),
('Text Fields', {
'fields': (
'about_us', 'terms_condition', 'terms_condition_user', 'terms_condition_merchant',
'privacy_policy', 'privacy_policy_user', 'privacy_policy_merchant',
'subscription_agreement', 'license_agreement_user', 'license_agreement_merchant'
)
}),
)
# readonly_fields = ('title',) # Add any other fields you want to make readonly
admin.site.register(Organization, OrganizationAdmin)

View File

@@ -0,0 +1,119 @@
import re
from rest_framework import serializers
from accounts.models import IAmPrincipal
from goodtimes import constants, date_utils
from manage_cms import models
from taggit.models import Tag
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = ['id', 'name']
class NewsAndArticlesSerializer(serializers.ModelSerializer):
class Meta:
model = models.NewsAndArticles
fields = "__all__"
class NewsLetterSerializer(serializers.ModelSerializer):
class Meta:
model = models.Newsletter
fields = "__all__"
class FaqSerializer(serializers.ModelSerializer):
class Meta:
model = models.Faqs
fields = "__all__"
class OrganizationSerializer(serializers.ModelSerializer):
about_us = serializers.CharField(source='about_us.html', read_only=True)
terms_condition = serializers.CharField(source='terms_condition.html', read_only=True)
terms_condition_user = serializers.CharField(source='terms_condition_user.html', read_only=True)
terms_condition_merchant = serializers.CharField(source='terms_condition_merchant.html', read_only=True)
privacy_policy = serializers.CharField(source='privacy_policy.html', read_only=True)
privacy_policy_user = serializers.CharField(source='privacy_policy_user.html', read_only=True)
privacy_policy_merchant = serializers.CharField(source='privacy_policy_merchant.html', read_only=True)
subscription_agreement = serializers.CharField(source='subscription_agreement.html', read_only=True)
license_agreement_user = serializers.CharField(source='license_agreement_user.html', read_only=True)
license_agreement_merchant = serializers.CharField(source='license_agreement_merchant.html', read_only=True)
class Meta:
model = models.Organization
fields = [
"about_us",
"terms_condition",
"terms_condition_user",
"terms_condition_merchant",
"privacy_policy",
"privacy_policy_user",
"privacy_policy_merchant",
"subscription_agreement",
"license_agreement_user",
"license_agreement_merchant",
]
class EducationVideoSerializer(serializers.ModelSerializer):
tags = TagSerializer(many=True, read_only=True)
date = serializers.DateTimeField(source="published_at", format='%d %b, %Y')
class Meta:
model = models.Education
fields = [
"content_type",
"title",
"description",
"thumbnail",
"video_url",
"tags",
"date"
]
def get_image_url(self, obj, field_name, request):
image_field = getattr(obj, field_name)
if image_field:
return request.build_absolute_uri(image_field.url)
return ""
def to_representation(self, instance):
data = super().to_representation(instance)
request = self.context.get("request")
data["thumbnail"] = self.get_image_url(instance, "thumbnail", request)
return data
class EducationMaterialSerializer(serializers.ModelSerializer):
tags = TagSerializer(many=True, read_only=True)
date = serializers.DateTimeField(source="published_at", format='%d %b, %Y')
class Meta:
model = models.Education
fields = [
"content_type",
"title",
"file",
"tags",
"date"
]
def get_image_url(self, obj, field_name, request):
image_field = getattr(obj, field_name)
if image_field:
return request.build_absolute_uri(image_field.url)
return ""
def to_representation(self, instance):
data = super().to_representation(instance)
request = self.context.get("request")
data["file"] = self.get_image_url(instance, "file", request)
return data
# class EducationTagSerializer(serializers.ModelSerializer):
# tags = TagSerializer(many=True, read_only=True)
# class Meta:
# model = models.Education
# fields = ["tags"]

17
manage_cms/api/urls.py Normal file
View File

@@ -0,0 +1,17 @@
from django.urls import path
from . import views
app_name = "manage_cms_api"
urlpatterns = [
path('news-article-mobile/', views.NewsAndArticlesView.as_view(), name='news_article_mobile'),
path('newsletter-mobile/', views.NewsLettersView.as_view(), name='newsletter_mobile'),
path('faq-mobile/', views.FaqView.as_view(), name='faq_mobile'),
path('organization-mobile/', views.OrganizationView.as_view(), name='organization_mobile'),
path('education/video/', views.EducationVideoView.as_view(), name='education_video'),
path('education/material/', views.EducationMaterialView.as_view(), name='education_material'),
path('education/tags/', views.EducationTagsView.as_view(), name='education_tags'),
]

158
manage_cms/api/views.py Normal file
View File

@@ -0,0 +1,158 @@
from django.db import transaction
from django.utils import timezone
from rest_framework import status, generics
from rest_framework.views import APIView
from rest_framework_simplejwt.tokens import RefreshToken
from . import serializers
from accounts.models import IAmPrincipal
from goodtimes import constants
from goodtimes.services import SMSError, SMSService
from manage_cms import models
from taggit.models import Tag
from taggit.models import TaggedItem
from django.contrib.contenttypes.models import ContentType
# from nifty11_project.services import SMSError, SMSService
from goodtimes.utils import ApiResponse
from goodtimes import date_utils
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework_simplejwt.authentication import JWTAuthentication
class NewsAndArticlesView(APIView):
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
def get(self, request):
queryset = models.NewsAndArticles.objects.filter(active=True)
serializer = serializers.NewsAndArticlesSerializer(queryset, many=True)
success_response = {
"status": status.HTTP_200_OK,
"message": constants.SUCCESS,
"data": serializer.data,
}
return ApiResponse.success(**success_response)
class NewsLettersView(APIView):
authentication_classes = []
permission_classes = []
def get(self, request):
queryset = models.Newsletter.objects.filter(active=True, is_published=True)
serializer = serializers.NewsLetterSerializer(queryset, many=True)
return ApiResponse.success(
{"data": serializer.data, "message": constants.SUCCESS}
)
class FaqView(APIView):
authentication_classes = []
permission_classes = []
def get(self, request):
queryset = models.Faqs.objects.filter(active=True)
serializer = serializers.FaqSerializer(queryset, many=True)
success_response = {
"status": status.HTTP_200_OK,
"message": constants.SUCCESS,
"data": serializer.data,
}
return ApiResponse.success(**success_response)
class OrganizationView(APIView):
# queryset = models.Organization.objects.latest('id')
model = models.Organization
serializer_class = serializers.OrganizationSerializer
authentication_classes = []
permission_classes = []
def get(self, request):
queryset = self.model.objects.filter(active=True).last()
serializer = self.serializer_class(queryset)
success_response = {
"status": status.HTTP_200_OK,
"message": constants.SUCCESS,
"data": serializer.data,
}
return ApiResponse.success(**success_response)
class EducationVideoView(APIView):
model = models.Education
serializer_class = serializers.EducationVideoSerializer
authentication_classes = []
permission_classes = []
def get(self, request):
queryset = self.model.objects.prefetch_related("tags").filter(
content_type=self.model.VIDEO,
active=True,
published_at__lte=date_utils.get_current_date().isoformat(),
withdrawn_at__gte=date_utils.get_current_date().isoformat(),
)
serializer = self.serializer_class(
queryset, many=True, context={"request": request}
)
success_response = {
"status": status.HTTP_200_OK,
"message": constants.SUCCESS,
"data": serializer.data,
}
return ApiResponse.success(**success_response)
class EducationMaterialView(APIView):
model = models.Education
serializer_class = serializers.EducationMaterialSerializer
authentication_classes = []
permission_classes = []
def get(self, request):
queryset = self.model.objects.prefetch_related("tags").filter(
content_type=self.model.MATERIAL,
active=True,
published_at__lte=date_utils.get_current_date().isoformat(),
withdrawn_at__gte=date_utils.get_current_date().isoformat(),
)
serializer = self.serializer_class(
queryset, many=True, context={"request": request}
)
success_response = {
"status": status.HTTP_200_OK,
"message": constants.SUCCESS,
"data": serializer.data,
}
return ApiResponse.success(**success_response)
class EducationTagsView(APIView):
model = models.Education
serializer_class = serializers.TagSerializer
authentication_classes = []
permission_classes = []
def get(self, request):
# Get ContentType for the Education model
education_content_type = ContentType.objects.get_for_model(self.model)
# Fetch tags associated with Education instances
education_tags = (
TaggedItem.objects.filter(content_type=education_content_type)
.values_list("tag", flat=True)
)
# Retrieve actual tag objects from Tag model using the tag IDs obtained
queryset = Tag.objects.filter(id__in=education_tags)
serializer = self.serializer_class(queryset, many=True)
success_response = {
"status": status.HTTP_200_OK,
"message": constants.SUCCESS,
"data": serializer.data,
}
return ApiResponse.success(**success_response)

6
manage_cms/apps.py Normal file
View File

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

216
manage_cms/forms.py Normal file
View File

@@ -0,0 +1,216 @@
from django import forms
from django.core.exceptions import ValidationError
from django.core import validators
from .models import (
Organization,
NewsAndArticlesCategory,
NewsAndArticles,
Newsletter,
FaqCategory,
Faqs,
Education,
)
from goodtimes import constants
from accounts import resource_action
class OrganizationForm(forms.ModelForm):
class Meta:
model = Organization
fields = [
"title",
"contact_us_email",
"instagram_handle",
"facebook_handle",
"linkedin_handle",
"logo_image",
"favicon_image",
"website_url",
]
labels = {
"title": "Organization Title",
"contact_us_email": "Contact Email",
"instagram_handle": "Instagram URL",
"facebook_handle": "Facebook URL",
"linkedin_handle": "LinkedIn URL",
"logo_image": "Organization Logo",
"favicon_image": "Favicon",
"website_url": "Website URL",
}
class NewsAndArticleCategoryForm(forms.ModelForm):
class Meta:
model = NewsAndArticlesCategory
fields = ["name"]
labels = {"name": "Category Name"}
class NewsAndArticlesForm(forms.ModelForm):
image_url = forms.ImageField()
class Meta:
model = NewsAndArticles
fields = [
"title",
"small_description",
"long_description",
"image_url",
"video_url",
"article_category",
"tags",
"active",
]
labels = {
"title": "Article Title",
"small_description": "Small Description",
"long_description": "Long Description",
"image_url": "Image",
"video_url": "Video URL",
"article_category": "Article Category",
"tags": "Tags",
"active": "Active",
}
def __init__(self, *args, **kwargs):
instance = kwargs.get("instance")
super().__init__(*args, **kwargs)
# Fetch the choices for the article_category field from the database
self.fields["article_category"].queryset = (
NewsAndArticlesCategory.objects.filter(deleted=False)
)
if instance is None:
# This is an add operation, exclude the 'active' field
self.fields.pop("active")
class AboutUsForm(forms.ModelForm):
class Meta:
model = Organization
fields = ["about_us"]
labels = {"about_us": "Enter information about your organization:"}
class TermsAndConditionForm(forms.ModelForm):
class Meta:
model = Organization
fields = ["terms_condition"]
labels = {"terms_condition": "Enter Terms and Conditions:"}
class PrivacyPolicyForm(forms.ModelForm):
class Meta:
model = Organization
fields = ["privacy_policy"]
labels = {"privacy_policy": "Enter Privacy Police:"}
class FaqCategoryFrom(forms.ModelForm):
class Meta:
model = FaqCategory
fields = ["name"]
labels = {"name": "Category name"}
class FaqsForm(forms.ModelForm):
class Meta:
model = Faqs
fields = [
# "faq_category",
"question",
"answer",
"active",
]
# labels = {"faq_category": "Category"}
def __init__(self, *args, **kwargs):
instance = kwargs.get("instance")
super().__init__(*args, **kwargs)
# Fetch the choices for the faq_category field from the database
# self.fields["faq_category"].queryset = FaqCategory.objects.all()
if instance is None:
# This is an add operation, exclude the 'active' field
self.fields.pop("active")
class EducationVideoForm(forms.ModelForm):
published_at = forms.DateTimeField()
withdrawn_at = forms.DateTimeField()
thumbnail = forms.ImageField()
class Meta:
model = Education
fields = [
"content_type",
"title",
"description",
"thumbnail",
"video_url",
"tags",
"published_at",
"withdrawn_at",
"active",
]
labels = {
"content_type": "Type",
"title": "Title",
"description": "Description",
"thumbnail": "Thumbnail",
"video_url": "Video Url",
"tags": "Tags",
"published_at": "Publish Date",
"withdrawn_at": "Withdrawn Date",
"active": "Active",
}
def __init__(self, *args, **kwargs):
instance = kwargs.get("instance")
super().__init__(*args, **kwargs)
self.fields["content_type"].initial = Education.VIDEO
self.fields["content_type"].widget = forms.HiddenInput()
if instance is None:
self.fields.pop("active")
class EducationMaterialForm(forms.ModelForm):
published_at = forms.DateTimeField()
withdrawn_at = forms.DateTimeField()
file = forms.FileField()
class Meta:
model = Education
fields = [
"content_type",
"title",
"file",
"tags",
"published_at",
"withdrawn_at",
"active",
]
labels = {
"content_type": "Type",
"title": "Title",
"file": "Upload File",
"tags": "Tags",
"published_at": "Publish Date",
"withdrawn_at": "Withdrawn Date",
"active": "Active",
}
def __init__(self, *args, **kwargs):
instance = kwargs.get("instance")
super().__init__(*args, **kwargs)
self.fields["content_type"].initial = Education.MATERIAL
self.fields["content_type"].widget = forms.HiddenInput()
if instance is None:
self.fields.pop("active")

View File

@@ -0,0 +1,436 @@
# Generated by Django 5.0.2 on 2024-02-29 07:47
import django.core.validators
import django.db.models.deletion
import django_quill.fields
import taggit.managers
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
(
"taggit",
"0006_rename_taggeditem_content_type_object_id_taggit_tagg_content_8fc721_idx",
),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name="Education",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("active", models.BooleanField(default=True)),
("deleted", models.BooleanField(default=False)),
("created_on", models.DateTimeField(auto_now_add=True)),
("modified_on", models.DateTimeField(auto_now=True)),
(
"content_type",
models.CharField(
choices=[("Video", "Video"), ("Material", "Material")],
max_length=20,
),
),
("title", models.CharField(max_length=255)),
("description", models.TextField()),
(
"thumbnail",
models.ImageField(blank=True, null=True, upload_to="education"),
),
(
"file",
models.FileField(
blank=True,
null=True,
upload_to="education",
validators=[
django.core.validators.FileExtensionValidator(
allowed_extensions=["pdf"],
message="Only PDF files are allowed.",
)
],
),
),
("video_url", models.URLField(blank=True, null=True)),
("published_at", models.DateTimeField()),
("withdrawn_at", models.DateTimeField()),
(
"created_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="%(class)s_created",
to=settings.AUTH_USER_MODEL,
),
),
(
"modified_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="%(class)s_modified",
to=settings.AUTH_USER_MODEL,
),
),
(
"tags",
taggit.managers.TaggableManager(
help_text="A comma-separated list of tags.",
through="taggit.TaggedItem",
to="taggit.Tag",
verbose_name="Tags",
),
),
],
options={
"abstract": False,
},
),
migrations.CreateModel(
name="FaqCategory",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("active", models.BooleanField(default=True)),
("deleted", models.BooleanField(default=False)),
("created_on", models.DateTimeField(auto_now_add=True)),
("modified_on", models.DateTimeField(auto_now=True)),
("name", models.CharField(max_length=255)),
(
"created_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="%(class)s_created",
to=settings.AUTH_USER_MODEL,
),
),
(
"modified_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="%(class)s_modified",
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"db_table": "faq_category",
},
),
migrations.CreateModel(
name="Faqs",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("active", models.BooleanField(default=True)),
("deleted", models.BooleanField(default=False)),
("created_on", models.DateTimeField(auto_now_add=True)),
("modified_on", models.DateTimeField(auto_now=True)),
("question", models.TextField(max_length=255)),
("answer", models.TextField(blank=True, null=True)),
(
"created_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="%(class)s_created",
to=settings.AUTH_USER_MODEL,
),
),
(
"faq_category",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="faqs_category",
to="manage_cms.faqcategory",
),
),
(
"modified_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="%(class)s_modified",
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"db_table": "faq",
},
),
migrations.CreateModel(
name="NewsAndArticlesCategory",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("active", models.BooleanField(default=True)),
("deleted", models.BooleanField(default=False)),
("created_on", models.DateTimeField(auto_now_add=True)),
("modified_on", models.DateTimeField(auto_now=True)),
("name", models.CharField(max_length=255)),
(
"created_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="%(class)s_created",
to=settings.AUTH_USER_MODEL,
),
),
(
"modified_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="%(class)s_modified",
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"db_table": "news_article_category",
},
),
migrations.CreateModel(
name="NewsAndArticles",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("active", models.BooleanField(default=True)),
("deleted", models.BooleanField(default=False)),
("created_on", models.DateTimeField(auto_now_add=True)),
("modified_on", models.DateTimeField(auto_now=True)),
("title", models.CharField(max_length=255)),
("small_description", models.TextField(blank=True, null=True)),
("long_description", django_quill.fields.QuillField()),
(
"image_url",
models.ImageField(blank=True, null=True, upload_to="news_article"),
),
("video_url", models.URLField(blank=True, max_length=2000, null=True)),
(
"author",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="%(class)s_author",
to=settings.AUTH_USER_MODEL,
),
),
(
"created_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="%(class)s_created",
to=settings.AUTH_USER_MODEL,
),
),
(
"modified_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="%(class)s_modified",
to=settings.AUTH_USER_MODEL,
),
),
(
"tags",
taggit.managers.TaggableManager(
help_text="A comma-separated list of tags.",
through="taggit.TaggedItem",
to="taggit.Tag",
verbose_name="Tags",
),
),
(
"article_category",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="%(class)s_category",
to="manage_cms.newsandarticlescategory",
),
),
],
options={
"db_table": "news_article",
},
),
migrations.CreateModel(
name="Newsletter",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("active", models.BooleanField(default=True)),
("deleted", models.BooleanField(default=False)),
("created_on", models.DateTimeField(auto_now_add=True)),
("modified_on", models.DateTimeField(auto_now=True)),
("title", models.CharField(max_length=255)),
("content", django_quill.fields.QuillField()),
("publication_date", models.DateField(auto_now_add=True)),
("is_published", models.BooleanField(default=False)),
(
"created_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="%(class)s_created",
to=settings.AUTH_USER_MODEL,
),
),
(
"modified_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="%(class)s_modified",
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"db_table": "newsletters",
"ordering": ["-publication_date"],
},
),
migrations.CreateModel(
name="Organization",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("active", models.BooleanField(default=True)),
("deleted", models.BooleanField(default=False)),
("created_on", models.DateTimeField(auto_now_add=True)),
("modified_on", models.DateTimeField(auto_now=True)),
("title", models.CharField(max_length=255)),
(
"contact_us_email",
models.EmailField(
blank=True, max_length=254, null=True, unique=True
),
),
("instagram_handle", models.URLField(blank=True, null=True)),
("facebook_handle", models.URLField(blank=True, null=True)),
("linkedin_handle", models.URLField(blank=True, null=True)),
(
"logo_image",
models.ImageField(
blank=True, null=True, upload_to="organization/logo"
),
),
(
"favicon_image",
models.ImageField(
blank=True, null=True, upload_to="organization/favicon"
),
),
("website_url", models.URLField(blank=True, null=True)),
("about_us", django_quill.fields.QuillField()),
("terms_condition", django_quill.fields.QuillField()),
("terms_condition_user", django_quill.fields.QuillField()),
("terms_condition_merchant", django_quill.fields.QuillField()),
("privacy_policy", django_quill.fields.QuillField()),
("privacy_policy_user", django_quill.fields.QuillField()),
("privacy_policy_merchant", django_quill.fields.QuillField()),
("subscription_agreement", django_quill.fields.QuillField()),
("license_agreement_user", django_quill.fields.QuillField()),
("license_agreement_merchant", django_quill.fields.QuillField()),
(
"created_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="%(class)s_created",
to=settings.AUTH_USER_MODEL,
),
),
(
"modified_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="%(class)s_modified",
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"db_table": "organization",
},
),
]

View File

159
manage_cms/models.py Normal file
View File

@@ -0,0 +1,159 @@
from django.db import models
from taggit.managers import TaggableManager
from django_quill.fields import QuillField
from accounts.models import BaseModel, IAmPrincipal, IAmPrincipalType
from django.utils import timezone
from django.core.exceptions import ValidationError
from django.core.validators import FileExtensionValidator
# Create your models here.
class NewsAndArticlesCategory(BaseModel):
name = models.CharField(max_length=255)
class Meta:
db_table = "news_article_category"
def __str__(self):
return self.name
class NewsAndArticles(BaseModel):
title = models.CharField(max_length=255)
small_description = models.TextField(blank=True, null=True)
long_description = QuillField()
image_url = models.ImageField(upload_to="news_article", blank=True, null=True)
video_url = models.URLField(max_length=2000, blank=True, null=True)
article_category = models.ForeignKey(
NewsAndArticlesCategory,
related_name="%(class)s_category",
on_delete=models.CASCADE,
)
author = models.ForeignKey(
IAmPrincipal,
related_name="%(class)s_author",
null=True,
blank=True,
on_delete=models.SET_NULL,
)
tags = TaggableManager()
class Meta:
db_table = "news_article"
def __str__(self):
return self.title
class Newsletter(BaseModel):
title = models.CharField(max_length=255)
content = QuillField()
publication_date = models.DateField(auto_now_add=True)
is_published = models.BooleanField(default=False)
class Meta:
db_table = "newsletters"
ordering = ["-publication_date"]
def __str__(self):
return self.title
class FaqCategory(BaseModel):
name = models.CharField(max_length=255)
class Meta:
db_table = "faq_category"
def __str__(self):
return self.name
class Faqs(BaseModel):
faq_category = models.ForeignKey(
FaqCategory, related_name="faqs_category", null=True, on_delete=models.SET_NULL
)
question = models.TextField(max_length=255)
answer = models.TextField(blank=True, null=True)
class Meta:
db_table = "faq"
def __str__(self):
return self.question
class Organization(BaseModel):
title = models.CharField(max_length=255)
contact_us_email = models.EmailField(unique=True, blank=True, null=True)
instagram_handle = models.URLField(blank=True, null=True)
facebook_handle = models.URLField(blank=True, null=True)
linkedin_handle = models.URLField(blank=True, null=True)
logo_image = models.ImageField(blank=True, null=True, upload_to="organization/logo")
favicon_image = models.ImageField(
blank=True, null=True, upload_to="organization/favicon"
)
website_url = models.URLField(blank=True, null=True)
about_us = QuillField()
terms_condition = QuillField()
terms_condition_user = QuillField()
terms_condition_merchant = QuillField()
privacy_policy = QuillField()
privacy_policy_user = QuillField()
privacy_policy_merchant = QuillField()
subscription_agreement = QuillField()
license_agreement_user = QuillField()
license_agreement_merchant = QuillField()
class Meta:
db_table = "organization"
def __str__(self):
return self.title
class Education(BaseModel):
VIDEO = "Video"
MATERIAL = "Material"
CONTENT_TYPE_CHOICES = (
(VIDEO, "Video"),
(MATERIAL, "Material"),
)
content_type = models.CharField(max_length=20, choices=CONTENT_TYPE_CHOICES)
title = models.CharField(max_length=255)
description = models.TextField()
thumbnail = models.ImageField(upload_to="education", null=True, blank=True)
file = models.FileField(
upload_to="education",
null=True,
blank=True,
validators=[
FileExtensionValidator(
allowed_extensions=["pdf"], message="Only PDF files are allowed."
)
]
)
video_url = models.URLField(null=True, blank=True)
tags = TaggableManager(related_name="education_tags")
published_at = models.DateTimeField()
withdrawn_at = models.DateTimeField()
def clean(self):
current_date = timezone.now()
# if self.published_at and self.published_at < current_date:
# raise ValidationError("Published date should be in the future.")
if self.withdrawn_at and self.withdrawn_at < current_date:
raise ValidationError("Withdrawn date should be in the future.")
if (self.published_at and self.withdrawn_at and self.published_at > self.withdrawn_at):
raise ValidationError(
"Published date should be earlier than withdrawn date."
)
def is_publised(self):
current_date = timezone.now()
return self.published_at <= current_date and (
not self.withdrawn_at or self.withdrawn_at > current_date
)

3
manage_cms/tests.py Normal file
View File

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

52
manage_cms/urls.py Normal file
View File

@@ -0,0 +1,52 @@
from django.urls import path
from . import views
from django.views.generic import TemplateView
app_name = 'manage_cms'
urlpatterns = [
path('dashboard/', views.CmsDashboardView.as_view(), name='cms_dashboard'),
path('news-article/', views.NewsArticleListView.as_view(), name='news_article_list'),
path('news-article/add/', views.NewsArticleCreateOrUpdateView.as_view(), name='news_article_add'),
path('news-article/edit/<int:pk>/', views.NewsArticleCreateOrUpdateView.as_view(), name='news_article_edit'),
path('news-article/delete/<int:pk>/', views.NewsArticleDeleteView.as_view(), name='news_article_delete'),
path('news-article/category/add/', views.NewsArticleCategoryCreateOrUpdateView.as_view(), name='news_article_category_add'),
path('news-article/category/edit/<int:pk>/', views.NewsArticleCategoryCreateOrUpdateView.as_view(), name='news_article_category_edit'),
path('news-article/category/delete/<int:pk>/', views.NewsArticleCategoryDeleteView.as_view(), name='news_article_category_delete'),
path('newsletter/', views.NewsLetterListView.as_view(), name='newsletter_list'),
path('newsletter/add/', views.NewsLetterCreateOrUpdateView.as_view(), name='newsletter_add'),
path('newsletter/edit/<int:pk>/', views.NewsArticleCreateOrUpdateView.as_view(), name='news_article_edit'),
path('newsletter/delete/<int:pk>/', views.NewsArticleDeleteView.as_view(), name='news_article_delete'),
path('about-us/', views.AboutUsView.as_view(), name='about_us_view'),
path('about-us/edit/', views.AboutUsCreateOrUpdateView.as_view(), name='about_us_add'),
path('terms-condition/', views.TermsConditionView.as_view(), name='terms_and_condition_view'),
path('terms-condition/edit/', views.TermsConditionCreateOrUpdateView.as_view(), name='terms_and_condition_edit'),
path('faq/', views.FaqListView.as_view(), name='faq_list'),
path('faq/add/', views.FaqCreateOrUpdateView.as_view(), name='faq_add'),
path('faq/edit/<int:pk>/', views.FaqCreateOrUpdateView.as_view(), name='faq_edit'),
path('faq/category/add/', views.FaqCategoryCreateOrUpdateView.as_view(), name='faq_category_add'),
path('faq/category/edit/<int:pk>/', views.FaqCategoryCreateOrUpdateView.as_view(), name='faq_category_edit'),
path('privacy-policy/', views.PrivacyPolicyView.as_view(), name='privacy_policy_view'),
path('privacy-policy/edit/', views.PrivacyPolicyCreateOrUpdateView.as_view(), name='privacy_policy_edit'),
path('testimonial/', views.TestimonialListView.as_view(), name='testimonial_list'),
path('organization/', views.OrganizationView.as_view(), name='organization_view'),
path('organization/add/', views.OrganizationCreateOrUpdateView.as_view(), name='organization_add'),
path('education/', views.EducationView.as_view(), name='education_view'),
path('education/video/add/', views.EducationVideoCreateOrUpdateView.as_view(), name='education_add_video'),
path('education/video/edit/<int:pk>/', views.EducationVideoCreateOrUpdateView.as_view(), name='education_edit_video'),
path('education/material/add/', views.EducationMaterialCreateOrUpdateView.as_view(), name='education_add_material'),
path('education/material/edit/<int:pk>/', views.EducationMaterialCreateOrUpdateView.as_view(), name='education_edit_material'),
]

879
manage_cms/views.py Normal file
View File

@@ -0,0 +1,879 @@
from typing import Any
from django.db import models
from django.shortcuts import render, get_object_or_404, redirect
from django.contrib.auth.mixins import LoginRequiredMixin
from accounts import resource_action
from django.views import generic
from goodtimes import constants
from django.urls import reverse_lazy
from django.contrib import messages
from .models import (
Organization,
NewsAndArticles,
NewsAndArticlesCategory,
Newsletter,
Faqs,
FaqCategory,
Education,
)
from .forms import (
OrganizationForm,
NewsAndArticlesForm,
NewsAndArticleCategoryForm,
AboutUsForm,
TermsAndConditionForm,
PrivacyPolicyForm,
FaqsForm,
FaqCategoryFrom,
EducationVideoForm,
EducationMaterialForm
)
class CmsDashboardView(LoginRequiredMixin, generic.TemplateView):
page_name = resource_action.RESOURCE_MANAGE_CMS
template_name = "manage_cms/cms_dashboard.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["page_name"] = self.page_name
return context
class NewsArticleListView(LoginRequiredMixin, generic.ListView):
page_name = resource_action.RESOURCE_MANAGE_CMS
resource = resource_action.RESOURCE_MANAGE_CMS
action = resource_action.ACTION_READ
template_name = "manage_cms/news_article_list.html"
model = NewsAndArticles
context_object_name = "news_article_obj"
def get_queryset(self):
return (
super()
.get_queryset()
.select_related("article_category")
.prefetch_related("tags")
.filter(deleted=False)
)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["page_name"] = self.page_name
context["category_obj"] = NewsAndArticlesCategory.objects.filter(deleted=False)
return context
class NewsArticleCreateOrUpdateView(LoginRequiredMixin, generic.View):
# Set the page_name and resource
page_name = resource_action.RESOURCE_MANAGE_CMS
resource = resource_action.RESOURCE_MANAGE_CMS
# Initialize the action as ACTION_CREATE (can change based on logic)
action = resource_action.ACTION_CREATE # Default action
template_name = "manage_cms/news_article_add.html"
model = NewsAndArticles
form_class = NewsAndArticlesForm
success_url = reverse_lazy("manage_cms:news_article_list")
error_message = "An error occurred while saving the data."
# Determine the success message dynamically based on whether it's an update or create
def get_success_message(self):
self.success_message = (
constants.RECORD_CREATED if not self.object else constants.RECORD_UPDATED
)
return self.success_message
# Get the object (if exists) based on URL parameter 'pk'
def get_object(self):
pk = self.kwargs.get("pk")
return get_object_or_404(self.model, pk=pk) if pk else None
# Add page_name and operation to the context
def get_context_data(self, **kwargs):
context = {
"page_name": self.page_name,
"operation": "Add" if not self.object else "Edit",
}
context.update(kwargs) # Include any additional context data passed to the view
return context
def get(self, request, *args, **kwargs):
self.object = self.get_object()
# If an object is found, change action to ACTION_UPDATE
if self.object is not None:
self.action = resource_action.ACTION_UPDATE
print("get method of article is called")
form = self.form_class(instance=self.object)
context = self.get_context_data(form=form)
return render(request, self.template_name, context=context)
def post(self, request, *args, **kwargs):
self.object = self.get_object()
# If an object is found, change action to ACTION_UPDATE
if self.object is not None:
self.action = resource_action.ACTION_UPDATE
print("post method is called")
form = self.form_class(request.POST, request.FILES, instance=self.object)
print("request with files", request.FILES)
if not form.is_valid():
print(form.errors)
context = self.get_context_data(form=form)
return render(request, self.template_name, context=context)
form.save()
messages.success(self.request, self.get_success_message())
return redirect(self.success_url)
class NewsArticleDeleteView(LoginRequiredMixin, generic.View):
page_name = resource_action.RESOURCE_MANAGE_CMS
resource = resource_action.RESOURCE_MANAGE_CMS
action = resource_action.ACTION_DELETE
model = NewsAndArticles
success_url = reverse_lazy("manage_cms:news_article_list")
success_message = constants.RECORD_DELETED
error_message = constants.RECORD_NOT_FOUND
def get(self, request, pk):
try:
type_obj = self.model.objects.get(id=pk)
type_obj.deleted = True
type_obj.save()
messages.success(request, self.success_message)
except self.model.DoesNotExist:
messages.success(request, self.error_message)
return redirect(self.success_url)
class NewsArticleCategoryCreateOrUpdateView(LoginRequiredMixin, generic.View):
# Set the page_name and resource
page_name = resource_action.RESOURCE_MANAGE_CMS
resource = resource_action.RESOURCE_MANAGE_CMS
# Initialize the action as ACTION_CREATE (can change based on logic)
action = resource_action.ACTION_CREATE # Default action
template_name = "manage_cms/news_article_category_add.html"
model = NewsAndArticlesCategory
form_class = NewsAndArticleCategoryForm
success_url = reverse_lazy("manage_cms:news_article_list")
error_message = "An error occurred while saving the data."
# Determine the success message dynamically based on whether it's an update or create
def get_success_message(self):
self.success_message = (
constants.RECORD_CREATED if not self.object else constants.RECORD_UPDATED
)
return self.success_message
# Get the object (if exists) based on URL parameter 'pk'
def get_object(self):
pk = self.kwargs.get("pk")
return get_object_or_404(self.model, pk=pk) if pk else None
# Add page_name and operation to the context
def get_context_data(self, **kwargs):
context = {
"page_name": self.page_name,
"operation": "Add" if not self.object else "Edit",
}
context.update(kwargs) # Include any additional context data passed to the view
return context
def get(self, request, *args, **kwargs):
print("Get Action called:")
self.object = self.get_object()
# If an object is found, change action to ACTION_UPDATE
if self.object is not None:
self.action = resource_action.ACTION_UPDATE
form = self.form_class(instance=self.object)
context = self.get_context_data(form=form)
return render(request, self.template_name, context=context)
def post(self, request, *args, **kwargs):
self.object = self.get_object()
# If an object is found, change action to ACTION_UPDATE
if self.object is not None:
self.action = resource_action.ACTION_UPDATE
form = self.form_class(request.POST, instance=self.object)
if not form.is_valid():
print(form.errors)
context = self.get_context_data(form=form)
return render(request, self.template_name, context=context)
form.save()
messages.success(self.request, self.get_success_message())
return redirect(self.success_url)
class NewsArticleCategoryDeleteView(LoginRequiredMixin, generic.View):
page_name = resource_action.RESOURCE_MANAGE_CMS
resource = resource_action.RESOURCE_MANAGE_CMS
action = resource_action.ACTION_DELETE
model = NewsAndArticlesCategory
success_url = reverse_lazy("manage_cms:news_article_list")
success_message = constants.RECORD_DELETED
error_message = constants.RECORD_NOT_FOUND
def get(self, request, pk):
try:
type_obj = self.model.objects.get(id=pk)
type_obj.deleted = True
type_obj.save()
messages.success(request, self.success_message)
except self.model.DoesNotExist:
messages.success(request, self.error_message)
return redirect(self.success_url)
class NewsLetterListView(LoginRequiredMixin, generic.TemplateView):
page_name = resource_action.RESOURCE_MANAGE_CMS
resource = resource_action.RESOURCE_MANAGE_CMS
action = resource_action.ACTION_READ
template_name = "manage_cms/newsletter_list.html"
# Determine the success message dynamically based on whether it's an update or create
def get_success_message(self):
self.success_message = (
constants.RECORD_CREATED if not self.object else constants.RECORD_UPDATED
)
return self.success_message
# Get the object (if exists) based on URL parameter 'pk'
def get_object(self):
pk = self.kwargs.get("pk")
return get_object_or_404(self.model, pk=pk) if pk else None
# Add page_name and operation to the context
def get_context_data(self, **kwargs):
context = {
"page_name": self.page_name,
"operation": "Add" if not self.object else "Edit",
}
context.update(kwargs) # Include any additional context data passed to the view
return context
def get(self, request, *args, **kwargs):
self.object = self.get_object()
# If an object is found, change action to ACTION_UPDATE
if self.object is not None:
self.action = resource_action.ACTION_UPDATE
form = self.form_class(instance=self.object)
context = self.get_context_data(form=form)
return render(request, self.template_name, context=context)
def post(self, request, *args, **kwargs):
self.object = self.get_object()
# If an object is found, change action to ACTION_UPDATE
if self.object is not None:
self.action = resource_action.ACTION_UPDATE
form = self.form_class(request.POST, instance=self.object)
if not form.is_valid():
print(form.errors)
context = self.get_context_data(form=form)
return render(request, self.template_name, context=context)
form.save()
messages.success(self.request, self.get_success_message())
return redirect(self.success_url)
class NewsLetterCreateOrUpdateView(LoginRequiredMixin, generic.TemplateView):
page_name = resource_action.RESOURCE_MANAGE_CMS
template_name = "manage_cms/newsletter_add.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["page_name"] = self.page_name
return context
class AboutUsView(LoginRequiredMixin, generic.DetailView):
page_name = resource_action.RESOURCE_MANAGE_CMS
template_name = "manage_cms/about_us_view.html"
model = Organization
context_object_name = "organization"
def get_object(self, queryset=None):
return self.model.objects.only("about_us").first()
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["page_name"] = self.page_name
return context
class AboutUsCreateOrUpdateView(LoginRequiredMixin, generic.View):
# Set the page_name and resource
page_name = resource_action.RESOURCE_MANAGE_CMS
resource = resource_action.RESOURCE_MANAGE_CMS
# Initialize the action as ACTION_CREATE (can change based on logic)
action = resource_action.ACTION_CREATE # Default action
template_name = "manage_cms/about_us_add.html"
model = Organization
form_class = AboutUsForm
success_url = reverse_lazy("manage_cms:about_us_view")
error_message = "An error occurred while saving the data."
# Determine the success message dynamically based on whether it's an update or create
def get_success_message(self):
self.success_message = (
constants.RECORD_CREATED if not self.object else constants.RECORD_UPDATED
)
return self.success_message
# Get the object (if exists) based on URL parameter 'pk'
def get_object(self):
return self.model.objects.only("about_us").first()
# Add page_name and operation to the context
def get_context_data(self, **kwargs):
context = {
"page_name": self.page_name,
"operation": "Add" if not self.object else "Edit",
}
context.update(kwargs) # Include any additional context data passed to the view
return context
def get(self, request, *args, **kwargs):
self.object = self.get_object()
# If an object is found, change action to ACTION_UPDATE
if self.object is not None:
self.action = resource_action.ACTION_UPDATE
form = self.form_class(instance=self.object)
context = self.get_context_data(form=form)
return render(request, self.template_name, context=context)
def post(self, request, *args, **kwargs):
self.object = self.get_object()
# If an object is found, change action to ACTION_UPDATE
if self.object is not None:
self.action = resource_action.ACTION_UPDATE
form = self.form_class(request.POST, instance=self.object)
if not form.is_valid():
print(form.errors)
context = self.get_context_data(form=form)
return render(request, self.template_name, context=context)
form.save()
messages.success(self.request, self.get_success_message())
return redirect(self.success_url)
class TermsConditionView(LoginRequiredMixin, generic.DetailView):
page_name = resource_action.RESOURCE_MANAGE_CMS
resource = resource_action.RESOURCE_MANAGE_CMS
action = resource_action.ACTION_DELETE
template_name = "manage_cms/terms_and_condition_view.html"
model = Organization
context_object_name = "organization"
def get_object(self, queryset=None):
return self.model.objects.only("terms_condition").first()
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["page_name"] = self.page_name
return context
class TermsConditionCreateOrUpdateView(LoginRequiredMixin, generic.View):
# Set the page_name and resource
page_name = resource_action.RESOURCE_MANAGE_CMS
resource = resource_action.RESOURCE_MANAGE_CMS
# Initialize the action as ACTION_CREATE (can change based on logic)
action = resource_action.ACTION_CREATE # Default action
template_name = "manage_cms/terms_and_condition_edit.html"
model = Organization
form_class = TermsAndConditionForm
success_url = reverse_lazy("manage_cms:terms_and_condition_view")
error_message = "An error occurred while saving the data."
# Determine the success message dynamically based on whether it's an update or create
def get_success_message(self):
self.success_message = (
constants.RECORD_CREATED if not self.object else constants.RECORD_UPDATED
)
return self.success_message
# Get the object (if exists) based on URL parameter 'pk'
def get_object(self):
return self.model.objects.only("terms_condition").first()
# Add page_name and operation to the context
def get_context_data(self, **kwargs):
context = {
"page_name": self.page_name,
"operation": "Add" if not self.object else "Edit",
}
context.update(kwargs) # Include any additional context data passed to the view
return context
def get(self, request, *args, **kwargs):
self.object = self.get_object()
# If an object is found, change action to ACTION_UPDATE
if self.object is not None:
self.action = resource_action.ACTION_UPDATE
form = self.form_class(instance=self.object)
context = self.get_context_data(form=form)
return render(request, self.template_name, context=context)
def post(self, request, *args, **kwargs):
self.object = self.get_object()
# If an object is found, change action to ACTION_UPDATE
if self.object is not None:
self.action = resource_action.ACTION_UPDATE
form = self.form_class(request.POST, instance=self.object)
if not form.is_valid():
print(form.errors)
context = self.get_context_data(form=form)
return render(request, self.template_name, context=context)
form.save()
messages.success(self.request, self.get_success_message())
return redirect(self.success_url)
class FaqListView(LoginRequiredMixin, generic.ListView):
page_name = resource_action.RESOURCE_MANAGE_CMS
resource = resource_action.RESOURCE_MANAGE_CMS
action = resource_action.ACTION_READ
template_name = "manage_cms/faq.html"
model = Faqs
context_object_name = "faqs_obj"
def get_queryset(self):
return (
super()
.get_queryset()
.filter(deleted=False)
)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["page_name"] = self.page_name
context["faq_category_obj"] = FaqCategory.objects.all()
return context
class FaqCreateOrUpdateView(LoginRequiredMixin, generic.View):
# Set the page_name and resource
page_name = resource_action.RESOURCE_MANAGE_CMS
resource = resource_action.RESOURCE_MANAGE_CMS
# Initialize the action as ACTION_CREATE (can change based on logic)
action = resource_action.ACTION_CREATE # Default action
template_name = "manage_cms/faq_add.html"
model = Faqs
form_class = FaqsForm
success_url = reverse_lazy("manage_cms:faq_list")
error_message = "An error occurred while saving the data."
# Determine the success message dynamically based on whether it's an update or create
def get_success_message(self):
self.success_message = (
constants.RECORD_CREATED if not self.object else constants.RECORD_UPDATED
)
return self.success_message
# Get the object (if exists) based on URL parameter 'pk
def get_object(self):
pk = self.kwargs.get("pk")
return get_object_or_404(self.model, pk=pk) if pk else None
# Add page_name and operation to the context
def get_context_data(self, **kwargs):
context = {
"page_name": self.page_name,
"operation": "Add" if not self.object else "Edit",
}
context.update(kwargs) # Include any additional context data passed to the view
return context
def get(self, request, *args, **kwargs):
self.object = self.get_object()
# If an object is found, change action to ACTION_UPDATE
if self.object is not None:
self.action = resource_action.ACTION_UPDATE
form = self.form_class(instance=self.object)
context = self.get_context_data(form=form)
return render(request, self.template_name, context=context)
def post(self, request, *args, **kwargs):
print("Request data: ", request.POST)
self.object = self.get_object()
# If an object is found, change action to ACTION_UPDATE
if self.object is not None:
self.action = resource_action.ACTION_UPDATE
form = self.form_class(request.POST, instance=self.object)
if not form.is_valid():
print(form.errors)
context = self.get_context_data(form=form)
return render(request, self.template_name, context=context)
form.save()
messages.success(self.request, self.get_success_message())
return redirect(self.success_url)
class FaqCategoryCreateOrUpdateView(LoginRequiredMixin, generic.View):
# Set the page_name and resource
page_name = resource_action.RESOURCE_MANAGE_CMS
resource = resource_action.RESOURCE_MANAGE_CMS
# Initialize the action as ACTION_CREATE (can change based on logic)
action = resource_action.ACTION_CREATE # Default action
template_name = "manage_cms/faq_category_add.html"
model = FaqCategory
form_class = FaqCategoryFrom
success_url = reverse_lazy("manage_cms:faq_list")
error_message = "An error occurred while saving the data."
# Determine the success message dynamically based on whether it's an update or create
def get_success_message(self):
self.success_message = (
constants.RECORD_CREATED if not self.object else constants.RECORD_UPDATED
)
return self.success_message
# Get the object (if exists) based on URL parameter 'pk
def get_object(self):
pk = self.kwargs.get("pk")
return get_object_or_404(self.model, pk=pk) if pk else None
# Add page_name and operation to the context
def get_context_data(self, **kwargs):
context = {
"page_name": self.page_name,
"operation": "Add" if not self.object else "Edit",
}
context.update(kwargs) # Include any additional context data passed to the view
return context
def get(self, request, *args, **kwargs):
print("Get Action called:")
self.object = self.get_object()
# If an object is found, change action to ACTION_UPDATE
if self.object is not None:
self.action = resource_action.ACTION_UPDATE
form = self.form_class(instance=self.object)
context = self.get_context_data(form=form)
return render(request, self.template_name, context=context)
def post(self, request, *args, **kwargs):
print("Request data: ", request.POST)
self.object = self.get_object()
# If an object is found, change action to ACTION_UPDATE
if self.object is not None:
self.action = resource_action.ACTION_UPDATE
form = self.form_class(request.POST, instance=self.object)
if not form.is_valid():
print(form.errors)
context = self.get_context_data(form=form)
return render(request, self.template_name, context=context)
form.save()
messages.success(self.request, self.get_success_message())
return redirect(self.success_url)
class PrivacyPolicyView(LoginRequiredMixin, generic.DetailView):
page_name = resource_action.RESOURCE_MANAGE_CMS
resource = resource_action.RESOURCE_MANAGE_CMS
action = resource_action.ACTION_READ
template_name = "manage_cms/privacy_policy_view.html"
model = Organization
context_object_name = "organization"
def get_object(self, queryset=None):
return self.model.objects.only("privacy_policy").first()
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["page_name"] = self.page_name
return context
class PrivacyPolicyCreateOrUpdateView(LoginRequiredMixin, generic.View):
# Set the page_name and resource
page_name = resource_action.RESOURCE_MANAGE_CMS
resource = resource_action.RESOURCE_MANAGE_CMS
# Initialize the action as ACTION_CREATE (can change based on logic)
action = resource_action.ACTION_CREATE # Default action
template_name = "manage_cms/privacy_policy_edit.html"
model = Organization
form_class = PrivacyPolicyForm
success_url = reverse_lazy("manage_cms:privacy_policy_view")
error_message = "An error occurred while saving the data."
# Determine the success message dynamically based on whether it's an update or create
def get_success_message(self):
self.success_message = (
constants.RECORD_CREATED if not self.object else constants.RECORD_UPDATED
)
return self.success_message
# Get the object (if exists) based on URL parameter 'pk'
def get_object(self):
return self.model.objects.only("privacy_policy").first()
# Add page_name and operation to the context
def get_context_data(self, **kwargs):
context = {
"page_name": self.page_name,
"operation": "Add" if not self.object else "Edit",
}
context.update(kwargs) # Include any additional context data passed to the view
return context
def get(self, request, *args, **kwargs):
self.object = self.get_object()
# If an object is found, change action to ACTION_UPDATE
if self.object is not None:
self.action = resource_action.ACTION_UPDATE
form = self.form_class(instance=self.object)
context = self.get_context_data(form=form)
return render(request, self.template_name, context=context)
def post(self, request, *args, **kwargs):
self.object = self.get_object()
# If an object is found, change action to ACTION_UPDATE
if self.object is not None:
self.action = resource_action.ACTION_UPDATE
form = self.form_class(request.POST, instance=self.object)
if not form.is_valid():
print(form.errors)
context = self.get_context_data(form=form)
return render(request, self.template_name, context=context)
form.save()
messages.success(self.request, self.get_success_message())
return redirect(self.success_url)
class TestimonialListView(LoginRequiredMixin, generic.TemplateView):
page_name = resource_action.RESOURCE_MANAGE_CMS
template_name = "manage_cms/testimonial_list.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["page_name"] = self.page_name
return context
class OrganizationView(LoginRequiredMixin, generic.DetailView):
page_name = resource_action.RESOURCE_MANAGE_CMS
resource = resource_action.RESOURCE_MANAGE_CMS
action = resource_action.ACTION_READ
model = Organization
template_name = "manage_cms/organization_view.html"
context_object_name = "organization_obj"
def get_object(self, queryset=None):
return self.model.objects.first()
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["page_name"] = self.page_name
return context
class OrganizationCreateOrUpdateView(LoginRequiredMixin, generic.View):
# Set the page_name and resource
page_name = resource_action.RESOURCE_MANAGE_CMS
resource = resource_action.RESOURCE_MANAGE_CMS
# Initialize the action as ACTION_CREATE (can change based on logic)
action = resource_action.ACTION_CREATE # Default action
template_name = "manage_cms/organization_add.html"
model = Organization
form_class = OrganizationForm
success_url = reverse_lazy("manage_cms:organization_view")
error_message = "An error occurred while saving the data."
# Determine the success message dynamically based on whether it's an update or create
def get_success_message(self):
self.success_message = (
constants.RECORD_CREATED if not self.object else constants.RECORD_UPDATED
)
return self.success_message
# Get the object (if exists) based on URL parameter 'pk'
def get_object(self):
return self.model.objects.first()
# Add page_name and operation to the context
def get_context_data(self, **kwargs):
context = {
"page_name": self.page_name,
"operation": "Add" if not self.object else "Edit",
}
context.update(kwargs) # Include any additional context data passed to the view
return context
def get(self, request, *args, **kwargs):
self.object = self.get_object()
# If an object is found, change action to ACTION_UPDATE
if self.object is not None:
self.action = resource_action.ACTION_UPDATE
form = self.form_class(instance=self.object)
context = self.get_context_data(form=form)
return render(request, self.template_name, context=context)
def post(self, request, *args, **kwargs):
self.object = self.get_object()
# If an object is found, change action to ACTION_UPDATE
if self.object is not None:
self.action = resource_action.ACTION_UPDATE
form = self.form_class(request.POST, instance=self.object)
if not form.is_valid():
print(form.errors)
context = self.get_context_data(form=form)
return render(request, self.template_name, context=context)
form.save()
messages.success(self.request, self.get_success_message())
return redirect(self.success_url)
class EducationView(LoginRequiredMixin, generic.ListView):
page_name = resource_action.RESOURCE_MANAGE_CMS
resource = resource_action.RESOURCE_MANAGE_CMS
action = resource_action.ACTION_READ
model = Education
template_name = "manage_cms/education_view.html"
context_object_name = "education_obj"
def get_queryset(self):
return (
super()
.get_queryset()
.prefetch_related("tags")
.filter(deleted=False)
)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["page_name"] = self.page_name
context["video_obj"] = self.get_queryset().filter(content_type=Education.VIDEO)
context["material_obj"] = self.get_queryset().filter(content_type=Education.MATERIAL)
print("video data", context["video_obj"])
return context
class EducationCreateOrUpdateView(LoginRequiredMixin, generic.View):
# Set the page_name and resource
page_name = resource_action.RESOURCE_MANAGE_CMS
resource = resource_action.RESOURCE_MANAGE_CMS
# Initialize the action as ACTION_CREATE (can change based on logic)
action = resource_action.ACTION_CREATE # Default action
page_title = None
template_name = "manage_cms/education_add.html"
model = Education
form_class = None
success_url = reverse_lazy("manage_cms:education_view")
# Determine the success message dynamically based on whether it's an update or create
def get_success_message(self):
self.success_message = (
constants.RECORD_CREATED if not self.object else constants.RECORD_UPDATED
)
return self.success_message
# Get the object (if exists) based on URL parameter 'pk
def get_object(self):
pk = self.kwargs.get("pk")
return get_object_or_404(self.model, pk=pk) if pk else None
# Add page_name and operation to the context
def get_context_data(self, **kwargs):
context = {
"page_name": self.page_name,
"page_title": self.page_title,
"operation": "Add" if not self.object else "Edit",
}
context.update(kwargs) # Include any additional context data passed to the view
return context
def get(self, request, *args, **kwargs):
self.object = self.get_object()
# If an object is found, change action to ACTION_UPDATE
if self.object is not None:
self.action = resource_action.ACTION_UPDATE
form = self.form_class(instance=self.object)
context = self.get_context_data(form=form)
return render(request, self.template_name, context=context)
def post(self, request, *args, **kwargs):
self.object = self.get_object()
# print("response of image file", request.POST, request.FILES)
# If an object is found, change action to ACTION_UPDATE
if self.object is not None:
self.action = resource_action.ACTION_UPDATE
print(f"published date {request.POST.get('published_at')}")
form = self.form_class(request.POST, request.FILES, instance=self.object)
if not form.is_valid():
print(form.errors)
context = self.get_context_data(form=form)
return render(request, self.template_name, context=context)
form.save()
messages.success(self.request, self.get_success_message())
return redirect(self.success_url)
class EducationVideoCreateOrUpdateView(EducationCreateOrUpdateView):
page_title = "Video"
form_class = EducationVideoForm
class EducationMaterialCreateOrUpdateView(EducationCreateOrUpdateView):
page_title = "Material"
form_class = EducationMaterialForm

View File

View File

@@ -0,0 +1,31 @@
from django.contrib import admin
from .models import TicketIssueType, TicketAttachment, Tickets
class TicketIssueTypeAdmin(admin.ModelAdmin):
list_display = ("id", "name")
search_fields = ("name",)
class TicketAttachmentAdmin(admin.ModelAdmin):
list_display = ("id", "image")
search_fields = ("image",)
class TicketsAdmin(admin.ModelAdmin):
list_display = (
"id",
"issuetype",
"principal",
"subject",
"ticket_status",
"is_stopped",
)
list_filter = ("issuetype", "ticket_status", "is_stopped")
search_fields = ("subject", "description")
filter_horizontal = ("attachments",) # For the many-to-many relationship
admin.site.register(TicketIssueType, TicketIssueTypeAdmin)
admin.site.register(TicketAttachment, TicketAttachmentAdmin)
admin.site.register(Tickets, TicketsAdmin)

View File

@@ -0,0 +1,84 @@
import re
from rest_framework import serializers
from accounts.models import IAmPrincipal
from goodtimes import constants
from manage_communications.models import ContactUs, Feedback, TicketIssueType, TicketAttachment, Tickets
from goodtimes import date_utils
class ContactUsSerializer(serializers.ModelSerializer):
class Meta:
model = ContactUs
fields = "__all__"
class TicketIssueTypeSerializer(serializers.ModelSerializer):
class Meta:
model = TicketIssueType
fields = ["id", "name"]
class TicketAttachmentSerializer(serializers.ModelSerializer):
class Meta:
model = TicketAttachment
fields = ["image"]
# def get_image_url(self, obj, field_name, request):
# image_field = getattr(obj, field_name)
# if image_field:
# return request.build_absolute_uri(image_field.url)
# return ""
# def to_representation(self, instance):
# data = super().to_representation(instance)
# request = self.context.get("request")
# data["image"] = self.get_image_url(instance, "image", request)
# return data
class TicketStatusSerializer(serializers.Serializer):
ticket_status = serializers.ChoiceField(choices=Tickets.TICKET_STATUS)
class TicketsSerializer(serializers.ModelSerializer):
issuetype = serializers.PrimaryKeyRelatedField(queryset=TicketIssueType.objects.all())
attachments = TicketAttachmentSerializer(many=True, read_only=True)
created_on = serializers.SerializerMethodField()
class Meta:
model = Tickets
fields = [
"id",
"issuetype",
"principal",
"subject",
"description",
"ticket_status",
"attachments",
"is_stopped",
"created_on",
]
def get_created_on(self, obj):
return date_utils.format_date_to_string(obj.created_on)
class FeedbackSerializer(serializers.ModelSerializer):
RATING_CHOICES = Feedback.RATING_CHOICES
rating = serializers.ChoiceField(choices=RATING_CHOICES)
feedback_reaction = serializers.SerializerMethodField()
class Meta:
model = Feedback
fields = ['principal', 'email', 'comment', 'rating', 'feedback_reaction']
def get_feedback_reaction(self, obj):
rating = obj.rating
if rating == 1:
return Feedback.VERY_BAD
elif rating == 2:
return Feedback.POOR
elif rating == 3:
return Feedback.MEDIUM
elif rating == 4:
return Feedback.GOOD
elif rating == 5:
return Feedback.EXCELLENT
else:
return None

View File

@@ -0,0 +1,15 @@
from django.urls import path
from . import views
app_name = "manage_communication_api"
urlpatterns = [
path('contact-us/', views.ContactUsView.as_view(), name='contact_us_mobile'),
path('ticket/', views.TicketView.as_view(), name='ticket'),
path('ticket/issue_category/', views.TicketCategoryView.as_view(), name='ticket_issue'),
path('ticket/stop/<int:pk>/', views.TicketStopView.as_view(), name='ticket_stop'),
path('feedback/', views.FeedbackView.as_view(), name='feedback_mobile'),
]

View File

@@ -0,0 +1,165 @@
from django.db import transaction
from django.utils import timezone
from rest_framework import status
from rest_framework.views import APIView
from . import serializers
from accounts.models import IAmPrincipal
from ..models import Tickets, TicketAttachment, TicketIssueType
from goodtimes import constants
from goodtimes.services import SMSError, SMSService
from manage_communications import models
# from nifty11_project.services import SMSError, SMSService
from goodtimes.utils import ApiResponse
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework_simplejwt.authentication import JWTAuthentication
from datetime import datetime
class ContactUsView(APIView):
authentication_classes = []
permission_classes = []
def post(self, request):
print("request.data: ", request.data)
serializer = serializers.ContactUsSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return ApiResponse.success(data=serializer.data, message=constants.SUCCESS)
class TicketView(APIView):
serializer_class = serializers.TicketsSerializer
model = Tickets
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
def get_queryset(self):
return Tickets.objects.filter(deleted=False)
def get(self, request):
month = request.GET.get("month")
status_filter = request.GET.get("status")
tickets = self.get_queryset()
if month:
try:
month_date = datetime.strptime(month, "%Y-%m")
print(f"month_Date is {month_date} , month is {month_date.month} , year is {month_date.year}, ticket is {tickets}")
tickets = tickets.filter(created_on__year=month_date.year)
except ValueError:
return ApiResponse.error(message="Invalid date format. Use YYYY-MM", errors="Invalid date format. Use YYYY-MM")
if status_filter:
tickets = tickets.filter(ticket_status=status_filter)
serializer = self.serializer_class(tickets, many=True)
ticket_status = [Tickets.REQUESTED, Tickets.VIEWED, Tickets.IN_PROGRESS, Tickets.RESOLVED]
print(f"ticket status is {ticket_status}")
data = {
"ticket_status": ticket_status,
"ticket": serializer.data
}
return ApiResponse.success(message=constants.SUCCESS, data=data)
def post(self, request):
data = request.data.copy()
print(f"your data is {request.data}")
attachments_data = data.pop("attachments", [])
serializer = self.serializer_class(data=request.data)
if not serializer.is_valid():
error_response = {
"status": status.HTTP_400_BAD_REQUEST,
"message": constants.VALIDATION_ERROR,
"errors": serializer.errors,
}
return ApiResponse.error(**error_response)
try:
ticket = serializer.save(principal=request.user)
attachments = []
for attachment_data in attachments_data:
attachment = TicketAttachment(image=attachment_data)
attachment.save()
attachments.append(attachment)
ticket.attachments.add(*attachments)
except Exception as e:
error_response = {
"status": status.HTTP_500_INTERNAL_SERVER_ERROR,
"message": constants.SOMETHING_WRONG,
"errors": str(e),
}
return ApiResponse.error(**error_response)
return ApiResponse.success(message=constants.SUCCESS)
class TicketCategoryView(APIView):
model = TicketIssueType
serializer_class = serializers.TicketIssueTypeSerializer
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
def get(self, request):
issue_type = self.model.objects.filter(deleted=False)
print(f"issue type {issue_type}")
serializer = self.serializer_class(issue_type, many=True)
return ApiResponse.success(message=constants.SUCCESS, data=serializer.data)
class TicketStopView(APIView):
model = Tickets
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
def get(self, request, pk):
try:
ticket = self.model.objects.get(pk=pk)
ticket.stop_ticket()
return ApiResponse.success(message=constants.SUCCESS)
except Exception as e:
error_message = {
"status": status.HTTP_404_NOT_FOUND,
"message": constants.RECORD_NOT_FOUND,
"errors": str(e)
}
return ApiResponse.error(**error_message)
class FeedbackView(APIView):
serializer_class = serializers.FeedbackSerializer
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
def post(self, request):
data = request.data.copy()
serializer = self.serializer_class(data=request.data)
if not serializer.is_valid():
error_response = {
"status": status.HTTP_400_BAD_REQUEST,
"message": constants.VALIDATION_ERROR,
"errors": serializer.errors,
}
return ApiResponse.error(**error_response)
try:
serializer.save(principal=request.user)
except Exception as e:
error_response = {
"status": status.HTTP_500_INTERNAL_SERVER_ERROR,
"message": constants.SOMETHING_WRONG,
"errors": str(e),
}
return ApiResponse.error(**error_response)
return ApiResponse.success(message=constants.SUCCESS)

View File

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

View File

@@ -0,0 +1,320 @@
# Generated by Django 5.0.2 on 2024-02-29 07:47
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name="ContactUs",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("active", models.BooleanField(default=True)),
("deleted", models.BooleanField(default=False)),
("created_on", models.DateTimeField(auto_now_add=True)),
("modified_on", models.DateTimeField(auto_now=True)),
("name", models.CharField(max_length=100, null=True)),
("email_address", models.EmailField(max_length=254)),
("mobile_number", models.CharField(max_length=15)),
("subject", models.CharField(max_length=200)),
("message", models.TextField()),
("reply", models.TextField(blank=True, null=True)),
(
"created_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="%(class)s_created",
to=settings.AUTH_USER_MODEL,
),
),
(
"modified_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="%(class)s_modified",
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"db_table": "contact_us",
},
),
migrations.CreateModel(
name="Feedback",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("active", models.BooleanField(default=True)),
("deleted", models.BooleanField(default=False)),
("created_on", models.DateTimeField(auto_now_add=True)),
("modified_on", models.DateTimeField(auto_now=True)),
(
"email",
models.EmailField(
blank=True,
help_text="Email address of the feedback provider",
max_length=254,
null=True,
),
),
("comment", models.TextField(help_text="Feedback comment")),
(
"rating",
models.PositiveSmallIntegerField(
choices=[
(1, "1 Star"),
(2, "2 Stars"),
(3, "3 Stars"),
(4, "4 Stars"),
(5, "5 Stars"),
],
help_text="Rating provided by the user",
),
),
(
"feedback_reaction",
models.CharField(
blank=True,
choices=[
("Very Bad", "Very Bad"),
("Poor", "Poor"),
("Medium", "Medium"),
("Good", "Good"),
("Excellent", "Excellent"),
],
help_text="Reaction associated with the feedback",
max_length=20,
null=True,
),
),
(
"created_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="%(class)s_created",
to=settings.AUTH_USER_MODEL,
),
),
(
"modified_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="%(class)s_modified",
to=settings.AUTH_USER_MODEL,
),
),
(
"principal",
models.ForeignKey(
blank=True,
help_text="User associated with this feedback",
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="feedbacks",
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"db_table": "feedback",
},
),
migrations.CreateModel(
name="TicketAttachment",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("active", models.BooleanField(default=True)),
("deleted", models.BooleanField(default=False)),
("created_on", models.DateTimeField(auto_now_add=True)),
("modified_on", models.DateTimeField(auto_now=True)),
("image", models.ImageField(upload_to="ticket_attachment")),
(
"created_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="%(class)s_created",
to=settings.AUTH_USER_MODEL,
),
),
(
"modified_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="%(class)s_modified",
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"db_table": "ticket_attachment",
},
),
migrations.CreateModel(
name="TicketIssueType",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("active", models.BooleanField(default=True)),
("deleted", models.BooleanField(default=False)),
("created_on", models.DateTimeField(auto_now_add=True)),
("modified_on", models.DateTimeField(auto_now=True)),
("name", models.CharField(max_length=100)),
(
"created_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="%(class)s_created",
to=settings.AUTH_USER_MODEL,
),
),
(
"modified_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="%(class)s_modified",
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"db_table": "ticket_issue_type",
},
),
migrations.CreateModel(
name="Tickets",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("active", models.BooleanField(default=True)),
("deleted", models.BooleanField(default=False)),
("created_on", models.DateTimeField(auto_now_add=True)),
("modified_on", models.DateTimeField(auto_now=True)),
("subject", models.CharField(max_length=100)),
("description", models.TextField()),
(
"ticket_status",
models.CharField(
choices=[
("Requested", "Requested"),
("Viewed", "Viewed"),
("In Progress", "In Progress"),
("Resolved", "Resolved"),
],
default="Requested",
help_text="Ticket status",
max_length=20,
),
),
("is_stopped", models.BooleanField(default=True)),
(
"attachments",
models.ManyToManyField(
related_name="tickets_attachments",
to="manage_communications.ticketattachment",
),
),
(
"created_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="%(class)s_created",
to=settings.AUTH_USER_MODEL,
),
),
(
"issuetype",
models.ForeignKey(
on_delete=django.db.models.deletion.DO_NOTHING,
to="manage_communications.ticketissuetype",
),
),
(
"modified_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="%(class)s_modified",
to=settings.AUTH_USER_MODEL,
),
),
(
"principal",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.DO_NOTHING,
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"db_table": "tickets",
},
),
]

View File

@@ -0,0 +1,127 @@
from django.db import models
from accounts.models import BaseModel, IAmPrincipal, IAmPrincipalType
class ContactUs(BaseModel):
name = models.CharField(max_length=100, null=True)
email_address = models.EmailField()
mobile_number = models.CharField(max_length=15)
subject = models.CharField(max_length=200)
message = models.TextField()
reply = models.TextField(blank=True, null=True)
class Meta:
db_table = "contact_us"
class TicketIssueType(BaseModel):
name = models.CharField(max_length=100)
class Meta:
db_table = "ticket_issue_type"
class TicketAttachment(BaseModel):
image = models.ImageField(upload_to="ticket_attachment")
class Meta:
db_table = "ticket_attachment"
class Tickets(BaseModel):
REQUESTED = "Requested"
VIEWED = "Viewed"
IN_PROGRESS = "In Progress"
RESOLVED = "Resolved"
TICKET_STATUS = (
(REQUESTED, "Requested"),
(VIEWED, "Viewed"),
(IN_PROGRESS, "In Progress"),
(RESOLVED, "Resolved"),
)
issuetype = models.ForeignKey(TicketIssueType, on_delete=models.DO_NOTHING)
principal = models.ForeignKey(IAmPrincipal, on_delete=models.DO_NOTHING, null=True, blank=True)
subject = models.CharField(max_length=100)
description = models.TextField()
ticket_status = models.CharField(
max_length=20,
choices=TICKET_STATUS,
default=REQUESTED,
help_text="Ticket status",
)
attachments = models.ManyToManyField(TicketAttachment, related_name="tickets_attachments")
is_stopped = models.BooleanField(default=True)
class Meta:
db_table = "tickets"
def __str__(self):
return f"{self.subject}"
def mark_as_viewed(self):
self.ticket_status = self.VIEWED
self.save()
def mark_as_in_progress(self):
self.ticket_status = self.IN_PROGRESS
self.save()
def mark_as_resolved(self):
self.ticket_status = self.RESOLVED
self.save()
def stop_ticket(self):
self.is_stopped = True
self.ticket_status = self.RESOLVED
self.save()
class Feedback(BaseModel):
VERY_BAD = "Very Bad"
POOR = "Poor"
MEDIUM = "Medium"
GOOD = "Good"
EXCELLENT = "Excellent"
RATING_CHOICES = (
(1, "1 Star"),
(2, "2 Stars"),
(3, "3 Stars"),
(4, "4 Stars"),
(5, "5 Stars"),
)
FEEDBACK_REACTION = (
(VERY_BAD, "Very Bad"),
(POOR, "Poor"),
(MEDIUM, "Medium"),
(GOOD, "Good"),
(EXCELLENT, "Excellent"),
)
principal = models.ForeignKey(
IAmPrincipal,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="feedbacks",
help_text="User associated with this feedback",
)
email = models.EmailField(null=True, blank=True, help_text="Email address of the feedback provider")
comment = models.TextField(help_text="Feedback comment")
rating = models.PositiveSmallIntegerField(choices=RATING_CHOICES, help_text="Rating provided by the user")
feedback_reaction = models.CharField(
max_length=20,
choices=FEEDBACK_REACTION,
null=True,
blank=True,
help_text="Reaction associated with the feedback",
)
class Meta:
db_table = "feedback"
def __str__(self):
return f"Author: {self.principal}, Comment: {self.comment}, Rating: {self.rating}"
def get_rating_display(self):
for value, label in self.RATING_CHOICES:
if value == self.rating:
return label
return ""

View File

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

View File

@@ -0,0 +1,15 @@
from django.urls import path
from . import views
app_name = "manage_communications"
urlpatterns = [
path('contact_us/', views.ContactUsListView.as_view(), name='contact_us_list'),
path('contact_us/reply/', views.ContactUsReplyView.as_view(), name='contact_us_reply'),
path('tickets/', views.TicketListView.as_view(), name='ticket_list'),
path('feedback/', views.FeedbackListView.as_view(), name='feedback_list'),
path('feedback/delete/<int:pk>', views.FeedbackDeleteView.as_view(), name='feedback_delete'),
]

View File

@@ -0,0 +1,132 @@
from django.contrib.auth.mixins import LoginRequiredMixin
from accounts import resource_action
from django.views import generic
from .models import Feedback, ContactUs, Tickets
from goodtimes import constants
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse_lazy
from django.contrib import messages
# Create your views here.
class ContactUsListView(LoginRequiredMixin, generic.ListView):
page_name = resource_action.RESOURCE_MANAGE_CONTACT_US
resource = resource_action.RESOURCE_MANAGE_CONTACT_US
action = resource_action.ACTION_READ
model = ContactUs
template_name = "manage_communications/contact_us_list.html"
context_object_name = "contact_us_obj"
def get_queryset(self):
return super().get_queryset().filter(deleted=False)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["page_name"] = self.page_name
return context
class ContactUsReplyView(LoginRequiredMixin, generic.View):
page_name = resource_action.RESOURCE_MANAGE_CONTACT_US
model = ContactUs
success_message = constants.DATA_SAVED
success_url = reverse_lazy("manage_communications:contact_us_list")
def post(self, request):
id = request.POST.get("id")
message = request.POST.get("message")
if id or message:
try:
instance = self.model.objects.get(id=id)
instance.reply = message
instance.save()
messages.success(request, self.success_message)
except self.model.DoesNotExist:
messages.error(request, "Contact Us entry not found")
except Exception as e:
messages.error(request, str(e))
else:
messages.error(request, "Missing 'id' or 'message' in the request")
# Redirect to the desired URL after form submission
return redirect(self.success_url)
class ContactUsCreateOrUpdateView(LoginRequiredMixin, generic.TemplateView):
page_name = resource_action.RESOURCE_MANAGE_CONTACT_US
template_name = "manage_communications/contact_us_list.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["page_name"] = self.page_name
return context
class TicketsListView(LoginRequiredMixin, generic.ListView):
page_name = resource_action.RESOURCE_MANAGE_TICKET
resource = resource_action.RESOURCE_MANAGE_TICKET
action = resource_action.ACTION_READ
model = ContactUs
template_name = "manage_communications/ticket_list.html"
context_object_name = "objs"
def get_queryset(self):
return super().get_queryset().filter(deleted=False)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["page_name"] = self.page_name
return context
class TicketListView(LoginRequiredMixin, generic.ListView):
page_name = resource_action.RESOURCE_MANAGE_CONTACT_US
resource = resource_action.RESOURCE_MANAGE_CONTACT_US
action = resource_action.ACTION_READ
model = ContactUs
template_name = "manage_communications/contact_us_list.html"
context_object_name = "contact_us_obj"
def get_queryset(self):
return super().get_queryset().filter(deleted=False)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["page_name"] = self.page_name
return context
class FeedbackListView(LoginRequiredMixin, generic.ListView):
page_name = resource_action.RESOURCE_MANAGE_FEEDBACK
resource = resource_action.RESOURCE_MANAGE_FEEDBACK
action = resource_action.ACTION_READ
model = Feedback
template_name = "manage_communications/feedback_list.html"
context_object_name = "feedback_obj"
def get_queryset(self):
return super().get_queryset().select_related("principal").filter(deleted=False)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["page_name"] = self.page_name
return context
class FeedbackDeleteView(LoginRequiredMixin, generic.View):
page_name = resource_action.RESOURCE_MANAGE_FEEDBACK
resource = resource_action.RESOURCE_MANAGE_FEEDBACK
action = resource_action.ACTION_DELETE
model = Feedback
success_url = reverse_lazy("manage_communications:feedback_list")
success_message = constants.RECORD_DELETED
error_message = constants.RECORD_NOT_FOUND
def get(self, request, pk):
try:
type_obj = self.model.objects.get(id=pk)
type_obj.deleted = True
type_obj.save()
messages.success(request, self.success_message)
except self.model.DoesNotExist:
messages.success(request, self.error_message)
return redirect(self.success_url)

View File

24
manage_events/admin.py Normal file
View File

@@ -0,0 +1,24 @@
from django.contrib import admin
from .models import EventCategory, Venue, EventMaster
# Register your models here.
class EventCategoryAdmin(admin.ModelAdmin):
list_display = ('title', 'description')
search_fields = ('title', 'description')
list_filter = ('title',)
class VenueAdmin(admin.ModelAdmin):
list_display = ('title', 'address', 'latitude', 'longitude')
search_fields = ('title', 'address')
list_filter = ('title',)
class EventMasterAdmin(admin.ModelAdmin):
list_display = ('title', 'event_category', 'description')
search_fields = ('title', 'description')
list_filter = ('event_category', 'title')
admin.site.register(EventCategory, EventCategoryAdmin)
admin.site.register(Venue, VenueAdmin)
admin.site.register(EventMaster, EventMasterAdmin)

View File

@@ -0,0 +1,165 @@
from rest_framework import serializers
from accounts.models import IAmPrincipalLocation
from manage_events.models import (
EventMaster,
Event,
EventCategory,
EventImage,
Venue,
PrincipalPreference,
)
class EventImageSerializer(serializers.ModelSerializer):
class Meta:
model = EventImage
fields = ["image"]
class EventDetailSerializer(serializers.ModelSerializer):
images = serializers.SerializerMethodField()
class Meta:
model = Event
fields = [
"id",
"title",
"description",
"start_date",
"end_date",
"from_time",
"to_time",
"category",
"venue",
"venue_capacity",
"image",
"video_url",
"entry_type",
"entry_fee",
"key_guest",
"age_group",
"images",
]
def get_images(self, obj):
images = (
obj.event_images.all()
) # Ensure this uses the correct related_name from your model
return EventImageSerializer(images, many=True, context=self.context).data
class CreateEventSerializer(serializers.ModelSerializer):
images = serializers.ListField(
child=serializers.ImageField(), write_only=True, required=False
)
class Meta:
model = Event
fields = [
"id",
"images",
"title",
"description",
"image",
"start_date",
"end_date",
"from_time",
"to_time",
"category",
"venue_capacity",
"video_url",
"entry_type",
"entry_fee",
"key_guest",
"age_group",
"draft",
"venue",
]
def create(self, validated_data):
images_data = validated_data.pop("images", None)
event = Event.objects.create(**validated_data)
if images_data:
for image_data in images_data:
EventImage.objects.create(event=event, image=image_data)
return event
class CreateVenueSerializer(serializers.ModelSerializer):
class Meta:
model = Venue
fields = [
"title",
"description",
"address",
"image",
"url",
"latitude",
"longitude",
]
class EventCategorySerializer(serializers.ModelSerializer):
class Meta:
model = EventCategory
fields = ["id", "title", "image", "description", "video_url"]
class IAmPrincipalLocationSerializer(serializers.ModelSerializer):
class Meta:
model = IAmPrincipalLocation
fields = ["latitude", "longitude"]
def create(self, validated_data):
principal = self.context["request"].user
return IAmPrincipalLocation.objects.create(
principal=principal, **validated_data
)
class VenueSerializer(serializers.ModelSerializer):
class Meta:
model = Venue
fields = "__all__"
read_only_fields = ("created_by",)
class PrincipalPreferenceSerializer(serializers.ModelSerializer):
preferred_categories = serializers.PrimaryKeyRelatedField(
many=True, queryset=EventCategory.objects.all()
)
class Meta:
model = PrincipalPreference
fields = ["preferred_categories"] # Removed 'principal' from fields
def create(self, validated_data):
# Accessing the principal from the context
user = self.context["request"].user
preferred_categories = validated_data.pop("preferred_categories")
principal_preference, created = PrincipalPreference.objects.update_or_create(
principal=user, defaults={}
)
principal_preference.preferred_categories.set(preferred_categories)
return principal_preference
def update(self, instance, validated_data):
instance.preferred_categories.set(
validated_data.get(
"preferred_categories", instance.preferred_categories.all()
)
)
instance.save()
return instance
class EventMasterSearchSerializer(serializers.Serializer):
title = serializers.CharField(max_length=255)
class EventMasterSerializer(serializers.ModelSerializer):
class Meta:
model = EventMaster
fields = "__all__"

59
manage_events/api/urls.py Normal file
View File

@@ -0,0 +1,59 @@
from django.urls import path
from . import views
app_name = "manage_events_api"
urlpatterns = [
path(
"add-event/",
views.CreateEventApi.as_view(),
name="add_event",
),
path(
"get-events/<str:filter>/",
views.EventsAPIView.as_view(),
name="get_events",
),
path(
"event/<int:pk>/",
views.EventDetailAPIView.as_view(),
name="get_event",
),
path(
"add-venue/",
views.CreateVenueApi.as_view(),
name="add_venue",
),
path(
"get-venue/",
views.VenueListView.as_view(),
name="get_venue",
),
path("event-master/search/", views.EventMasterSearchAPIView.as_view(), name="event_master_search"),
# Others
path("geocode/", views.GeocodeAPIView.as_view(), name="geocode_api"),
# All Preferences List
path(
"event-categories/",
views.EventCategoryListAPIView.as_view(),
name="event-category-list",
),
# Add Principal Preferences
path(
"add-principal-preferences/",
views.PrincipalPreferenceView.as_view(),
name="principal_preferences",
),
# Principal Preference List
path(
"principal-preferences/",
views.PrincipalPreferenceDetailView.as_view(),
name="principal-preferences",
),
# Principal Location
path(
"add-location/",
views.IAmPrincipalLocationAPIView.as_view(),
name="add_location",
),
]

394
manage_events/api/views.py Normal file
View File

@@ -0,0 +1,394 @@
from datetime import timedelta
from django.db import transaction
from django.utils import timezone
from rest_framework import status, generics
from rest_framework.views import APIView
from django.conf import settings
from accounts.models import IAmPrincipalLocation
from accounts.permission import IsOwnerOrReadOnly
from goodtimes import constants
from django.db.models import Q
from goodtimes import services
from goodtimes.utils import ApiResponse, CapacityError
from rest_framework.permissions import IsAuthenticated
from rest_framework_simplejwt.authentication import JWTAuthentication
from manage_events.api.serializers import (
EventMasterSearchSerializer,
EventMasterSerializer,
CreateEventSerializer,
CreateVenueSerializer,
EventCategorySerializer,
EventDetailSerializer,
IAmPrincipalLocationSerializer,
PrincipalPreferenceSerializer,
VenueSerializer,
)
from manage_events.models import EventMaster, Event, EventCategory, PrincipalPreference, Venue
import requests
class CreateEventApi(APIView):
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
def post(self, request):
serializer = CreateEventSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save(created_by=self.request.user)
# Add additional logic for handling other relationships (e.g., Venue)
return ApiResponse.success(
status=status.HTTP_201_CREATED,
message=constants.SUCCESS,
data=serializer.data,
)
class CreateVenueApi(APIView):
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
def post(self, request):
serializer = VenueSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save(created_by=self.request.user, active=True)
# Add additional logic for handling other relationships (e.g., Venue)
return ApiResponse.success(
status=status.HTTP_201_CREATED,
message=constants.SUCCESS,
data=serializer.data,
)
class EventsAPIView(APIView):
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
def get(self, request, filter, *args, **kwargs):
today = timezone.now().date()
params = ["expensive", "cheap", "preference", "today", "tomorrow"]
if filter not in params:
return ApiResponse.error(
status=status.HTTP_400_BAD_REQUEST,
message=constants.FAILURE,
errors="No filter found",
)
try:
if filter == "expensive":
# Constructing a complex query using Q objects
current_and_future_events_query = Q(active=True, deleted=False) & (
Q(start_date__lte=today, end_date__gte=today)
| Q(start_date__gt=today) # Current events # Future events
)
current_and_future_events = Event.objects.filter(
current_and_future_events_query
).order_by("-entry_fee")
serializer = EventDetailSerializer(current_and_future_events, many=True)
return ApiResponse.success(
status=status.HTTP_200_OK,
message=constants.SUCCESS,
data=serializer.data,
)
elif filter == "cheap":
current_and_future_events_query = Q(active=True, deleted=False) & (
Q(start_date__lte=today, end_date__gte=today)
| Q(start_date__gt=today) # Current events # Future events
)
current_and_future_events = Event.objects.filter(
current_and_future_events_query
).order_by("entry_fee")
serializer = EventDetailSerializer(current_and_future_events, many=True)
return ApiResponse.success(
status=status.HTTP_200_OK,
message=constants.SUCCESS,
data=serializer.data,
)
elif filter == "preference":
preferences = PrincipalPreference.objects.get(principal=request.user)
preferred_categories_ids = preferences.preferred_categories.values_list(
"id", flat=True
)
# Filter events based on user preferences and that are upcoming or ongoing
preference_events = Event.objects.filter(
category__in=preferred_categories_ids, end_date__gte=today
).distinct()
serializer = EventDetailSerializer(preference_events, many=True)
return ApiResponse.success(
status=status.HTTP_200_OK,
message=constants.SUCCESS,
data=serializer.data,
)
except Exception as e:
return ApiResponse.error(
status=status.HTTP_400_BAD_REQUEST,
message=constants.FAILURE,
errors=str(e),
)
class PrinciaplPreferenceEventsAPIView(APIView):
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
def get(self, request, *args, **kwargs):
today = timezone.now().date()
try:
preferences = PrincipalPreference.objects.get(principal=request.user)
preferred_categories_ids = preferences.preferred_categories.values_list(
"id", flat=True
)
# Filter events based on user preferences and that are upcoming or ongoing
events = Event.objects.filter(
category__in=preferred_categories_ids, end_date__gte=today
).distinct()
if not events.exists():
# If no events found based on preferences, get future events
events = Event.objects.filter(start_date__gt=today)
serializer = EventDetailSerializer(events, many=True)
return ApiResponse.success(
status=status.HTTP_200_OK,
message=constants.SUCCESS,
data=serializer.data,
)
except PrincipalPreference.DoesNotExist:
# If the user has no preferences, default to future events
events = Event.objects.filter(start_date__gt=today)
class EventDetailAPIView(APIView):
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
def get(self, request, pk):
try:
event = Event.objects.get(pk=pk)
interaction = services.InteractionCalculator(event)
interactions = interaction.calculate()
# Serialize your event data here
event_data = EventDetailSerializer(event).data
event_data["interactions"] = interactions
return ApiResponse.success(
status=status.HTTP_200_OK,
message=constants.SUCCESS,
data=event_data,
)
except Event.DoesNotExist:
return ApiResponse.error(
status=status.HTTP_404_NOT_FOUND,
message=constants.FAILURE,
errors=constants.RECORD_NOT_FOUND,
)
except CapacityError as e:
return ApiResponse.error(
status=status.HTTP_405_METHOD_NOT_ALLOWED,
message=constants.FAILURE,
errors=str(e),
)
class VenueListView(generics.ListAPIView):
serializer_class = VenueSerializer
permission_classes = [IsAuthenticated]
def get_queryset(self):
# Ensures that a user sees only their venues
return Venue.objects.filter(created_by=self.request.user)
def list(self, request, *args, **kwargs):
queryset = self.get_queryset()
serializer = self.get_serializer(queryset, many=True)
# Customizing the response format
return ApiResponse.success(
status=status.HTTP_200_OK,
message=constants.SUCCESS,
data=serializer.data,
)
class VenueDetailView(generics.RetrieveUpdateDestroyAPIView):
queryset = Venue.objects.all()
serializer_class = VenueSerializer
permission_classes = [IsAuthenticated, IsOwnerOrReadOnly]
def get_queryset(self):
# This ensures a user can only access their own venues
return self.queryset.filter(created_by=self.request.user)
class GeocodeAPIView(APIView):
authentication_classes = []
permission_classes = []
"""
API View to fetch latitude and longitude for a given address using the Google Maps Geocoding API.
"""
def get(self, request, *args, **kwargs):
# Extract 'address' from query parameters
address = request.query_params.get("address")
print("address: ", address)
if not address:
return ApiResponse.error(
{"error": "Address parameter is missing."},
status=status.HTTP_400_BAD_REQUEST,
)
# Call the get_lat_long method
lat_long = self.get_lat_long(address)
if lat_long:
return ApiResponse.success(
{"latitude": lat_long[0], "longitude": lat_long[1]}
)
else:
return ApiResponse.error(
{"error": "Failed to fetch latitude and longitude."},
status=status.HTTP_404_NOT_FOUND,
)
def get_lat_long(self, address):
"""
Fetches latitude and longitude for a given address using the Google Maps Geocoding API.
"""
url = "https://maps.googleapis.com/maps/api/geocode/json?"
params = {
"address": address,
# "key": settings.GOOGLE_MAPS_API_KEY, # Replace with your actual API key
"key": "AIzaSyCQv-Cfzkh3cXerrui55oId7CDHhuIImhc", # Replace with your actual API key
}
response = requests.get(url, params=params)
if response.status_code == 200:
data = response.json()
if data["status"] == "OK":
location = data["results"][0]["geometry"]["location"]
latitude = location["lat"]
longitude = location["lng"]
return latitude, longitude
else:
print(f"Geocoding failed: {data['status']}")
return None
else:
print(f"API request failed with status code: {response.status_code}")
return None
class EventCategoryListAPIView(generics.ListAPIView):
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
queryset = EventCategory.objects.all()
serializer_class = EventCategorySerializer
class IAmPrincipalLocationAPIView(APIView):
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
def get(self, request, *args, **kwargs):
# Assuming there's a UserLocation model that stores user locations,
# and it has a ForeignKey to the user model.
try:
user_location = IAmPrincipalLocation.objects.filter(principal=request.user).last()
serializer = IAmPrincipalLocationSerializer(user_location)
return ApiResponse.success(
status=status.HTTP_200_OK,
message=constants.SUCCESS,
data=serializer.data,
)
except IAmPrincipalLocation.DoesNotExist:
return ApiResponse.error(
status=status.HTTP_404_NOT_FOUND,
message="Location not found for the user.",
errors="No location data available for the current user.",
)
def post(self, request, *args, **kwargs):
serializer = IAmPrincipalLocationSerializer(
data=request.data, context={"request": request}
)
if serializer.is_valid():
serializer.save()
return ApiResponse.success(
status=status.HTTP_200_OK,
message=constants.SUCCESS,
data=serializer.data,
)
return ApiResponse.error(
status=status.HTTP_400_BAD_REQUEST,
message=constants.FAILURE,
errors=serializer.errors,
)
class PrincipalPreferenceView(APIView):
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
def post(self, request, *args, **kwargs):
serializer = PrincipalPreferenceSerializer(
data=request.data, context={"request": request}
)
if serializer.is_valid():
serializer.save()
return ApiResponse.success(
message=constants.SUCCESS,
data=serializer.data,
status=status.HTTP_201_CREATED,
)
return ApiResponse.error(
message=constants.FAILURE,
errors=serializer.errors,
status=status.HTTP_400_BAD_REQUEST,
)
class PrincipalPreferenceDetailView(generics.RetrieveAPIView):
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
serializer_class = PrincipalPreferenceSerializer
def get_object(self):
# Attempt to retrieve the authenticated user's preferences
user = self.request.user
obj, created = PrincipalPreference.objects.get_or_create(principal=user)
return obj
class EventMasterSearchAPIView(APIView):
def post(self, request, *args, **kwargs):
serializer = EventMasterSearchSerializer(data=request.data)
if serializer.is_valid():
title = serializer.validated_data.get("title")
# Search for existing brands
existing_event_master = EventMaster.objects.filter(title__icontains=title)
if existing_event_master.exists():
# Return existing brands
return ApiResponse.success(
data=EventMasterSerializer(existing_event_master, many=True).data,
message=constants.SUCCESS,
status=status.HTTP_200_OK,
)
else:
# No brand found, return an empty list
return ApiResponse.success(
data=[],
message="No related EventMaster Found",
status=status.HTTP_200_OK,
)
return ApiResponse.error(
message=constants.FAILURE,
errors=serializer.errors,
status=status.HTTP_400_BAD_REQUEST,
)

6
manage_events/apps.py Normal file
View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class ManageEventsConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "manage_events"

72
manage_events/forms.py Normal file
View File

@@ -0,0 +1,72 @@
from django import forms
from manage_events.models import EventMaster, Event, EventCategory
class EventCategoryForm(forms.ModelForm):
# image = forms.ImageField()
class Meta:
model = EventCategory
fields = ["title", "image", "description", "video_url"]
widgets = {
"image": forms.FileInput(attrs={"class": "form-control-file"}),
}
class EventForm(forms.ModelForm):
class Meta:
model = Event
fields = [
"title",
"event_master",
"description",
"image",
"status",
"start_date",
"end_date",
"from_time",
"to_time",
"venue",
"venue_capacity",
"video_url",
"entry_type",
"entry_fee",
"key_guest",
"age_group",
"draft",
]
widgets = {
"title": forms.TextInput(attrs={"class": "form-control"}),
"description": forms.Textarea(attrs={"class": "form-control", "rows": 4}),
"status": forms.Select(attrs={"class": "form-control"}),
"start_date": forms.DateInput(
attrs={"class": "form-control", "type": "date"}
),
"end_date": forms.DateInput(
attrs={"class": "form-control", "type": "date"}
),
"from_time": forms.TimeInput(
attrs={"class": "form-control", "type": "time"}
),
"to_time": forms.TimeInput(attrs={"class": "form-control", "type": "time"}),
"venue_capacity": forms.NumberInput(attrs={"class": "form-control"}),
"video_url": forms.URLInput(attrs={"class": "form-control"}),
"entry_type": forms.TextInput(attrs={"class": "form-control"}),
"entry_fee": forms.NumberInput(attrs={"class": "form-control"}),
"key_guest": forms.Textarea(attrs={"class": "form-control", "rows": 3}),
"age_group": forms.TextInput(attrs={"class": "form-control"}),
"draft": forms.CheckboxInput(attrs={"class": "form-check-input"}),
# For the 'image' field, you might not need to specify a widget since the default is appropriate.
# However, if you want to add specific classes or attributes, you can do it like this:
"image": forms.FileInput(attrs={"class": "form-control-file"}),
# For ForeignKey fields like 'EventMaster' and 'venue', Django uses a select widget by default.
# You can customize it further if needed:
"event_master": forms.Select(attrs={"class": "form-control"}),
"venue": forms.Select(attrs={"class": "form-control"}),
}
class EventMasterForm(forms.ModelForm):
class Meta:
model = EventMaster
fields = "__all__" # Includes all fields from the model

View File

@@ -0,0 +1,378 @@
# Generated by Django 5.0.2 on 2024-02-29 07:47
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name="EventCategory",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("active", models.BooleanField(default=True)),
("deleted", models.BooleanField(default=False)),
("created_on", models.DateTimeField(auto_now_add=True)),
("modified_on", models.DateTimeField(auto_now=True)),
("title", models.CharField(max_length=255)),
(
"image",
models.ImageField(
blank=True, null=True, upload_to="event_category"
),
),
("description", models.TextField(blank=True, null=True)),
("video_url", models.URLField(blank=True, null=True)),
(
"created_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="%(class)s_created",
to=settings.AUTH_USER_MODEL,
),
),
(
"modified_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="%(class)s_modified",
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"abstract": False,
},
),
migrations.CreateModel(
name="Event",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("active", models.BooleanField(default=True)),
("deleted", models.BooleanField(default=False)),
("created_on", models.DateTimeField(auto_now_add=True)),
("modified_on", models.DateTimeField(auto_now=True)),
("title", models.CharField(max_length=255)),
("description", models.TextField(blank=True, null=True)),
("image", models.ImageField(blank=True, null=True, upload_to="event")),
(
"status",
models.CharField(
blank=True,
choices=[
("upcoming", "Upcoming"),
("live", "Live"),
("post", "Post"),
("archive", "Archive"),
],
max_length=10,
null=True,
),
),
("start_date", models.DateField()),
("end_date", models.DateField()),
("from_time", models.TimeField()),
("to_time", models.TimeField()),
("venue_capacity", models.IntegerField()),
("video_url", models.URLField(blank=True, null=True)),
("entry_type", models.CharField(max_length=100)),
(
"entry_fee",
models.DecimalField(decimal_places=2, default=0.0, max_digits=14),
),
("key_guest", models.TextField(blank=True, null=True)),
("age_group", models.CharField(blank=True, max_length=100, null=True)),
("draft", models.BooleanField(default=False)),
(
"created_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="%(class)s_created",
to=settings.AUTH_USER_MODEL,
),
),
(
"modified_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="%(class)s_modified",
to=settings.AUTH_USER_MODEL,
),
),
(
"category",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="manage_events.eventcategory",
),
),
],
options={
"db_table": "event",
},
),
migrations.CreateModel(
name="EventImage",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("image", models.ImageField(upload_to="event_images/")),
(
"event",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="event_images",
to="manage_events.event",
),
),
],
),
migrations.CreateModel(
name="EventMaster",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("active", models.BooleanField(default=True)),
("deleted", models.BooleanField(default=False)),
("created_on", models.DateTimeField(auto_now_add=True)),
("modified_on", models.DateTimeField(auto_now=True)),
("title", models.CharField(max_length=255)),
("description", models.TextField(blank=True, null=True)),
("image", models.ImageField(blank=True, null=True, upload_to="brand")),
(
"created_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="%(class)s_created",
to=settings.AUTH_USER_MODEL,
),
),
(
"event_category",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="manage_events.eventcategory",
),
),
(
"modified_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="%(class)s_modified",
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"db_table": "brand",
},
),
migrations.AddField(
model_name="event",
name="event_master",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="manage_events.eventmaster",
),
),
migrations.CreateModel(
name="PrincipalPreference",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("active", models.BooleanField(default=True)),
("deleted", models.BooleanField(default=False)),
("created_on", models.DateTimeField(auto_now_add=True)),
("modified_on", models.DateTimeField(auto_now=True)),
(
"created_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="%(class)s_created",
to=settings.AUTH_USER_MODEL,
),
),
(
"modified_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="%(class)s_modified",
to=settings.AUTH_USER_MODEL,
),
),
(
"preferred_categories",
models.ManyToManyField(
related_name="preferred_by_users",
to="manage_events.eventcategory",
),
),
(
"principal",
models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"db_table": "user_preference",
},
),
migrations.CreateModel(
name="Venue",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("active", models.BooleanField(default=True)),
("deleted", models.BooleanField(default=False)),
("created_on", models.DateTimeField(auto_now_add=True)),
("modified_on", models.DateTimeField(auto_now=True)),
("title", models.CharField(max_length=255)),
("description", models.TextField(blank=True, null=True)),
("address", models.TextField(blank=True, null=True)),
("image", models.ImageField(blank=True, null=True, upload_to="venue")),
("url", models.URLField(blank=True, null=True)),
("latitude", models.DecimalField(decimal_places=8, max_digits=14)),
("longitude", models.DecimalField(decimal_places=8, max_digits=14)),
(
"created_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="%(class)s_created",
to=settings.AUTH_USER_MODEL,
),
),
(
"modified_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="%(class)s_modified",
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"abstract": False,
},
),
migrations.AddField(
model_name="event",
name="venue",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to="manage_events.venue"
),
),
migrations.CreateModel(
name="EventPrincipalInteraction",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"status",
models.CharField(
choices=[("going", "Going"), ("interested", "Interested")],
max_length=10,
),
),
(
"event",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="manage_events.event",
),
),
(
"principal",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"unique_together": {("principal", "event", "status")},
},
),
]

View File

122
manage_events/models.py Normal file
View File

@@ -0,0 +1,122 @@
from django.db import models
from accounts.models import BaseModel, IAmPrincipal
from django.db import transaction
# from django.contrib.gis.db import models as gis_models
# Create your models here.
class EventCategory(BaseModel):
title = models.CharField(max_length=255)
image = models.ImageField(upload_to="event_category", null=True, blank=True)
description = models.TextField(null=True, blank=True)
video_url = models.URLField(max_length=200, blank=True, null=True)
def __str__(self):
return self.title
class Venue(BaseModel):
title = models.CharField(max_length=255)
description = models.TextField(null=True, blank=True)
address = models.TextField(null=True, blank=True)
image = models.ImageField(upload_to="venue", null=True, blank=True)
url = models.URLField(max_length=200, blank=True, null=True)
latitude = models.DecimalField(max_digits=14, decimal_places=8)
longitude = models.DecimalField(max_digits=14, decimal_places=8)
def __str__(self):
return self.title
class EventStatus(models.TextChoices):
UPCOMING = "upcoming", "Upcoming"
LIVE = "live", "Live"
POST = "post", "Post"
ARCHIVE = "archive", "Archive"
class EventMaster(BaseModel):
title = models.CharField(max_length=255)
description = models.TextField(blank=True, null=True)
event_category = models.ForeignKey(EventCategory, on_delete=models.CASCADE)
image = models.ImageField(upload_to="brand", null=True, blank=True)
def __str__(self):
return self.title
class Meta:
db_table = "brand"
class Event(BaseModel):
title = models.CharField(max_length=255)
category = models.ForeignKey(EventCategory, on_delete=models.CASCADE)
event_master = models.ForeignKey(EventMaster, on_delete=models.SET_NULL, null=True, blank=True)
description = models.TextField(blank=True, null=True)
image = models.ImageField(upload_to="event", null=True, blank=True)
status = models.CharField(
max_length=10, choices=EventStatus.choices, blank=True, null=True
)
start_date = models.DateField()
end_date = models.DateField()
from_time = models.TimeField()
to_time = models.TimeField()
venue = models.ForeignKey(Venue, on_delete=models.CASCADE)
venue_capacity = models.IntegerField()
video_url = models.URLField(max_length=200, blank=True, null=True)
entry_type = models.CharField(
max_length=100
) # Assuming entry type is a string (e.g., Free, Ticketed)
entry_fee = models.DecimalField(
max_digits=14, decimal_places=2, default=0.00
) # Assuming it's an integer. Use DecimalField if you need to handle cents.
key_guest = models.TextField(blank=True, null=True)
age_group = models.CharField(max_length=100, blank=True, null=True)
draft = models.BooleanField(default=False)
def __str__(self):
return self.title
class Meta:
db_table = "event" # Optional: Specify custom table name
class EventInteractionType(models.TextChoices):
GOING = "going", "Going"
INTERESTED = "interested", "Interested"
class EventImage(models.Model):
event = models.ForeignKey(
Event, related_name="event_images", on_delete=models.CASCADE
)
image = models.ImageField(upload_to="event_images/")
def __str__(self):
return f"Image for event: {self.event.title}"
class EventPrincipalInteraction(models.Model):
principal = models.ForeignKey(IAmPrincipal, on_delete=models.CASCADE)
event = models.ForeignKey(Event, on_delete=models.CASCADE)
status = models.CharField(
max_length=10,
choices=EventInteractionType.choices,
)
class Meta:
unique_together = ("principal", "event", "status")
class PrincipalPreference(BaseModel):
principal = models.OneToOneField(IAmPrincipal, on_delete=models.CASCADE)
preferred_categories = models.ManyToManyField(
EventCategory, related_name="preferred_by_users"
)
def __str__(self):
return str(self.preferred_categories)
class Meta:
db_table = "user_preference"

Some files were not shown because too many files have changed in this diff Show More