wrong commit
This commit is contained in:
148
.gitignore
vendored
Normal file
148
.gitignore
vendored
Normal 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
0
accounts/__init__.py
Normal file
93
accounts/admin.py
Normal file
93
accounts/admin.py
Normal 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)
|
||||
42
accounts/api/authenticate.py
Normal file
42
accounts/api/authenticate.py
Normal 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
262
accounts/api/serializers.py
Normal 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
35
accounts/api/urls.py
Normal 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
111
accounts/api/utils.py
Normal 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
740
accounts/api/views.py
Normal 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
6
accounts/apps.py
Normal 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
49
accounts/backend.py
Normal 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
|
||||
47
accounts/context_processors.py
Normal file
47
accounts/context_processors.py
Normal 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
272
accounts/fixture_script.py
Normal 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],
|
||||
},
|
||||
},
|
||||
]
|
||||
305
accounts/fixtures/resource_action_fixture.json
Normal file
305
accounts/fixtures/resource_action_fixture.json
Normal 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
321
accounts/forms.py
Normal 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
86
accounts/google_login.py
Normal 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
|
||||
52
accounts/management/commands/load_custom_fixture.py
Normal file
52
accounts/management/commands/load_custom_fixture.py
Normal 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
16
accounts/middleware.py
Normal 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
|
||||
|
||||
760
accounts/migrations/0001_initial.py
Normal file
760
accounts/migrations/0001_initial.py
Normal 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",
|
||||
),
|
||||
),
|
||||
]
|
||||
0
accounts/migrations/__init__.py
Normal file
0
accounts/migrations/__init__.py
Normal file
448
accounts/models.py
Normal file
448
accounts/models.py
Normal 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
64
accounts/permission.py
Normal 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
|
||||
34
accounts/resource_action.py
Normal file
34
accounts/resource_action.py
Normal 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"
|
||||
27
accounts/templatetags/custom_permissions.py
Normal file
27
accounts/templatetags/custom_permissions.py
Normal 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
3
accounts/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
53
accounts/urls.py
Normal file
53
accounts/urls.py
Normal 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
13
accounts/utils.py
Normal 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
633
accounts/views.py
Normal 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
0
chat/__init__.py
Normal file
15
chat/admin.py
Normal file
15
chat/admin.py
Normal 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
23
chat/api/serializers.py
Normal 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
17
chat/api/urls.py
Normal 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
108
chat/api/views.py
Normal 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
6
chat/apps.py
Normal 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
127
chat/consumers.py
Normal 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
|
||||
62
chat/migrations/0001_initial.py
Normal file
62
chat/migrations/0001_initial.py
Normal 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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
0
chat/migrations/__init__.py
Normal file
0
chat/migrations/__init__.py
Normal file
31
chat/models.py
Normal file
31
chat/models.py
Normal 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
8
chat/routing.py
Normal 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
3
chat/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
9
chat/urls.py
Normal file
9
chat/urls.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from django.urls import path
|
||||
from . import views
|
||||
|
||||
app_name = "chat"
|
||||
|
||||
urlpatterns = [
|
||||
|
||||
|
||||
]
|
||||
3
chat/views.py
Normal file
3
chat/views.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
||||
0
dashboard/__init__.py
Normal file
0
dashboard/__init__.py
Normal file
3
dashboard/admin.py
Normal file
3
dashboard/admin.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
6
dashboard/apps.py
Normal file
6
dashboard/apps.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class DashboardConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'dashboard'
|
||||
0
dashboard/migrations/__init__.py
Normal file
0
dashboard/migrations/__init__.py
Normal file
3
dashboard/models.py
Normal file
3
dashboard/models.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
3
dashboard/tests.py
Normal file
3
dashboard/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
12
dashboard/urls.py
Normal file
12
dashboard/urls.py
Normal 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
16
dashboard/views.py
Normal 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
0
goodtimes/__init__.py
Normal file
16
goodtimes/asgi.py
Normal file
16
goodtimes/asgi.py
Normal 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
71
goodtimes/constants.py
Normal 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
64
goodtimes/date_utils.py
Normal 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
15
goodtimes/renderers.py
Normal 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
849
goodtimes/services.py
Normal 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
123
goodtimes/settings.py
Normal 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"
|
||||
24
goodtimes/settings/__init__.py
Normal file
24
goodtimes/settings/__init__.py
Normal 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
350
goodtimes/settings/base.py
Normal 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,
|
||||
},
|
||||
}
|
||||
55
goodtimes/settings/development.py
Normal file
55
goodtimes/settings/development.py
Normal 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")]
|
||||
77
goodtimes/settings/production.py
Normal file
77
goodtimes/settings/production.py
Normal 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")]
|
||||
75
goodtimes/settings/staging.py
Normal file
75
goodtimes/settings/staging.py
Normal 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
65
goodtimes/urls.py
Normal 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
66
goodtimes/utils.py
Normal 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
16
goodtimes/wsgi.py
Normal 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
22
manage.py
Normal 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
0
manage_cms/__init__.py
Normal file
34
manage_cms/admin.py
Normal file
34
manage_cms/admin.py
Normal 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)
|
||||
|
||||
119
manage_cms/api/serializers.py
Normal file
119
manage_cms/api/serializers.py
Normal 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
17
manage_cms/api/urls.py
Normal 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
158
manage_cms/api/views.py
Normal 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
6
manage_cms/apps.py
Normal 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
216
manage_cms/forms.py
Normal 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")
|
||||
436
manage_cms/migrations/0001_initial.py
Normal file
436
manage_cms/migrations/0001_initial.py
Normal 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",
|
||||
},
|
||||
),
|
||||
]
|
||||
0
manage_cms/migrations/__init__.py
Normal file
0
manage_cms/migrations/__init__.py
Normal file
159
manage_cms/models.py
Normal file
159
manage_cms/models.py
Normal 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
3
manage_cms/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
52
manage_cms/urls.py
Normal file
52
manage_cms/urls.py
Normal 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
879
manage_cms/views.py
Normal 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
|
||||
0
manage_communications/__init__.py
Normal file
0
manage_communications/__init__.py
Normal file
31
manage_communications/admin.py
Normal file
31
manage_communications/admin.py
Normal 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)
|
||||
84
manage_communications/api/serializers.py
Normal file
84
manage_communications/api/serializers.py
Normal 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
|
||||
15
manage_communications/api/urls.py
Normal file
15
manage_communications/api/urls.py
Normal 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'),
|
||||
|
||||
|
||||
]
|
||||
165
manage_communications/api/views.py
Normal file
165
manage_communications/api/views.py
Normal 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)
|
||||
6
manage_communications/apps.py
Normal file
6
manage_communications/apps.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class ManageCommunicationsConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'manage_communications'
|
||||
320
manage_communications/migrations/0001_initial.py
Normal file
320
manage_communications/migrations/0001_initial.py
Normal 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",
|
||||
},
|
||||
),
|
||||
]
|
||||
0
manage_communications/migrations/__init__.py
Normal file
0
manage_communications/migrations/__init__.py
Normal file
127
manage_communications/models.py
Normal file
127
manage_communications/models.py
Normal 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 ""
|
||||
3
manage_communications/tests.py
Normal file
3
manage_communications/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
15
manage_communications/urls.py
Normal file
15
manage_communications/urls.py
Normal 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'),
|
||||
|
||||
]
|
||||
132
manage_communications/views.py
Normal file
132
manage_communications/views.py
Normal 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)
|
||||
0
manage_events/__init__.py
Normal file
0
manage_events/__init__.py
Normal file
24
manage_events/admin.py
Normal file
24
manage_events/admin.py
Normal 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)
|
||||
165
manage_events/api/serializers.py
Normal file
165
manage_events/api/serializers.py
Normal 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
59
manage_events/api/urls.py
Normal 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
394
manage_events/api/views.py
Normal 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
6
manage_events/apps.py
Normal 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
72
manage_events/forms.py
Normal 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
|
||||
378
manage_events/migrations/0001_initial.py
Normal file
378
manage_events/migrations/0001_initial.py
Normal 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")},
|
||||
},
|
||||
),
|
||||
]
|
||||
0
manage_events/migrations/__init__.py
Normal file
0
manage_events/migrations/__init__.py
Normal file
122
manage_events/models.py
Normal file
122
manage_events/models.py
Normal 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
Reference in New Issue
Block a user