feat(module 1): add customer, non transferlist, import , export
This commit is contained in:
@@ -6,6 +6,7 @@ from rest_framework import serializers
|
||||
from accounts.models import (
|
||||
AppVersion,
|
||||
IAmPrincipal,
|
||||
IAmPrincipalExtendedData,
|
||||
IAmPrincipalType,
|
||||
# IAmPrincipalKYCDetails,
|
||||
)
|
||||
@@ -348,4 +349,10 @@ class AppVersionSerializer(serializers.ModelSerializer):
|
||||
"version",
|
||||
"force_upgrade",
|
||||
"recommend_upgrade",
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
class IAmPrincipalExtendedDataSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = IAmPrincipalExtendedData
|
||||
fields = "__all__"
|
||||
@@ -38,5 +38,6 @@ urlpatterns = [
|
||||
name="delete_account",
|
||||
),
|
||||
path('version-check/', views.VersionCheck.as_view(), name='version_check'),
|
||||
path('transfer-check/', views.AccountTransferCheckView.as_view(), name='transfer_check'),
|
||||
|
||||
]
|
||||
|
||||
@@ -17,6 +17,7 @@ from django.views.decorators.http import require_http_methods
|
||||
from accounts.models import (
|
||||
AppVersion,
|
||||
IAmPrincipal,
|
||||
IAmPrincipalExtendedData,
|
||||
IAmPrincipalOtp,
|
||||
IAmPrincipalSource,
|
||||
IAmPrincipalType,
|
||||
@@ -41,6 +42,7 @@ from .serializers import (
|
||||
# RegistrationPasswordSerializer,
|
||||
# PhoneSerializer,
|
||||
EmailSerializer,
|
||||
IAmPrincipalExtendedDataSerializer,
|
||||
PlayerIDSerializer,
|
||||
RegistrationPasswordSerializer,
|
||||
RegistrationSerializer,
|
||||
@@ -971,3 +973,44 @@ class VersionCheck(APIView):
|
||||
version_data = AppVersionSerializer(version)
|
||||
|
||||
return ApiResponse.success(message=constants.SUCCESS, data=version_data.data)
|
||||
|
||||
|
||||
class AccountTransferCheckView(APIView):
|
||||
authentication_classes = [JWTAuthentication]
|
||||
permission_classes = [IsAuthenticated]
|
||||
model = IAmPrincipalExtendedData
|
||||
serializer_class = IAmPrincipalExtendedDataSerializer
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
print("request.user is ", request.user)
|
||||
try:
|
||||
obj = IAmPrincipalExtendedData.objects.get(principal=request.user)
|
||||
except Exception as e:
|
||||
error_response = {
|
||||
"status": status.HTTP_404_NOT_FOUND,
|
||||
"message": constants.RECORD_NOT_FOUND,
|
||||
"errors": str(e),
|
||||
}
|
||||
return ApiResponse.error(**error_response)
|
||||
|
||||
serializer = self.serializer_class(obj)
|
||||
print("serializer data", serializer)
|
||||
return ApiResponse.success(message=constants.SUCCESS, data=serializer.data)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
try:
|
||||
obj = IAmPrincipalExtendedData.objects.get(principal=request.user)
|
||||
except Exception as e:
|
||||
error_response = {
|
||||
"status": status.HTTP_404_NOT_FOUND,
|
||||
"message": constants.RECORD_NOT_FOUND,
|
||||
"errors": str(e),
|
||||
}
|
||||
return ApiResponse.error(**error_response)
|
||||
|
||||
obj.pwd_changed_post_transfer = True
|
||||
obj.save()
|
||||
serializer = self.serializer_class(obj)
|
||||
return ApiResponse.success(message=constants.SUCCESS, data=serializer.data)
|
||||
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ from django.core import validators
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from goodtimes import constants
|
||||
from manage_events.models import EventCategory
|
||||
|
||||
from . import models
|
||||
# from .backend import EmailBackend
|
||||
@@ -357,4 +358,63 @@ class IAmPrincipalResourceLinkForm(IAmPrincipalForm):
|
||||
principal.principal_resource.set(principal_resource_data)
|
||||
# Save the instance to the database
|
||||
if commit:
|
||||
principal.save()
|
||||
principal.save()
|
||||
|
||||
|
||||
class CreateCustomerForm(forms.Form):
|
||||
first_name = forms.CharField(max_length=255, required=True, label='First Name')
|
||||
last_name = forms.CharField(max_length=255, required=True, label='Last Name')
|
||||
email = forms.EmailField(required=True, label='Email')
|
||||
preferences = forms.ModelMultipleChoiceField(
|
||||
queryset=EventCategory.objects.all(),
|
||||
widget=forms.widgets.SelectMultiple(
|
||||
attrs={"class": "form_select js-example-basic-multiple"}
|
||||
),
|
||||
required=False,
|
||||
label='Preferences'
|
||||
)
|
||||
free_start_date = forms.DateField(
|
||||
required=True,
|
||||
label=_('Free period start date'),
|
||||
help_text=_('Enter the start date of the free period')
|
||||
)
|
||||
free_end_date = forms.DateField(
|
||||
required=True,
|
||||
label=_('Free period end date'),
|
||||
help_text=_('Enter the end date of the free period')
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['preferences'].queryset = EventCategory.objects.all()
|
||||
|
||||
class UpdateCustomerForm(forms.Form):
|
||||
first_name = forms.CharField(max_length=255, required=True, label='First Name')
|
||||
last_name = forms.CharField(max_length=255, required=True, label='Last Name')
|
||||
email = forms.EmailField(required=True, label='Email', widget=forms.TextInput(attrs={'readonly': 'readonly'}))
|
||||
preferences = forms.ModelMultipleChoiceField(
|
||||
queryset=EventCategory.objects.all(),
|
||||
widget=forms.widgets.SelectMultiple(
|
||||
attrs={"class": "form_select js-example-basic-multiple"}
|
||||
),
|
||||
required=False,
|
||||
label='Preferences'
|
||||
)
|
||||
free_start_date = forms.DateField(
|
||||
required=True,
|
||||
label=_('Free period start date'),
|
||||
help_text=_('Enter the start date of the free period')
|
||||
)
|
||||
free_end_date = forms.DateField(
|
||||
required=True,
|
||||
label=_('Free period end date'),
|
||||
help_text=_('Enter the end date of the free period')
|
||||
)
|
||||
active = forms.BooleanField(required=False, label='Active', help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['preferences'].queryset = EventCategory.objects.all()
|
||||
|
||||
class UploadExcelForm(forms.Form):
|
||||
file = forms.FileField()
|
||||
|
||||
28
accounts/migrations/0012_iamprincipalextendeddata.py
Normal file
28
accounts/migrations/0012_iamprincipalextendeddata.py
Normal file
@@ -0,0 +1,28 @@
|
||||
# Generated by Django 5.0.2 on 2024-06-25 07:28
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('accounts', '0011_alter_iamprincipallocation_principal'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='IAmPrincipalExtendedData',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('is_onboarded', models.BooleanField(default=False, help_text='Indicates whether the user was onboarded by an admin.')),
|
||||
('is_transferred', models.BooleanField(default=False, help_text='Indicates whether the account has been transferred to the user.')),
|
||||
('transferred_on', models.DateTimeField(blank=True, help_text='The date and time when the account was transferred to the user.', null=True)),
|
||||
('principal', models.OneToOneField(help_text='The principal user to which this extended data is related.', on_delete=django.db.models.deletion.CASCADE, related_name='extended_data', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'db_table': 'iam_principal_extended_data',
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.0.2 on 2024-06-27 08:11
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('accounts', '0012_iamprincipalextendeddata'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='iamprincipalextendeddata',
|
||||
name='pwd_changed_post_transfer',
|
||||
field=models.BooleanField(default=False, help_text='Indicates if the user changed their password after the account was transferred.'),
|
||||
),
|
||||
]
|
||||
@@ -333,6 +333,39 @@ class IAmPrincipal(AbstractUser):
|
||||
return f"{self.email}"
|
||||
|
||||
|
||||
class IAmPrincipalExtendedData(models.Model):
|
||||
principal = models.OneToOneField(
|
||||
IAmPrincipal,
|
||||
related_name="extended_data",
|
||||
on_delete=models.CASCADE,
|
||||
help_text="The principal user to which this extended data is related."
|
||||
)
|
||||
is_onboarded = models.BooleanField(
|
||||
default=False,
|
||||
help_text="Indicates whether the user was onboarded by an admin."
|
||||
)
|
||||
is_transferred = models.BooleanField(
|
||||
default=False,
|
||||
help_text="Indicates whether the account has been transferred to the user."
|
||||
)
|
||||
transferred_on = models.DateTimeField(
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="The date and time when the account was transferred to the user."
|
||||
)
|
||||
pwd_changed_post_transfer = models.BooleanField(default=False, help_text="Indicates if the user changed their password after the account was transferred.")
|
||||
|
||||
class Meta:
|
||||
db_table = "iam_principal_extended_data"
|
||||
|
||||
def __str__(self):
|
||||
return f"Extended Data for {self.principal}"
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if self.is_transferred and self.transferred_on is None:
|
||||
self.transferred_on = datetime.datetime.now()
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
class IAmPrincipalResourceLink(models.Model):
|
||||
principal = models.ForeignKey(
|
||||
IAmPrincipal,
|
||||
|
||||
@@ -42,6 +42,14 @@ urlpatterns = [
|
||||
path('principal/role/delete/<int:pk>/', views.AppRoleDeleteView.as_view(), name="role_delete"),
|
||||
|
||||
path('customer/', views.CustomerListView.as_view(), name="customer_list"),
|
||||
path('customer/add/', views.CustomerCreateView.as_view(), name="customer_add"),
|
||||
path('customer/edit/<int:pk>/', views.CustomerUpdateView.as_view(), name="customer_edit"),
|
||||
path('customer/detail/<int:pk>/', views.CustomerDetailView.as_view(), name="customer_detail"),
|
||||
path('customer/transfer/<int:pk>/', views.CustomerTransferView.as_view(), name="customer_transfer"),
|
||||
path('customer/check-email/', views.CustomerCheckEmail.as_view(), name="customer_check_email"),
|
||||
path('customer/download-excel-template/', views.export_excel_template, name='download_excel_template'),
|
||||
path('customer/import-customer-data/', views.CustomerImportView.as_view(), name='import_customer_data'),
|
||||
path('customer/export-customer-data/', views.CustomerExportView.as_view(), name='export_customer_data'),
|
||||
|
||||
|
||||
# ignore this to path this for example setup
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import logging
|
||||
from django.conf import settings
|
||||
from django.db.models import Count, Q
|
||||
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.hashers import make_password
|
||||
from django.contrib.auth.views import (
|
||||
LoginView,
|
||||
PasswordResetCompleteView,
|
||||
@@ -13,6 +15,7 @@ from django.contrib.auth.views import (
|
||||
PasswordResetView,
|
||||
)
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.http import JsonResponse
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.urls import reverse_lazy
|
||||
from django.views import generic
|
||||
@@ -20,9 +23,15 @@ from django.db import models, transaction, IntegrityError
|
||||
from django.utils import timezone
|
||||
from accounts import permission
|
||||
from goodtimes import constants
|
||||
from goodtimes.services import EmailService
|
||||
from goodtimes.utils import JsonResponseUtil
|
||||
from manage_events.models import EventCategory, PrincipalPreference
|
||||
from manage_referrals.models import ReferralCode
|
||||
from manage_subscriptions.models import PrincipalSubscription, Subscription
|
||||
from . import resource_action
|
||||
|
||||
from .forms import (
|
||||
CreateCustomerForm,
|
||||
CustomAuthenticationForm,
|
||||
IAmPrincipalForm,
|
||||
IAmPrincipalGroupRoleLinkForm,
|
||||
@@ -30,9 +39,12 @@ from .forms import (
|
||||
IAmPrincipalRoleAppResourceActionLinkForm,
|
||||
IAmPrincipalGroupLinkForm,
|
||||
ProfileEditForm,
|
||||
UpdateCustomerForm,
|
||||
UploadExcelForm,
|
||||
)
|
||||
from .models import (
|
||||
IAmPrincipal,
|
||||
IAmPrincipalExtendedData,
|
||||
IAmPrincipalType,
|
||||
IAmAppResourceActionLink,
|
||||
IAmPrincipalGroup,
|
||||
@@ -557,6 +569,193 @@ class AppRoleDeleteView(LoginRequiredMixin, generic.View):
|
||||
|
||||
"""Customer"""
|
||||
|
||||
class CustomerCheckEmail(generic.View):
|
||||
model = IAmPrincipal
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
email = request.POST.get('email')
|
||||
print("check email is cllaed ", email)
|
||||
if self.model.objects.filter(email=email).exists():
|
||||
print("exist called")
|
||||
return JsonResponse({'message': 'This email address is already in use.'}, status=400)
|
||||
else:
|
||||
print("email is valid")
|
||||
return JsonResponse({'message': 'Email is available.'}, status=200)
|
||||
|
||||
|
||||
class CustomerCreateView(LoginRequiredMixin, generic.View):
|
||||
page_name = resource_action.RESOURCE_MANAGE_CUSTOMER
|
||||
resource = resource_action.RESOURCE_MANAGE_CUSTOMER
|
||||
model = IAmPrincipal
|
||||
form_class = CreateCustomerForm
|
||||
template_name = "accounts/customer/customer_add.html"
|
||||
success_url = reverse_lazy("accounts:customer_list")
|
||||
success_message = "Saved Successfully"
|
||||
error_message = "An error occurred while saving the data."
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
"page_name": self.page_name,
|
||||
"operation": "Add",
|
||||
}
|
||||
context.update(kwargs) # Include any additional context data passed to the view
|
||||
return context
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
form = self.form_class()
|
||||
context = self.get_context_data(form=form)
|
||||
return render(request, self.template_name, context=context)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
print(request.POST)
|
||||
# return redirect(self.success_url)
|
||||
form = self.form_class(request.POST)
|
||||
if not form.is_valid():
|
||||
context = self.get_context_data(form=form)
|
||||
return render(request, self.template_name, context=context)
|
||||
try:
|
||||
with transaction.atomic():
|
||||
# save principal data
|
||||
principal_obj = IAmPrincipal.objects.create(
|
||||
email=form.cleaned_data.get('email'),
|
||||
first_name=form.cleaned_data.get('first_name'),
|
||||
last_name=form.cleaned_data.get('last_name'),
|
||||
password=make_password("goodtimes#2024"),
|
||||
username=form.cleaned_data.get("email"),
|
||||
email_verified=True,
|
||||
register_complete=True,
|
||||
principal_type=IAmPrincipalType.objects.get(name=resource_action.PRINCIPAL_TYPE_EVENT_MANAGER),
|
||||
)
|
||||
|
||||
# generate referralcode of manager
|
||||
ReferralCode.create_referral_code_for_user_manager(
|
||||
principal=principal_obj, principal_type=principal_obj.principal_type
|
||||
)
|
||||
|
||||
IAmPrincipalExtendedData.objects.create(
|
||||
principal=principal_obj,
|
||||
is_onboarded=True,
|
||||
)
|
||||
|
||||
# save principal preferences record
|
||||
principal_preference = PrincipalPreference.objects.create(principal=principal_obj)
|
||||
principal_preference.preferred_categories.set(form.cleaned_data.get("preferences"))
|
||||
|
||||
principal_subscription= PrincipalSubscription.objects.create(
|
||||
start_date=form.cleaned_data.get("free_start_date"),
|
||||
end_date=form.cleaned_data.get("free_end_date"),
|
||||
principal=principal_obj,
|
||||
grace_period_end_date=PrincipalSubscription.generate_grace_period_end_date(form.cleaned_data.get("free_end_date")),
|
||||
is_paid=True,
|
||||
subscription=Subscription.objects.filter(is_free=True, active=True).first()
|
||||
)
|
||||
|
||||
messages.success(self.request, constants.REGISTRATION_SUCCESS)
|
||||
return redirect(self.success_url)
|
||||
except Exception as e:
|
||||
messages.error(self.request, str(e))
|
||||
context = self.get_context_data(form=form)
|
||||
return render(request, self.template_name, context=context)
|
||||
|
||||
|
||||
class CustomerUpdateView(LoginRequiredMixin, generic.View):
|
||||
page_name = resource_action.RESOURCE_MANAGE_CUSTOMER
|
||||
resource = resource_action.RESOURCE_MANAGE_CUSTOMER
|
||||
model = IAmPrincipal
|
||||
form_class = UpdateCustomerForm
|
||||
template_name = "accounts/customer/customer_edit.html"
|
||||
success_url = reverse_lazy("accounts:customer_list")
|
||||
success_message = "Updated Successfully"
|
||||
error_message = "An error occurred while saving the data."
|
||||
|
||||
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):
|
||||
principal_id = kwargs.get("pk")
|
||||
principal_obj = IAmPrincipal.objects.get(pk=principal_id)
|
||||
|
||||
initial_data = {
|
||||
"first_name": principal_obj.first_name,
|
||||
"last_name": principal_obj.last_name,
|
||||
"email": principal_obj.email,
|
||||
"active": principal_obj.is_active
|
||||
}
|
||||
|
||||
try:
|
||||
principal_preference = PrincipalPreference.objects.get(principal=principal_obj)
|
||||
initial_data["preferences"] = list(principal_preference.preferred_categories.all().values_list("id", flat=True))
|
||||
except PrincipalPreference.DoesNotExist:
|
||||
initial_data["preferences"] = []
|
||||
|
||||
try:
|
||||
subscription = PrincipalSubscription.objects.filter(principal=principal_obj).latest("created_on")
|
||||
initial_data["free_start_date"] = subscription.start_date
|
||||
initial_data["free_end_date"] = subscription.end_date
|
||||
except PrincipalSubscription.DoesNotExist:
|
||||
initial_data["free_start_date"] = None
|
||||
initial_data["free_end_date"] = None
|
||||
|
||||
form = self.form_class(initial=initial_data)
|
||||
context = self.get_context_data(form=form, principal_obj=principal_obj)
|
||||
print("context dta is ", context)
|
||||
return render(request, self.template_name, context=context)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
customer_id = kwargs.get("pk")
|
||||
principal_obj = IAmPrincipal.objects.get(pk=customer_id)
|
||||
form = self.form_class(request.POST)
|
||||
if not form.is_valid():
|
||||
context = self.get_context_data(form=form)
|
||||
return render(request, self.template_name, context=context)
|
||||
try:
|
||||
with transaction.atomic():
|
||||
# update principal data
|
||||
principal_obj.first_name = form.cleaned_data.get('first_name')
|
||||
principal_obj.last_name = form.cleaned_data.get('last_name')
|
||||
principal_obj.save()
|
||||
|
||||
# update principal preferences record
|
||||
principal_preference, _ = PrincipalPreference.objects.get_or_create(principal=principal_obj)
|
||||
principal_preference.preferred_categories.set(form.cleaned_data.get("preferences"))
|
||||
|
||||
# update principal subscription record
|
||||
principal_subscription = PrincipalSubscription.objects.filter(principal=principal_obj).order_by('-end_date').first()
|
||||
if principal_subscription:
|
||||
principal_subscription.start_date = form.cleaned_data.get("free_start_date")
|
||||
principal_subscription.end_date = form.cleaned_data.get("free_end_date")
|
||||
principal_subscription.grace_period_end_date = form.cleaned_data.get("free_end_date") + datetime.timedelta(days=15)
|
||||
principal_subscription.save()
|
||||
else:
|
||||
PrincipalSubscription.objects.create(
|
||||
principal=principal_obj,
|
||||
start_date=form.cleaned_data.get("free_start_date"),
|
||||
end_date=form.cleaned_data.get("free_end_date"),
|
||||
grace_period_end_date=PrincipalSubscription.generate_grace_period_end_date(form.cleaned_data.get("free_end_date")),
|
||||
is_paid=True,
|
||||
subscription=Subscription.objects.filter().first() # Assuming you want to link a default subscription
|
||||
)
|
||||
|
||||
messages.success(self.request, self.success_message)
|
||||
return redirect(self.success_url)
|
||||
except Exception as e:
|
||||
messages.error(self.request, str(e))
|
||||
context = self.get_context_data(form=form)
|
||||
return render(request, self.template_name, context=context)
|
||||
|
||||
class CustomerDetailView(LoginRequiredMixin, generic.DetailView):
|
||||
template_name = 'accounts/customer/customer_detail.html'
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
principal_obj = IAmPrincipal.objects.get(pk=kwargs.get("pk"))
|
||||
principal_preference = PrincipalPreference.objects.get(principal_id=principal_obj.id)
|
||||
principal_subscription = PrincipalSubscription.objects.filter(principal=principal_obj).latest("start_date")
|
||||
return render(request, self.template_name, locals())
|
||||
|
||||
class CustomerListView(LoginRequiredMixin, generic.ListView):
|
||||
page_name = resource_action.RESOURCE_MANAGE_CUSTOMER
|
||||
@@ -570,7 +769,7 @@ class CustomerListView(LoginRequiredMixin, generic.ListView):
|
||||
queryset = (
|
||||
super()
|
||||
.get_queryset()
|
||||
.select_related("principal_type", "principal_source")
|
||||
.select_related("principal_type", "principal_source", "extended_data")
|
||||
.filter(
|
||||
models.Q(
|
||||
principal_type__name=resource_action.PRINCIPAL_TYPE_EVENT_MANAGER
|
||||
@@ -602,11 +801,311 @@ class CustomerListView(LoginRequiredMixin, generic.ListView):
|
||||
context["page_name"] = self.page_name
|
||||
return context
|
||||
|
||||
import pandas as pd
|
||||
from openpyxl import Workbook, load_workbook
|
||||
from openpyxl.worksheet.datavalidation import DataValidation
|
||||
from openpyxl.styles import Font
|
||||
from django.http import HttpResponse
|
||||
|
||||
# def export_excel_template(request):
|
||||
# # Define the columns and create an empty DataFrame
|
||||
# columns = ['First Name', 'Last Name', 'Email', 'Preferences', 'Free period start date', 'Free period end date']
|
||||
# df = pd.DataFrame(columns=columns)
|
||||
|
||||
# # Create a workbook and select the active worksheet
|
||||
# wb = Workbook()
|
||||
# ws = wb.active
|
||||
# ws.title = 'Customer Registration'
|
||||
|
||||
# # # Write the column headers
|
||||
# # for col_num, column_title in enumerate(df.columns, 1):
|
||||
# # cell = ws.cell(row=1, column=col_num, value=column_title)
|
||||
# # cell.font = Font(bold=True)
|
||||
|
||||
# # # Create a hidden sheet for preferences
|
||||
# # ws_prefs = wb.create_sheet(title="Preferences")
|
||||
# # ws_prefs.sheet_state = 'hidden'
|
||||
|
||||
# # # Fetch preferences options from the EventCategory model
|
||||
# # preferences_options = EventCategory.objects.values_list('title', flat=True)
|
||||
|
||||
# # # Write preferences to the hidden sheet
|
||||
# # for row_num, preference in enumerate(preferences_options, 1):
|
||||
# # ws_prefs.cell(row=row_num, column=1, value=preference)
|
||||
|
||||
# # # Define the range for preferences in the hidden sheet
|
||||
# # preferences_range = f"Preferences!$A$1:$A${len(preferences_options)}"
|
||||
|
||||
# # # Add Data Validation for preferences (drop-down list)
|
||||
# # dv_preferences = DataValidation(
|
||||
# # type="list",
|
||||
# # formula1=preferences_range,
|
||||
# # allow_blank=True,
|
||||
# # showDropDown=True
|
||||
# # )
|
||||
# # ws.add_data_validation(dv_preferences)
|
||||
# # dv_preferences.add(f'D2:D1048576') # Apply to the whole column
|
||||
|
||||
# # # Add Data Validation for date comparison
|
||||
# # dv_start_date = DataValidation(
|
||||
# # type="date",
|
||||
# # operator="greaterThan",
|
||||
# # formula1='"1900-01-01"',
|
||||
# # showErrorMessage=True
|
||||
# # )
|
||||
# # dv_end_date = DataValidation(
|
||||
# # type="custom",
|
||||
# # formula1="=AND(ISNUMBER(F2), F2>E2)",
|
||||
# # showErrorMessage=True,
|
||||
# # errorTitle="Invalid Date",
|
||||
# # error="End date must be greater than start date."
|
||||
# # )
|
||||
# # ws.add_data_validation(dv_start_date)
|
||||
# # ws.add_data_validation(dv_end_date)
|
||||
# # dv_start_date.add(f'E2:E1048576')
|
||||
# # dv_end_date.add(f'F2:F1048576')
|
||||
|
||||
# # Save the workbook to a bytes buffer
|
||||
# response = HttpResponse(content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
|
||||
# response['Content-Disposition'] = 'attachment; filename=customer_registration_template.xlsx'
|
||||
# wb.save(response)
|
||||
# return response
|
||||
|
||||
|
||||
def export_excel_template(request):
|
||||
# Define the columns and create an empty DataFrame
|
||||
columns = ['First Name', 'Last Name', 'Email', 'Preferences(should be seperated by comma)', 'Free period start date(YYYY-MM-DD)', 'Free period end date(YYYY-MM-DD)']
|
||||
df = pd.DataFrame(columns=columns)
|
||||
|
||||
# Create a workbook and select the active worksheet
|
||||
wb = Workbook()
|
||||
ws = wb.active
|
||||
ws.title = 'Customer Registration'
|
||||
|
||||
# Write the column headers
|
||||
for col_num, column_title in enumerate(df.columns, 1):
|
||||
cell = ws.cell(row=1, column=col_num, value=column_title)
|
||||
cell.font = Font(bold=True)
|
||||
|
||||
# Save the workbook to a bytes buffer
|
||||
response = HttpResponse(content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
|
||||
response['Content-Disposition'] = 'attachment; filename=customer_registration_template.xlsx'
|
||||
wb.save(response)
|
||||
return response
|
||||
|
||||
class CustomerTransferView(LoginRequiredMixin, generic.View):
|
||||
model = IAmPrincipal
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
try:
|
||||
principal_obj = self.model.objects.get(pk=kwargs.get("pk"))
|
||||
except self.model.DoesNotExist:
|
||||
messages.error(request, "Something went wrong")
|
||||
return redirect(reverse_lazy("accounts:customer_detail"))
|
||||
|
||||
email_service = EmailService(
|
||||
subject="Your Exclusive Account Access Details with Good Times!",
|
||||
to=principal_obj.email,
|
||||
from_email=settings.EMAIL_HOST_USER,
|
||||
)
|
||||
|
||||
# Send the email
|
||||
try:
|
||||
temp_password="goodtimes#2024"
|
||||
principal_obj.password = make_password(temp_password)
|
||||
principal_obj.save()
|
||||
email_service.load_template(
|
||||
"accounts/customer/account_transfer_email_template.html", locals()
|
||||
)
|
||||
email_service.send()
|
||||
|
||||
principal_preference = IAmPrincipalExtendedData.objects.get(principal=principal_obj)
|
||||
principal_preference.is_transferred = True
|
||||
principal_preference.save()
|
||||
messages.success(request, "Account Transfer mail send successfully")
|
||||
except Exception as e:
|
||||
messages.error(request, f"{str(e)}")
|
||||
|
||||
return redirect(reverse_lazy("accounts:customer_detail", kwargs={"pk": kwargs.get("pk")}))
|
||||
|
||||
|
||||
class CustomerImportView(LoginRequiredMixin, generic.View):
|
||||
page_name = resource_action.RESOURCE_MANAGE_CUSTOMER
|
||||
resource = resource_action.RESOURCE_MANAGE_CUSTOMER
|
||||
action = resource_action.ACTION_READ
|
||||
template_name = "accounts/customer/customer_bulk_template.html"
|
||||
form_class = UploadExcelForm
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
"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):
|
||||
form = self.form_class()
|
||||
context = self.get_context_data(form=form)
|
||||
return render(request, self.template_name, context=context)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
form = self.form_class(request.POST, request.FILES)
|
||||
context = self.get_context_data(form=form)
|
||||
if not form.is_valid():
|
||||
print(form.errors)
|
||||
return render(request, self.template_name, context=context)
|
||||
|
||||
excel_file = request.FILES['file']
|
||||
|
||||
wb = load_workbook(filename=excel_file)
|
||||
ws = wb.active
|
||||
|
||||
error_log = []
|
||||
|
||||
principals = []
|
||||
preferences_l = []
|
||||
subscriptions = []
|
||||
principal_type = IAmPrincipalType.objects.get(name=resource_action.PRINCIPAL_TYPE_EVENT_MANAGER)
|
||||
free_subscription = Subscription.objects.filter(is_free=True, active=True).first()
|
||||
|
||||
for idx, row in enumerate(ws.iter_rows(min_row=2, values_only=True), start=2):
|
||||
first_name, last_name, email, preferences, start_date, end_date = row
|
||||
print(f"{first_name}, {last_name, email, preferences, start_date, end_date}")
|
||||
|
||||
# validate all data
|
||||
if not first_name or not last_name or not email or not preferences or not start_date or not end_date:
|
||||
error_log.append(f"Row {idx}: Missing data.")
|
||||
continue
|
||||
|
||||
# validate email existence
|
||||
if IAmPrincipal.objects.filter(email=email).exists():
|
||||
error_log.append(f"Row {idx}: Email {email} already exists.")
|
||||
continue
|
||||
|
||||
# validate date rnage
|
||||
if end_date < start_date:
|
||||
error_log.append(f"Row {idx}: End date {end_date} must greater then start date {start_date}.")
|
||||
continue
|
||||
|
||||
# validate preferences
|
||||
preference_list = [pref.strip() for pref in preferences.split(',')]
|
||||
event_categories = EventCategory.objects.filter(title__in=preference_list)
|
||||
if len(event_categories) != len(preference_list):
|
||||
error_log.append(f"Row {idx}: One or more preferences are invalid.")
|
||||
continue
|
||||
|
||||
# collect the principals
|
||||
principal = IAmPrincipal(
|
||||
first_name=first_name,
|
||||
last_name=last_name,
|
||||
email=email,
|
||||
password=make_password("goodtimes#2024"),
|
||||
username=email,
|
||||
email_verified=True,
|
||||
register_complete=True,
|
||||
principal_type=principal_type
|
||||
)
|
||||
principals.append(principal)
|
||||
|
||||
# Collect preferences to be set later
|
||||
preferences_l.append((principal, event_categories, start_date, end_date))
|
||||
|
||||
if error_log:
|
||||
context = self.get_context_data(form=form, error_log=error_log)
|
||||
messages.error(request, "No recore is created check error log and fix the error in the file ")
|
||||
return render(request, self.template_name, context=context)
|
||||
|
||||
# Use transaction.atomic to ensure all-or-nothing
|
||||
with transaction.atomic():
|
||||
# Bulk create principals
|
||||
IAmPrincipal.objects.bulk_create(principals)
|
||||
|
||||
# Now we need to refresh principals from the DB to get their IDs
|
||||
principals = IAmPrincipal.objects.filter(email__in=[p.email for p in principals])
|
||||
|
||||
# Create subscriptions and preferences
|
||||
for principal, event_categories, start_date, end_date in preferences_l:
|
||||
principal = principals.get(email=principal.email)
|
||||
|
||||
# Generate referral code for the manager
|
||||
ReferralCode.create_referral_code_for_user_manager(principal=principal, principal_type=principal_type)
|
||||
|
||||
# Create IAmPrincipalExtendedData record
|
||||
IAmPrincipalExtendedData.objects.create(principal=principal, is_onboarded=True)
|
||||
|
||||
# Create PrincipalSubscription record
|
||||
subscription = PrincipalSubscription(
|
||||
principal=principal,
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
grace_period_end_date=PrincipalSubscription.generate_grace_period_end_date(end_date),
|
||||
is_paid=True,
|
||||
subscription=free_subscription
|
||||
)
|
||||
subscriptions.append(subscription)
|
||||
|
||||
# Create PrincipalPreferences record
|
||||
preference = PrincipalPreference(principal=principal)
|
||||
preference.save()
|
||||
preference.preferred_categories.set(event_categories)
|
||||
|
||||
# Bulk create subscriptions
|
||||
PrincipalSubscription.objects.bulk_create(subscriptions)
|
||||
|
||||
messages.success(request, "Data imported successfully")
|
||||
return render(request, self.template_name, context=context)
|
||||
|
||||
|
||||
class CustomerExportView(LoginRequiredMixin, generic.View):
|
||||
model = IAmPrincipal
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
princiapls = IAmPrincipal.objects.select_related("extended_data").filter(principal_type__name=resource_action.PRINCIPAL_TYPE_EVENT_MANAGER)
|
||||
|
||||
# prepare data for excel file
|
||||
data = []
|
||||
for principal in princiapls:
|
||||
data.append([
|
||||
principal.email,
|
||||
principal.first_name,
|
||||
principal.last_name,
|
||||
str(principal.phone_no),
|
||||
principal.email_verified,
|
||||
principal.is_active,
|
||||
principal.extended_data.is_onboarded if principal.extended_data else 'N/A',
|
||||
principal.extended_data.is_transferred if principal.extended_data else 'N/A',
|
||||
principal.created_on.replace(tzinfo=None) if principal.created_on else 'N/A'
|
||||
])
|
||||
|
||||
# Define the columns for the Excel file
|
||||
columns = ["Email", "First Name", "Last Name", "Phone Number", "Email Verified", "Active", "Onboarde by Admin", "Transferred to Customer", "Created Date"]
|
||||
|
||||
# Create a workbook and select the active worksheet
|
||||
wb = Workbook()
|
||||
ws = wb.active
|
||||
ws.title = "Event Managers List"
|
||||
|
||||
# Write the column headers
|
||||
for col_num, column_title in enumerate(columns, 1):
|
||||
cell = ws.cell(row=1, column=col_num, value=column_title)
|
||||
cell.font = Font(bold=True)
|
||||
|
||||
# write the data rows
|
||||
|
||||
for row_num, row_data in enumerate(data, 2):
|
||||
for col_num, cell_value in enumerate(row_data, 1):
|
||||
ws.cell(row=row_num, column=col_num, value=cell_value)
|
||||
|
||||
response = HttpResponse(content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
|
||||
response['Content-Disposition'] = 'attachment; filename=event_managers.xlsx'
|
||||
wb.save(response)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
class DatatableListView(LoginRequiredMixin, generic.ListView):
|
||||
pass
|
||||
|
||||
|
||||
class PrincipalProfileView(LoginRequiredMixin, generic.ListView):
|
||||
page_name = resource_action.RESOURCE_MANAGE_DASHBOARD
|
||||
model = IAmPrincipal
|
||||
|
||||
32
goodtimes/mixins.py
Normal file
32
goodtimes/mixins.py
Normal file
@@ -0,0 +1,32 @@
|
||||
from django.views import generic
|
||||
from goodtimes.utils import JsonResponseUtil
|
||||
|
||||
|
||||
class ActionMixin(generic.View):
|
||||
model = None
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
|
||||
if self.model is None:
|
||||
raise NotImplementedError("Subclasses of BaseActionView must define a 'model' attribute.")
|
||||
|
||||
action = request.POST.get('action') # 'archive', 'active', or 'unarchive'
|
||||
ids = request.POST.getlist('ids[]') # List of IDs to perform action on
|
||||
active = request.POST.get('active')
|
||||
print(f"arhive action {action} and id is {ids} and active data is {active}")
|
||||
if action == 'archive':
|
||||
# Update 'deleted' field to True for the selected users
|
||||
self.model.objects.filter(id__in=ids).update(deleted=True, active=False)
|
||||
message = 'Record archived successfully.'
|
||||
elif action == 'active':
|
||||
# Update 'active' field to True for the selected users
|
||||
self.model.objects.filter(id__in=ids).update(active=active.capitalize())
|
||||
message = 'Record updated successfully.'
|
||||
elif action == 'unarchive':
|
||||
# Update 'deleted' field to False for the selected users
|
||||
self.model.objects.filter(id__in=ids).update(deleted=False)
|
||||
message = 'Record unarchived successfully.'
|
||||
else:
|
||||
return JsonResponseUtil.error(message="Invalid Action")
|
||||
|
||||
return JsonResponseUtil.success(message=message)
|
||||
@@ -1,3 +1,4 @@
|
||||
from django.http import JsonResponse
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.renderers import JSONRenderer
|
||||
from rest_framework import status
|
||||
@@ -40,6 +41,26 @@ class ApiResponse:
|
||||
# return ApiResponse.error("Validation error", errors, status_code)
|
||||
|
||||
|
||||
class JsonResponseUtil:
|
||||
"""
|
||||
A utility class for creating JSON responses with a standardized format.
|
||||
"""
|
||||
@staticmethod
|
||||
def success(message, data=None, status=200):
|
||||
response_data = {"success": True, "status": status, "message": message}
|
||||
if data is not None:
|
||||
response_data["data"] = data
|
||||
return JsonResponse(response_data, status=status)
|
||||
|
||||
@staticmethod
|
||||
def error(message, errors=None, status=403):
|
||||
response_data = {"success": False, "status": status, "message": message}
|
||||
if errors is not None:
|
||||
response_data["errors"] = errors
|
||||
return JsonResponse(response_data, status=status)
|
||||
|
||||
|
||||
|
||||
class RandomGenerator:
|
||||
@staticmethod
|
||||
def number(start, end):
|
||||
|
||||
@@ -54,7 +54,7 @@ class CreateEventApi(APIView):
|
||||
serializer = CreateEventSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
serializer.save(created_by=self.request.user)
|
||||
serializer.save(created_by=self.request.user, principal=self.request.user)
|
||||
|
||||
# Add additional logic for handling other relationships (e.g., Venue)
|
||||
return ApiResponse.success(
|
||||
@@ -122,7 +122,7 @@ class CreateVenueApi(APIView):
|
||||
serializer = VenueSerializer(data=data, context={"request": request})
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
serializer.save(created_by=self.request.user, active=True)
|
||||
serializer.save(created_by=self.request.user, principal=self.request.user, active=True)
|
||||
|
||||
# Add additional logic for handling other relationships (e.g., Venue)
|
||||
return ApiResponse.success(
|
||||
@@ -688,7 +688,7 @@ class EventFilterByLocationAPIView(APIView):
|
||||
)
|
||||
|
||||
max_distance_km = 10 # Set your desired maximum distance
|
||||
current_and_future_events_query = Q(active=True, deleted=False, draft=False) & (
|
||||
current_and_future_events_query = Q(active=True, deleted=False, draft=False, created_by__is_active=True,) & (
|
||||
Q(end_date__gte=today)
|
||||
)
|
||||
|
||||
@@ -714,7 +714,7 @@ class EventFilterByLocationAPIView(APIView):
|
||||
venues_within_range.append(venue.id)
|
||||
print("venues_within_range: ", venues_within_range)
|
||||
# venues_data = [venue_to_dict(venue) for venue in venues_within_range]
|
||||
events = Event.objects.filter(venue__id__in=venues_within_range)
|
||||
events = events_queryset.filter(venue__id__in=venues_within_range)
|
||||
|
||||
# Serialize and return the filtered events
|
||||
serializer = EventDetailSerializer(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from django import forms
|
||||
from accounts.models import IAmPrincipal, IAmPrincipalExtendedData
|
||||
from manage_events.models import EventMaster, Event, EventCategory, Venue
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
@@ -16,9 +17,25 @@ class EventCategoryForm(forms.ModelForm):
|
||||
|
||||
|
||||
class EventForm(forms.ModelForm):
|
||||
principal = forms.ModelChoiceField(
|
||||
queryset=IAmPrincipal.objects.select_related("extended_data").filter(
|
||||
extended_data__is_onboarded=True,
|
||||
extended_data__is_transferred=False
|
||||
),
|
||||
label="Non-transfer user list",
|
||||
required=True
|
||||
)
|
||||
venue = forms.ModelChoiceField(
|
||||
queryset=Venue.objects.filter(
|
||||
active=True
|
||||
),
|
||||
label="venue",
|
||||
required=True
|
||||
)
|
||||
class Meta:
|
||||
model = Event
|
||||
fields = [
|
||||
"principal",
|
||||
"title",
|
||||
# "event_master",
|
||||
"description",
|
||||
@@ -36,6 +53,8 @@ class EventForm(forms.ModelForm):
|
||||
"entry_fee",
|
||||
"key_guest",
|
||||
"age_group",
|
||||
"coupon_code",
|
||||
"coupon_description",
|
||||
"tags",
|
||||
"draft",
|
||||
"active",
|
||||
@@ -101,9 +120,19 @@ class EventMasterForm(forms.ModelForm):
|
||||
|
||||
|
||||
class VenueForm(forms.ModelForm):
|
||||
principal = forms.ModelChoiceField(
|
||||
queryset=IAmPrincipal.objects.select_related("extended_data").filter(
|
||||
extended_data__is_onboarded=True,
|
||||
extended_data__is_transferred=False
|
||||
),
|
||||
label="Non-transfer user list",
|
||||
required=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Venue
|
||||
fields = [
|
||||
"principal",
|
||||
"title",
|
||||
"description",
|
||||
"address",
|
||||
|
||||
21
manage_events/migrations/0013_venue_principal.py
Normal file
21
manage_events/migrations/0013_venue_principal.py
Normal file
@@ -0,0 +1,21 @@
|
||||
# Generated by Django 5.0.2 on 2024-06-25 17:04
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('manage_events', '0012_event_coupon_code_event_coupon_description'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='venue',
|
||||
name='principal',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='venues_principal', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
]
|
||||
21
manage_events/migrations/0014_event_principal.py
Normal file
21
manage_events/migrations/0014_event_principal.py
Normal file
@@ -0,0 +1,21 @@
|
||||
# Generated by Django 5.0.2 on 2024-06-25 17:09
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('manage_events', '0013_venue_principal'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='event',
|
||||
name='principal',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='events_principal', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
]
|
||||
@@ -18,6 +18,7 @@ class EventCategory(BaseModel):
|
||||
|
||||
|
||||
class Venue(BaseModel):
|
||||
principal = models.ForeignKey(IAmPrincipal, related_name="venues_principal", on_delete=models.CASCADE, null=True)
|
||||
title = models.CharField(max_length=255)
|
||||
description = models.TextField(null=True, blank=True)
|
||||
address = models.TextField(null=True, blank=True)
|
||||
@@ -59,6 +60,7 @@ class Event(BaseModel):
|
||||
("free", "Free"),
|
||||
("paid", "Paid"),
|
||||
]
|
||||
principal = models.ForeignKey(IAmPrincipal, related_name="events_principal", on_delete=models.CASCADE, null=True)
|
||||
title = models.CharField(max_length=255)
|
||||
category = models.ForeignKey(EventCategory, on_delete=models.CASCADE)
|
||||
event_master = models.ForeignKey(
|
||||
@@ -144,7 +146,7 @@ class PrincipalPreference(BaseModel):
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return str(self.preferred_categories)
|
||||
return str(self.preferred_categories.name)
|
||||
|
||||
class Meta:
|
||||
db_table = "user_preference"
|
||||
|
||||
@@ -281,7 +281,9 @@ class EventCreateOrUpdateView(LoginRequiredMixin, generic.View):
|
||||
print(form.errors)
|
||||
context = self.get_context_data(form=form)
|
||||
return render(request, self.template_name, context=context)
|
||||
form.save()
|
||||
instance = form.save()
|
||||
instance.created_by = form.cleaned_data.get("principal")
|
||||
instance.save()
|
||||
messages.success(self.request, self.get_success_message())
|
||||
return redirect(self.success_url)
|
||||
|
||||
@@ -414,6 +416,7 @@ class VenueCreateOrUpdateView(LoginRequiredMixin, generic.View):
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
print(f"self.object is {self.object}")
|
||||
|
||||
# If an object is found, change action to ACTION_UPDATE
|
||||
if self.object is not None:
|
||||
@@ -425,6 +428,7 @@ class VenueCreateOrUpdateView(LoginRequiredMixin, generic.View):
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
print(f"form data is {request.POST} and self.object is {self.object}")
|
||||
|
||||
# If an object is found, change action to ACTION_UPDATE
|
||||
if self.object is not None:
|
||||
@@ -435,7 +439,10 @@ class VenueCreateOrUpdateView(LoginRequiredMixin, generic.View):
|
||||
print(form.errors)
|
||||
context = self.get_context_data(form=form)
|
||||
return render(request, self.template_name, context=context)
|
||||
form.save()
|
||||
|
||||
instance = form.save()
|
||||
instance.created_by = form.cleaned_data.get("principal")
|
||||
instance.save()
|
||||
messages.success(self.request, self.get_success_message())
|
||||
return redirect(self.success_url)
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ class SubscriptionForm(forms.ModelForm):
|
||||
"referral_percentage",
|
||||
"active",
|
||||
"deleted",
|
||||
"is_free",
|
||||
] # Include all fields you want from the model
|
||||
|
||||
|
||||
|
||||
18
manage_subscriptions/migrations/0008_subscription_is_free.py
Normal file
18
manage_subscriptions/migrations/0008_subscription_is_free.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.0.2 on 2024-06-25 07:28
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('manage_subscriptions', '0007_alter_subscription_referral_percentage'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='subscription',
|
||||
name='is_free',
|
||||
field=models.BooleanField(default=False, help_text='Indicates whether this subscription is free and only accessible by administrators, not visible to regular users.'),
|
||||
),
|
||||
]
|
||||
@@ -1,3 +1,4 @@
|
||||
from datetime import timedelta, timezone
|
||||
from django.db import models
|
||||
from accounts.models import BaseModel, IAmPrincipal, IAmPrincipalType
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
@@ -30,6 +31,7 @@ class Subscription(BaseModel):
|
||||
IAmPrincipalType, related_name="principal_type_subscriptions", blank=True
|
||||
)
|
||||
referral_percentage = models.DecimalField(max_digits=5, decimal_places=2)
|
||||
is_free = models.BooleanField(default=False, help_text="Indicates whether this subscription is free and only accessible by administrators, not visible to regular users.")
|
||||
|
||||
class Meta:
|
||||
db_table = "subscription"
|
||||
@@ -75,6 +77,12 @@ class PrincipalSubscription(BaseModel):
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.subscription} - {self.principal.first_name}"
|
||||
|
||||
def generate_order_id(email):
|
||||
return f"order_{str(timezone.localtime().timestamp())}{str(email)}"
|
||||
|
||||
def generate_grace_period_end_date(date):
|
||||
return date + timedelta(days=15)
|
||||
|
||||
|
||||
class WebhookEvent(BaseModel):
|
||||
|
||||
@@ -37,6 +37,7 @@ from django.views.generic.base import TemplateView
|
||||
|
||||
# Create your views here.
|
||||
|
||||
from django.db.models import Q
|
||||
|
||||
class SubscriptionCreateOrUpdateView(LoginRequiredMixin, generic.View):
|
||||
# Set the page_name and resource
|
||||
@@ -96,6 +97,14 @@ class SubscriptionCreateOrUpdateView(LoginRequiredMixin, generic.View):
|
||||
print(form.errors)
|
||||
context = self.get_context_data(form=form)
|
||||
return render(request, self.template_name, context=context)
|
||||
|
||||
# This code ensures that only one free plan can be created by checking for existing free plans before saving a new one.
|
||||
if form.cleaned_data.get("is_free"):
|
||||
if self.model.objects.filter(Q(is_free=True) & Q(active=True)).exists:
|
||||
messages.error(self.request, "A free plan is already available. Please deactivate the existing one before creating a new one.")
|
||||
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)
|
||||
@@ -386,7 +395,7 @@ class SubscriptionPageView(TemplateView):
|
||||
if request.user.is_authenticated:
|
||||
print("request.user: ", request.user)
|
||||
subscriptions = Subscription.objects.filter(
|
||||
principal_types=request.user.principal_type, active=True, deleted=False
|
||||
principal_types=request.user.principal_type, active=True, deleted=False, is_free=False
|
||||
)
|
||||
|
||||
if subscriptions.exists():
|
||||
|
||||
4
static/src/plugins/src/jquery-validate/jquery.validate.min.js
vendored
Normal file
4
static/src/plugins/src/jquery-validate/jquery.validate.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -0,0 +1,31 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Your Exclusive Account Access Details with Good Times!</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h2>Dear Valued Customer,</h2>
|
||||
<p>Greetings from Good Times! We trust this correspondence finds you in splendid spirits.</p>
|
||||
<p>We are pleased to provide you with your account credentials for seamless access:</p>
|
||||
<table border="0" cellpadding="5" cellspacing="0">
|
||||
<tr>
|
||||
<td><strong>Username:</strong></td>
|
||||
<td>{{ principal_obj.email }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Password:</strong></td>
|
||||
<td>{{ temp_password }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p>Please utilize the temporary password to access your account promptly. Upon your initial login, we recommend changing your password to further enhance security measures.</p>
|
||||
<p>We sincerely hope your experience with Good Times has been delightful thus far and look forward to continuing to exceed your expectations!</p>
|
||||
<p>Warmest regards,</p>
|
||||
Good Times<br>
|
||||
{{ settings.EMAIL_HOST_USER }}</p>
|
||||
</body>
|
||||
</html>
|
||||
212
templates/accounts/customer/customer_add.html
Normal file
212
templates/accounts/customer/customer_add.html
Normal file
@@ -0,0 +1,212 @@
|
||||
{% extends 'layout/base_template.html' %}
|
||||
{% load static %}
|
||||
{% block stylesheet %}
|
||||
<!-- include required css cdn link through html here -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
|
||||
{% include "cdn_through_html/flatpicker_cdn_css.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="row layout-top-spacing">
|
||||
<div class="col-lg-12">
|
||||
<div class="row mb-2">
|
||||
<div class="col">
|
||||
<a href="{% url 'accounts:customer_list'%}" style="height: fit-content;width: fit-content;display: inline-block;">
|
||||
<h3 class="card-title m-2 d-flex align-items-center gap-2" style="width: fit-content;"><span class="fw-bold material-symbols-outlined">
|
||||
arrow_back
|
||||
</span><span>{{operation}} Customer</span></h3>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row layout-spacing">
|
||||
<div class="col-lg-12">
|
||||
<div class="statbox widget box box-shadow">
|
||||
<div class="widget-content widget-content-area">
|
||||
|
||||
<form method="post" id="addCustomer">
|
||||
{% csrf_token %}
|
||||
{% include 'includes/dynamic_template_form.html' with form=form %}
|
||||
<div class="mt-4 mb-0">
|
||||
<div class="d-grid"><button class="btn btn-primary btn-block" type="submit">Submit</button></div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{% endblock content %}
|
||||
|
||||
{% block javascript %}
|
||||
<!-- include required js cdn link through html here -->
|
||||
{% include "cdn_through_html/flatpicker_cdn_js.html" %}
|
||||
{% include "cdn_through_html/jquery_validate_cdn_js.html" %}
|
||||
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
|
||||
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.3/jquery.validate.min.js"></script>
|
||||
-->
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
|
||||
// var f3 = flatpickr(document.getElementById('id_free_period'), {
|
||||
// mode: "range"
|
||||
// });
|
||||
|
||||
var start_date = flatpickr(document.getElementById('id_free_start_date'), {
|
||||
minDate: "today",
|
||||
onChange: function(selectedDates, dateStr, instance) {
|
||||
end_date.set('minDate', selectedDates[0]);
|
||||
}
|
||||
});
|
||||
|
||||
var end_date = flatpickr(document.getElementById('id_free_end_date'), {
|
||||
minDate: null // initialize with no minimum date
|
||||
});
|
||||
|
||||
$('.js-example-basic-multiple').select2({
|
||||
placeholder: 'Select options',
|
||||
allowClear: true,
|
||||
tags: true, // Allow the user to enter custom tags
|
||||
tokenSeparators: [',', ' '], // Customize token separators
|
||||
closeOnSelect: false // Keep the dropdown open after selection
|
||||
});
|
||||
|
||||
|
||||
// Add custom validation method for email
|
||||
$.validator.addMethod("validEmail", function(value, element) {
|
||||
return /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$/.test(value);
|
||||
}, "Please enter a valid email address.");
|
||||
|
||||
// Add custom validation method to check for special characters
|
||||
$.validator.addMethod("noSpecialChars", function(value, element) {
|
||||
return /^[a-zA-Z\s]*$/.test(value); // Allow only letters and whitespace
|
||||
}, "Please enter only letters and spaces.");
|
||||
|
||||
// Add custom validation method to check for starting with a letter
|
||||
$.validator.addMethod("startsWithLetter", function(value, element) {
|
||||
return /^[a-zA-Z]/.test(value); // Check if the value starts with a letter
|
||||
}, "Please start with a letter.");
|
||||
|
||||
|
||||
$("#addCustomer").validate({
|
||||
rules: {
|
||||
first_name: {
|
||||
required: true,
|
||||
minlength: 2,
|
||||
maxlength:15,
|
||||
noSpecialChars: true,
|
||||
startsWithLetter: true,
|
||||
noSpace: true
|
||||
},
|
||||
last_name: {
|
||||
required: true,
|
||||
minlength: 2,
|
||||
maxlength: 15,
|
||||
noSpecialChars: true,
|
||||
startsWithLetter: true,
|
||||
noSpace: true
|
||||
},
|
||||
email: {
|
||||
required: true,
|
||||
validEmail: true,
|
||||
remote: {
|
||||
url: "{% url 'accounts:customer_check_email' %}", // Replace with your actual URL for the view
|
||||
type: "POST",
|
||||
data: {
|
||||
email: function() {
|
||||
return $("#id_email").val();
|
||||
}
|
||||
},
|
||||
beforeSend: function(xhr) {
|
||||
xhr.setRequestHeader('X-CSRFToken', $('input[name="csrfmiddlewaretoken"]').val());
|
||||
},
|
||||
success: function(data) {
|
||||
console.log(date)
|
||||
// Handle successful email check (optional)
|
||||
// You can remove this if you only need to display the error message
|
||||
},
|
||||
error: function(response) {
|
||||
console.log(response)
|
||||
}
|
||||
},
|
||||
},
|
||||
preferences: {
|
||||
required: true,
|
||||
minlength: 3
|
||||
},
|
||||
free_start_date: {
|
||||
required: true,
|
||||
// You can add a custom validation method for date format (optional)
|
||||
},
|
||||
free_end_date: {
|
||||
required: true,
|
||||
// You can add a custom validation method for date format (optional)
|
||||
}
|
||||
},
|
||||
messages: {
|
||||
first_name: {
|
||||
required: "Please enter your first name.",
|
||||
minlength: "First name must be at least 2 characters.",
|
||||
maxlength: "First name must not exceed 20 characters.",
|
||||
noSpecialChars: "Please enter only letters and spaces.",
|
||||
startsWithLetter: "First name must start with a letter."
|
||||
},
|
||||
last_name: {
|
||||
required: "Please enter your last name.",
|
||||
minlength: "Last name must be at least 2 characters.",
|
||||
maxlength: "First name must not exceed 20 characters.",
|
||||
noSpecialChars: "Please enter only letters and spaces.",
|
||||
startsWithLetter: "Last name must start with a letter."
|
||||
},
|
||||
email: {
|
||||
required: "Please enter your email address.",
|
||||
validEmail: "Please enter a valid email address."
|
||||
},
|
||||
preferences: {
|
||||
required: "Please select at least one preference.",
|
||||
minlength: "Please select at least one preference."
|
||||
},
|
||||
free_start_date: {
|
||||
required: "Please select a start date for the free period."
|
||||
},
|
||||
free_end_date: {
|
||||
required: "Please select an end date for the free period.",
|
||||
greaterThanStartDate: "The end date must be after the start date."
|
||||
}
|
||||
},
|
||||
customMethods: {
|
||||
greaterThanStartDate: function(element) {
|
||||
var startDate = $("#id_free_start_date").datepicker("getDate"); // Assuming you're using datepicker
|
||||
var endDate = $(element).datepicker("getDate");
|
||||
if (!endDate || !startDate) {
|
||||
return true; // Allow if either date is not selected (prevents errors)
|
||||
}
|
||||
return endDate > startDate;
|
||||
}
|
||||
},
|
||||
errorElement: 'div',
|
||||
errorPlacement: function(error, element) {
|
||||
error.addClass('invalid-feedback');
|
||||
$(element).closest('.form-group').append(error);
|
||||
},
|
||||
highlight: function(element, errorClass, validClass) {
|
||||
$(element).addClass('is-invalid').removeClass('is-valid');
|
||||
},
|
||||
unhighlight: function(element, errorClass, validClass) {
|
||||
$(element).removeClass('is-invalid').addClass('is-valid');
|
||||
},
|
||||
submitHandler: function(form) {
|
||||
|
||||
// Disable the submit button to prevent multiple submissions
|
||||
$('button[type="submit"]').prop('disabled', true);
|
||||
form.submit();
|
||||
}
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
80
templates/accounts/customer/customer_bulk_template.html
Normal file
80
templates/accounts/customer/customer_bulk_template.html
Normal file
@@ -0,0 +1,80 @@
|
||||
{% extends 'layout/base_template.html' %}
|
||||
{% load static %}
|
||||
{% block stylesheet %}
|
||||
<!-- include required css cdn link through html here -->
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="row layout-top-spacing">
|
||||
<div class="col-lg-12">
|
||||
<div class="row mb-2">
|
||||
<div class="col">
|
||||
<a href="{% url 'accounts:customer_list'%}" style="height: fit-content;width: fit-content;display: inline-block;">
|
||||
<h3 class="card-title m-2 d-flex align-items-center gap-2" style="width: fit-content;"><span class="fw-bold material-symbols-outlined">
|
||||
arrow_back
|
||||
</span><span>Customer</span></h3>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row layout-spacing">
|
||||
<div class="col-lg-12">
|
||||
<div class="statbox widget box box-shadow">
|
||||
<div class="widget-content widget-content-area">
|
||||
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
{% include 'includes/dynamic_template_form.html' with form=form %}
|
||||
<div class="mt-4 mb-0">
|
||||
<div class="d-grid"><button class="btn btn-primary btn-block" type="submit">Submit</button></div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="mt-3">
|
||||
{% if error_log %}
|
||||
<h3>Error Log:</h3>
|
||||
<ul>
|
||||
{% for error in error_log %}
|
||||
<li>{{ error }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{% endblock content %}
|
||||
|
||||
{% block javascript %}
|
||||
<script>
|
||||
// $('form').submit(function(event) {
|
||||
// event.preventDefault(); // prevent default form submission
|
||||
// var formData = new FormData(this); // create a FormData object from the form
|
||||
// if ($('#excel_file').val() == '') {
|
||||
// alert('Please select a file to upload');
|
||||
// return;
|
||||
// }
|
||||
// $.ajax({
|
||||
// type: 'POST',
|
||||
// url: '{% url "accounts:import_customer_data" %}', // replace with your view URL
|
||||
// data: formData,
|
||||
// contentType: false,
|
||||
// processData: false,
|
||||
// success: function(data) {
|
||||
// console.log('File uploaded successfully!');
|
||||
// },
|
||||
// error: function(xhr, status, error) {
|
||||
// console.log('Error uploading file: ' + error);
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
101
templates/accounts/customer/customer_detail.html
Normal file
101
templates/accounts/customer/customer_detail.html
Normal file
@@ -0,0 +1,101 @@
|
||||
{% extends 'layout/base_template.html' %}
|
||||
{% load static %}
|
||||
{% block stylesheet %}
|
||||
<!-- include required css cdn link through html here -->
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="row layout-top-spacing">
|
||||
<div class="col-lg-12">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<a href="{% url 'accounts:customer_list'%}" style="height: fit-content;width: fit-content;display: inline-block;">
|
||||
<h3 class="card-title m-2 d-flex align-items-center gap-2" style="width: fit-content;"><span class="fw-bold material-symbols-outlined">
|
||||
arrow_back
|
||||
</span><span>Customer Details</span></h3>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row layout-spacing">
|
||||
<div class="col-lg-12">
|
||||
<div class="statbox widget box box-shadow">
|
||||
<div class="widget-content widget-content-area">
|
||||
<div class="row mb-3">
|
||||
|
||||
<div class="col-md-3">First Name</div>
|
||||
|
||||
<div class="col-md-9">{{principal_obj.first_name}}</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
|
||||
<div class="col-md-3">Last Name</div>
|
||||
|
||||
<div class="col-md-9">{{principal_obj.last_name}}</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
|
||||
<div class="col-md-3">Email Address</div>
|
||||
|
||||
<div class="col-md-9">{{principal_obj.email}}</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
|
||||
<div class="col-md-3">Preferences</div>
|
||||
|
||||
<div class="col-md-9">
|
||||
{% for category in principal_preference.preferred_categories.all %}
|
||||
<span class="shadow-none badge badge-primary mb-1">
|
||||
{{ category.title }}
|
||||
</span>
|
||||
{% empty %}
|
||||
<span class="shadow-none badge badge-primary">
|
||||
No preferred categories.
|
||||
</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
|
||||
<div class="col-md-3">Start Date</div>
|
||||
|
||||
<div class="col-md-9">{{principal_subscription.start_date}}</div>
|
||||
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
|
||||
<div class="col-md-3">End Date</div>
|
||||
|
||||
<div class="col-md-9">{{principal_subscription.end_date}}</div>
|
||||
|
||||
</div>
|
||||
{% if not principal_obj.extended_data.is_transferred %}
|
||||
<div class="col text-end">
|
||||
<a class="btn btn-primary mb-3" href="{% url 'accounts:customer_transfer' principal_obj.id%}">
|
||||
Transfer Account
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock content %}
|
||||
|
||||
{% block javascript %}
|
||||
|
||||
{% endblock %}
|
||||
220
templates/accounts/customer/customer_edit.html
Normal file
220
templates/accounts/customer/customer_edit.html
Normal file
@@ -0,0 +1,220 @@
|
||||
{% extends 'layout/base_template.html' %}
|
||||
{% load static %}
|
||||
{% block stylesheet %}
|
||||
<!-- include required css cdn link through html here -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
|
||||
{% include "cdn_through_html/flatpicker_cdn_css.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="row layout-top-spacing">
|
||||
<div class="col-lg-12">
|
||||
<div class="row mb-2">
|
||||
<div class="col">
|
||||
<a href="{% url 'accounts:customer_list'%}" style="height: fit-content;width: fit-content;display: inline-block;">
|
||||
<h3 class="card-title m-2 d-flex align-items-center gap-2" style="width: fit-content;"><span class="fw-bold material-symbols-outlined">
|
||||
arrow_back
|
||||
</span><span>{{operation}} Customer</span></h3>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% if not principal_obj.extended_data.is_transferred %}
|
||||
<div class="col text-end">
|
||||
<a class="btn btn-dark mb-2 me-4" href="{% url 'accounts:customer_transfer' principal_obj.id%}">
|
||||
Transfer Account
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="row layout-spacing">
|
||||
<div class="col-lg-12">
|
||||
<div class="statbox widget box box-shadow">
|
||||
<div class="widget-content widget-content-area">
|
||||
|
||||
<form method="post" id="addCustomer">
|
||||
{% csrf_token %}
|
||||
{% include 'includes/dynamic_template_form.html' with form=form %}
|
||||
<div class="mt-4 mb-0">
|
||||
<div class="d-grid"><button class="btn btn-primary btn-block" type="submit">Submit</button></div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{% endblock content %}
|
||||
|
||||
{% block javascript %}
|
||||
<!-- include required js cdn link through html here -->
|
||||
{% include "cdn_through_html/flatpicker_cdn_js.html" %}
|
||||
{% include "cdn_through_html/jquery_validate_cdn_js.html" %}
|
||||
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
|
||||
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.3/jquery.validate.min.js"></script>
|
||||
-->
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
|
||||
// var f3 = flatpickr(document.getElementById('id_free_period'), {
|
||||
// mode: "range"
|
||||
// });
|
||||
|
||||
var start_date = flatpickr(document.getElementById('id_free_start_date'), {
|
||||
minDate: "today",
|
||||
onChange: function(selectedDates, dateStr, instance) {
|
||||
end_date.set('minDate', selectedDates[0]);
|
||||
}
|
||||
});
|
||||
|
||||
var end_date = flatpickr(document.getElementById('id_free_end_date'), {
|
||||
minDate: null // initialize with no minimum date
|
||||
});
|
||||
|
||||
$('.js-example-basic-multiple').select2({
|
||||
placeholder: 'Select options',
|
||||
allowClear: true,
|
||||
tags: true, // Allow the user to enter custom tags
|
||||
tokenSeparators: [',', ' '], // Customize token separators
|
||||
closeOnSelect: false // Keep the dropdown open after selection
|
||||
});
|
||||
|
||||
|
||||
// Add custom validation method for email
|
||||
$.validator.addMethod("validEmail", function(value, element) {
|
||||
return /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$/.test(value);
|
||||
}, "Please enter a valid email address.");
|
||||
|
||||
// Add custom validation method to check for special characters
|
||||
$.validator.addMethod("noSpecialChars", function(value, element) {
|
||||
return /^[a-zA-Z\s]*$/.test(value); // Allow only letters and whitespace
|
||||
}, "Please enter only letters and spaces.");
|
||||
|
||||
// Add custom validation method to check for starting with a letter
|
||||
$.validator.addMethod("startsWithLetter", function(value, element) {
|
||||
return /^[a-zA-Z]/.test(value); // Check if the value starts with a letter
|
||||
}, "Please start with a letter.");
|
||||
|
||||
|
||||
$("#addCustomer").validate({
|
||||
rules: {
|
||||
first_name: {
|
||||
required: true,
|
||||
minlength: 2,
|
||||
maxlength:15,
|
||||
noSpecialChars: true,
|
||||
startsWithLetter: true,
|
||||
noSpace: true
|
||||
},
|
||||
last_name: {
|
||||
required: true,
|
||||
minlength: 2,
|
||||
maxlength: 15,
|
||||
noSpecialChars: true,
|
||||
startsWithLetter: true,
|
||||
noSpace: true
|
||||
},
|
||||
email: {
|
||||
required: true,
|
||||
validEmail: true,
|
||||
remote: {
|
||||
url: "{% url 'accounts:customer_check_email' %}", // Replace with your actual URL for the view
|
||||
type: "POST",
|
||||
data: {
|
||||
email: function() {
|
||||
return $("#id_email").val();
|
||||
}
|
||||
},
|
||||
beforeSend: function(xhr) {
|
||||
xhr.setRequestHeader('X-CSRFToken', $('input[name="csrfmiddlewaretoken"]').val());
|
||||
},
|
||||
success: function(data) {
|
||||
console.log(date)
|
||||
// Handle successful email check (optional)
|
||||
// You can remove this if you only need to display the error message
|
||||
},
|
||||
error: function(response) {
|
||||
console.log(response)
|
||||
}
|
||||
},
|
||||
},
|
||||
preferences: {
|
||||
required: true,
|
||||
minlength: 3
|
||||
},
|
||||
free_start_date: {
|
||||
required: true,
|
||||
// You can add a custom validation method for date format (optional)
|
||||
},
|
||||
free_end_date: {
|
||||
required: true,
|
||||
// You can add a custom validation method for date format (optional)
|
||||
}
|
||||
},
|
||||
messages: {
|
||||
first_name: {
|
||||
required: "Please enter your first name.",
|
||||
minlength: "First name must be at least 2 characters.",
|
||||
maxlength: "First name must not exceed 20 characters.",
|
||||
noSpecialChars: "Please enter only letters and spaces.",
|
||||
startsWithLetter: "First name must start with a letter."
|
||||
},
|
||||
last_name: {
|
||||
required: "Please enter your last name.",
|
||||
minlength: "Last name must be at least 2 characters.",
|
||||
maxlength: "First name must not exceed 20 characters.",
|
||||
noSpecialChars: "Please enter only letters and spaces.",
|
||||
startsWithLetter: "Last name must start with a letter."
|
||||
},
|
||||
email: {
|
||||
required: "Please enter your email address.",
|
||||
validEmail: "Please enter a valid email address."
|
||||
},
|
||||
preferences: {
|
||||
required: "Please select at least one preference.",
|
||||
minlength: "Please select at least one preference."
|
||||
},
|
||||
free_start_date: {
|
||||
required: "Please select a start date for the free period."
|
||||
},
|
||||
free_end_date: {
|
||||
required: "Please select an end date for the free period.",
|
||||
greaterThanStartDate: "The end date must be after the start date."
|
||||
}
|
||||
},
|
||||
customMethods: {
|
||||
greaterThanStartDate: function(element) {
|
||||
var startDate = $("#id_free_start_date").datepicker("getDate"); // Assuming you're using datepicker
|
||||
var endDate = $(element).datepicker("getDate");
|
||||
if (!endDate || !startDate) {
|
||||
return true; // Allow if either date is not selected (prevents errors)
|
||||
}
|
||||
return endDate > startDate;
|
||||
}
|
||||
},
|
||||
errorElement: 'div',
|
||||
errorPlacement: function(error, element) {
|
||||
error.addClass('invalid-feedback');
|
||||
$(element).closest('.form-group').append(error);
|
||||
},
|
||||
highlight: function(element, errorClass, validClass) {
|
||||
$(element).addClass('is-invalid').removeClass('is-valid');
|
||||
},
|
||||
unhighlight: function(element, errorClass, validClass) {
|
||||
$(element).removeClass('is-invalid').addClass('is-valid');
|
||||
},
|
||||
submitHandler: function(form) {
|
||||
|
||||
// Disable the submit button to prevent multiple submissions
|
||||
$('button[type="submit"]').prop('disabled', true);
|
||||
form.submit();
|
||||
}
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
@@ -11,18 +11,24 @@
|
||||
<div class="row layout-top-spacing">
|
||||
<div class="col-lg-12">
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="col-sm-4">
|
||||
<h3>Manage Customer</h3>
|
||||
</div>
|
||||
<div class="col-sm-6 text-md-end">
|
||||
<!--
|
||||
<button class="btn btn-dark mb-2 me-md-4" onclick="history.back()">
|
||||
<i class="fa fa-arrow-left"></i>
|
||||
Back
|
||||
</button>
|
||||
-->
|
||||
{% comment %} <a class="btn btn-primary mb-2 me-md-4" href="{% url 'accounts:example_add' %}">Example form</a>
|
||||
<a class="btn btn-primary mb-2" href="{% url 'accounts:principal_add' %}">Add Principal</a> {% endcomment %}
|
||||
<div class="col-sm-8 text-md-end">
|
||||
|
||||
<a class="btn btn-dark mb-2 me-md-4" href="{% url 'accounts:download_excel_template' %}">
|
||||
Download Excel Template
|
||||
</a>
|
||||
|
||||
<a class="btn btn-dark mb-2 me-md-4" href="{% url 'accounts:import_customer_data' %}">
|
||||
import
|
||||
</a>
|
||||
|
||||
<a class="btn btn-dark mb-2 me-md-4" href="{% url 'accounts:export_customer_data' %}">
|
||||
Export
|
||||
</a>
|
||||
|
||||
<a class="btn btn-primary mb-2 me-4" href="{% url 'accounts:customer_add' %}">Add Customer</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -58,6 +64,10 @@
|
||||
style="width: 98.875px;">Email Verified</th>
|
||||
<th class="text-center sorting" tabindex="0" aria-controls="style-3" rowspan="1" colspan="1"
|
||||
style="width: 98.875px;">Referral Count</th>
|
||||
<th class="text-center sorting" tabindex="0" aria-controls="style-3" rowspan="1" colspan="1"
|
||||
style="width: 98.875px;">Onboarded by Admin</th>
|
||||
<th class="text-center sorting" tabindex="0" aria-controls="style-3" rowspan="1" colspan="1"
|
||||
style="width: 98.875px;">Transferred to Customer</th>
|
||||
<th class="text-center sorting" tabindex="0" aria-controls="style-3" rowspan="1" colspan="1"
|
||||
style="width: 98.875px;">Created On</th>
|
||||
<th class="text-center sorting" tabindex="0" aria-controls="style-3" rowspan="1" colspan="1"
|
||||
@@ -87,6 +97,16 @@
|
||||
<!-- <td>{{ data_obj.phone_verified }}</td> -->
|
||||
<td>{{ data_obj.email_verified }}</td>
|
||||
<td>{{ data_obj.referral_count }}</td>
|
||||
<td class="text-center">
|
||||
<span class="shadow-none badge {% if data_obj.extended_data.is_onboarded %}badge-primary{% else %}badge-danger{% endif %}">
|
||||
{{ data_obj.extended_data.is_onboarded }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<span class="shadow-none badge {% if data_obj.extended_data.is_transferred %}badge-primary{% else %}badge-danger{% endif %}">
|
||||
{{ data_obj.extended_data.is_transferred }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ data_obj.created_on }}</td>
|
||||
<td>{{ data_obj.modified_on }}</td>
|
||||
<td class="text-center">
|
||||
@@ -101,7 +121,7 @@
|
||||
</td> -->
|
||||
<td class="text-center">
|
||||
<ul class="table-controls">
|
||||
<li><a href="{% url 'accounts:principal_edit' data_obj.id%}" class="bs-tooltip"
|
||||
<li><a href="{% url 'accounts:customer_edit' data_obj.id%}" class="bs-tooltip"
|
||||
data-bs-toggle="tooltip" data-bs-placement="top" title=""
|
||||
data-original-title="Edit" data-bs-original-title="Edit"
|
||||
aria-label="Edit"><svg xmlns="http://www.w3.org/2000/svg"
|
||||
@@ -113,19 +133,9 @@
|
||||
d="M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z">
|
||||
</path>
|
||||
</svg></a></li>
|
||||
<!-- <li><a href="javascript:void(0);" class="bs-tooltip"
|
||||
data-bs-toggle="tooltip" data-bs-placement="top" title=""
|
||||
data-original-title="Delete" data-bs-original-title="Delete"
|
||||
aria-label="Delete"><svg xmlns="http://www.w3.org/2000/svg"
|
||||
width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round"
|
||||
class="feather feather-trash p-1 br-8 mb-1">
|
||||
<polyline points="3 6 5 6 21 6"></polyline>
|
||||
<path
|
||||
d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2">
|
||||
</path>
|
||||
</svg></a></li> -->
|
||||
<li><a href="{% url 'accounts:customer_detail' data_obj.id%}" class="bs-tooltip" data-bs-toggle="tooltip" data-bs-placement="top" title="" data-original-title="View" data-bs-original-title="View" aria-label="View">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-eye"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path><circle cx="12" cy="12" r="3"></circle></svg>
|
||||
</a></li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
2
templates/cdn_through_html/jquery_validate_cdn_js.html
Normal file
2
templates/cdn_through_html/jquery_validate_cdn_js.html
Normal file
@@ -0,0 +1,2 @@
|
||||
{% load static%}
|
||||
<script src="{% static 'src/plugins/src/jquery-validate/jquery.validate.min.js' %}"></script>
|
||||
@@ -81,7 +81,7 @@
|
||||
<script src="{% static 'src/assets/js/dashboard/dash_1.js' %}"></script>
|
||||
<!-- BEGIN PAGE LEVEL PLUGINS/CUSTOM SCRIPTS -->
|
||||
<script>
|
||||
$(".messgae-alert").fadeTo(2000, 500).slideUp(500, function () {
|
||||
$(".messgae-alert").fadeTo(5000, 500).slideUp(500, function () {
|
||||
$(".messgae-alert").slideUp(500);
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -16,15 +16,12 @@
|
||||
<div class="col-lg-12">
|
||||
<div class="row mb-2">
|
||||
<div class="col">
|
||||
<h3>{{operation}} {{page_title}}</h3>
|
||||
<a href="{% url 'manage_events:event_list'%}" style="height: fit-content;width: fit-content;display: inline-block;">
|
||||
<h3 class="card-title m-2 d-flex align-items-center gap-2" style="width: fit-content;"><span class="fw-bold material-symbols-outlined">
|
||||
arrow_back
|
||||
</span><span>{{operation}} Event</span></h3>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="col text-end">
|
||||
<button class="btn btn-dark mb-2 me-4" onclick="history.back()">
|
||||
<i class="fa fa-arrow-left"></i>
|
||||
Back
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row layout-spacing">
|
||||
<div class="col-lg-12">
|
||||
|
||||
@@ -41,6 +41,8 @@
|
||||
<tr role="row">
|
||||
<th class="checkbox-column sorting_asc" tabindex="0" aria-controls="style-3"
|
||||
aria-sort="ascending" style="width: 69.2656px;"> Record Id </th>
|
||||
<th class="checkbox-column sorting_asc" tabindex="0" aria-controls="style-3"
|
||||
aria-sort="ascending" style="width: 69.2656px;"> Customer </th>
|
||||
<th class="checkbox-column sorting_asc" tabindex="0" aria-controls="style-3"
|
||||
aria-sort="ascending" style="width: 69.2656px;"> Title </th>
|
||||
<th class="checkbox-column sorting_asc" tabindex="0" aria-controls="style-3"
|
||||
@@ -51,8 +53,7 @@
|
||||
aria-sort="ascending" style="width: 69.2656px;"> Start Time </th>
|
||||
<th class="checkbox-column sorting_asc" tabindex="0" aria-controls="style-3"
|
||||
aria-sort="ascending" style="width: 69.2656px;"> End Time </th>
|
||||
<th class="checkbox-column sorting_asc" tabindex="0" aria-controls="style-3"
|
||||
aria-sort="ascending" style="width: 69.2656px;"> Principal </th>
|
||||
|
||||
<th class="sorting" tabindex="7" aria-controls="style-3"
|
||||
style="width: 79.7969px;">Draft</th>
|
||||
<th class="sorting" tabindex="7" aria-controls="style-3"
|
||||
@@ -67,12 +68,19 @@
|
||||
<td class="checkbox-column text-center sorting_1"><a
|
||||
href="{% url 'manage_events:event_detail' data_obj.id %}">{{data_obj.id}}</a>
|
||||
</td>
|
||||
<td>
|
||||
{% if data_obj.principal %}
|
||||
{{ data_obj.principal }}
|
||||
{% elif data_obj.created_by %}
|
||||
{{ data_obj.created_by }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{data_obj.title}}</td>
|
||||
<td>{{data_obj.start_date}}</td>
|
||||
<td>{{data_obj.end_date}}</td>
|
||||
<td>{{data_obj.from_time}}</td>
|
||||
<td>{{data_obj.to_time}}</td>
|
||||
<td>{{data_obj.created_by}}</td>
|
||||
|
||||
<td class="text-center">
|
||||
<span
|
||||
class="shadow-none badge {% if data_obj.draft %}badge-primary{% else %}badge-danger{% endif %}">{{data_obj.draft}}</span>
|
||||
|
||||
@@ -50,6 +50,9 @@
|
||||
<th class="checkbox-column sorting_asc" tabindex="0"
|
||||
aria-controls="style-3" aria-sort="ascending"
|
||||
style="width: 69.2656px;"> Amount </th>
|
||||
<th class="checkbox-column sorting_asc" tabindex="0"
|
||||
aria-controls="style-3" aria-sort="ascending"
|
||||
style="width: 69.2656px;"> Free for Admin </th>
|
||||
<th class="sorting" tabindex="7" aria-controls="style-3"
|
||||
style="width: 79.7969px;">Active</th>
|
||||
<th class="dt-no-sorting sorting" tabindex="8"
|
||||
@@ -64,6 +67,9 @@
|
||||
<td>{{data_obj.title}}</td>
|
||||
<td>{{data_obj.plan.days}}</td>
|
||||
<td>{{data_obj.amount}}</td>
|
||||
<td class="text-center">
|
||||
<span class="shadow-none badge {% if data_obj.is_free %}badge-primary{% else %}badge-danger{% endif %}">{{data_obj.is_free}}</span>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<span class="shadow-none badge {% if data_obj.active %}badge-primary{% else %}badge-danger{% endif %}">{{data_obj.active}}</span>
|
||||
</td>
|
||||
|
||||
@@ -16,14 +16,11 @@
|
||||
<div class="col-lg-12">
|
||||
<div class="row mb-2">
|
||||
<div class="col">
|
||||
<h3>{{operation}} {{page_title}}</h3>
|
||||
</div>
|
||||
|
||||
<div class="col text-end">
|
||||
<button class="btn btn-dark mb-2 me-4" onclick="history.back()">
|
||||
<i class="fa fa-arrow-left"></i>
|
||||
Back
|
||||
</button>
|
||||
<a href="{% url 'manage_events:venue_list'%}" style="height: fit-content;width: fit-content;display: inline-block;">
|
||||
<h3 class="card-title m-2 d-flex align-items-center gap-2" style="width: fit-content;"><span class="fw-bold material-symbols-outlined">
|
||||
arrow_back
|
||||
</span><span>{{operation}} Venue</span></h3>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row layout-spacing">
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
<tr role="row">
|
||||
<th class="checkbox-column sorting_asc" tabindex="0" aria-controls="style-3"
|
||||
aria-sort="ascending" style="width: 69.2656px;"> Record Id </th>
|
||||
<th class="checkbox-column sorting_asc" tabindex="0" aria-controls="style-3"
|
||||
aria-sort="ascending" style="width: 69.2656px;"> Customer </th>
|
||||
<th class="checkbox-column sorting_asc" tabindex="0" aria-controls="style-3"
|
||||
aria-sort="ascending" style="width: 69.2656px;"> Title </th>
|
||||
<th class="checkbox-column sorting_asc" tabindex="0" aria-controls="style-3"
|
||||
@@ -57,6 +59,13 @@
|
||||
{% for data_obj in venue_obj %}
|
||||
<tr role="row">
|
||||
<td>{{data_obj.id}}</td>
|
||||
<td>
|
||||
{% if data_obj.principal %}
|
||||
{{ data_obj.principal }}
|
||||
{% elif data_obj.created_by %}
|
||||
{{ data_obj.created_by }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{data_obj.title}}</td>
|
||||
<td>{{data_obj.address}}</td>
|
||||
<td>{{data_obj.latitude}}</td>
|
||||
|
||||
Reference in New Issue
Block a user