Compare commits

..

14 Commits

Author SHA1 Message Date
b3e23de837 Merge pull request 'development' (#1) from development into main
Reviewed-on: #1
2025-01-23 06:36:53 +00:00
bobbyvish
9d20583a78 fix: event multi image upload 2025-01-23 12:04:24 +05:30
bobbyvish
5693ec3c48 fixed:(customer): fixed issue of veiw button 2025-01-08 13:41:42 +05:30
bobbyvish
bec8f9d608 Update(Customer): added profile photo field 2025-01-08 12:37:07 +05:30
bobbyvish
b559c62926 updated(event): added link field in event 2024-12-24 17:00:59 +05:30
bobbyvish
24949ade3e feat(customer): customer cred and postcode for address 2024-12-20 19:43:44 +05:30
bobbyvish
3425d82891 updated : requirement.txt 2024-12-02 12:05:45 +05:30
bobbyvish
8ccec7012c refactor: custom command to update social key 2024-12-02 11:51:51 +05:30
bobbyvish
3f263b2af5 fixed: filter title with tag and calendar issue 2024-11-28 16:05:32 +05:30
bobbyvish
76ac9413c4 fix: report isssue and custom command to generate individual marchant report 2024-11-28 15:56:20 +05:30
bobbyvish
76323a1ea1 Merge branch 'development' of https://github.com/WDI-Ideas/goodtimes into development 2024-11-06 14:40:28 +05:30
BOBBY VISHWAKARMA
9ba960fcad Merge pull request #102 from WDI-Ideas/feature/key-guest-multiple
Updated : key guest for multiple
2024-10-31 17:49:19 +05:30
bobbyvish
6f07fe4877 Updated : key guest for multiple 2024-10-30 12:36:05 +05:30
97cf4bc753 Add README.md 2024-06-19 10:00:56 +00:00
31 changed files with 1055 additions and 382 deletions

96
README.md Normal file
View File

@@ -0,0 +1,96 @@
Table of Contents
Project Overview - Events Management App with full control on admin side.
Key Features
- Add Events and its related data like venue details.
- Many types of filter searches for events like preferences and location.
Prerequisites
- Installation
- Setting Up a Development Environment (Python, Virtual Environment)
- Cloning the Repository and Installing Dependencies
- Database Configuration
- Running the Project
Project Overview
Description:
- GoodTimes App: Find Fun Events in the UK
Unforgettable experiences await! The GoodTimes app helps you discover and plan exciting events happening across the UK.
Key Features:
- Discover a Variety of Events: Explore a wide range of events tailored to your interests, from concerts and festivals to workshops and sporting events.
Effortless Event Creation: Planning an event yourself? GoodTimes makes it easy to add your event along with its details, including venue information.
Powerful Search Filters: Narrow down your event search using filters based on your preferences and location. Find events happening near you or explore options in specific cities across the UK.
Seamless Event Planning: Get all the information you need in one place. View event details, venue information, and make informed decisions about the events you want to attend.
With GoodTimes, you'll never miss out on the fun!
Basic Computer Knowledge: No prior Python experience is required, but basic computer literacy will be helpful.
Text Editor or IDE: Any text editor or Integrated Development Environment (IDE) that supports Python will work. Some popular options include:
Visual Studio Code
PyCharm (Community Edition is free)
Sublime Text
Atom
Installation
Setting Up a Development Environment:
Install Python (3.10): https://www.python.org/downloads/
Install a virtual environment tool like virtualenv: https://docs.python.org/3/library/venv.html
Create a virtual environment:
python virtualenv my_env # Using venv
source my_env/bin/activate # Activate on Linux/macOS
Cloning the Repository and Installing Dependencies:
Clone this repository using Gitea: http://git.wdipl.com/Rizwan.Shaikh/goodtimes
git clone http://git.wdipl.com/Rizwan.Shaikh/goodtimes
Navigate to the project directory:
cd <goodtimes>
Activate your virtual environment (if not already active).
Install project dependencies:
pip install -r requirements.txt
Database Configuration:
Create a database for your project using MySQL. Refer to your database's documentation for setup instructions.
Running the Project
Development Server:
Activate your virtual environment (if not already active).
Add fixtures for user types:
python manage.py load_custom_fixture
Add 10 Event Categories from admin dashboard with images(all images for categories inside static/images):
1. Arts & Entertainment - image - Black.png
2. Business & Economic - image - business.png
3. Health & Wellness - image - health.png
4. Leisure & Hobbies - image - leisure.png
5. Cultural & Social - image - cultural.png
6. Education & Technology - image - education.png
7. Outdoor Activities - image - outdoor.png
8. Recreation & Sports - image - recreation.png
9. Family - image - cultural.png
10. Shopping Sale - image - business.png
Start the development server:
python manage.py runserver
This will start a local server at http://127.0.0.1:8000/ (or a different port depending on your settings) where you can access your Django application.

View File

@@ -362,6 +362,7 @@ class IAmPrincipalResourceLinkForm(IAmPrincipalForm):
class CreateCustomerForm(forms.Form):
profile_photo = forms.ImageField(label="Profile Image", widget=forms.ClearableFileInput(attrs={'class': 'filepond'}),)
first_name = forms.CharField(max_length=255, required=True, label='First Name')
last_name = forms.CharField(max_length=255, required=True, label='Last Name')
business_name = forms.CharField(max_length=200, required=True, label="Business Name")
@@ -402,6 +403,7 @@ class CreateCustomerForm(forms.Form):
self.fields['preferences'].queryset = EventCategory.objects.all()
class UpdateCustomerForm(forms.Form):
profile_photo = forms.ImageField(label="Profile Image")
first_name = forms.CharField(max_length=255, required=True, label='First Name')
last_name = forms.CharField(max_length=255, required=True, label='Last Name')
business_name = forms.CharField(max_length=200, required=True, label="Business Name")

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.0.2 on 2024-12-20 12:15
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0015_iamprincipal_twitter_profile'),
]
operations = [
migrations.AddField(
model_name='iamprincipalextendeddata',
name='encrypted_pass',
field=models.TextField(blank=True, null=True),
),
]

View File

@@ -13,6 +13,7 @@ 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,
@@ -333,9 +334,16 @@ class IAmPrincipal(AbstractUser):
def __str__(self):
return f"{self.email}"
@staticmethod
def generate_random_password():
"""Generate a password in the format 'GoodTimes@xxxx'."""
random_number = random.randint(1000, 9999) # Generate a 4-digit random number
return f"GoodTimes@{random_number}"
class IAmPrincipalExtendedData(models.Model):
principal = models.OneToOneField(
IAmPrincipal,
related_name="extended_data",
@@ -356,7 +364,7 @@ class IAmPrincipalExtendedData(models.Model):
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.")
encrypted_pass = models.TextField(blank=True, null=True)
class Meta:
db_table = "iam_principal_extended_data"
@@ -368,6 +376,12 @@ class IAmPrincipalExtendedData(models.Model):
self.transferred_on = datetime.datetime.now()
super().save(*args, **kwargs)
@property
def decrypted_field(self):
from goodtimes.services import Encryptor
encryptor = Encryptor()
return encryptor.decrypt(self.encrypted_pass)
class IAmPrincipalResourceLink(models.Model):
principal = models.ForeignKey(
IAmPrincipal,

View File

@@ -42,6 +42,7 @@ 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/get-decrypted-password/<int:customer_id>/', views.GetDecryptedPasswordView.as_view(), name='get_decrypted_password'),
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"),

View File

@@ -24,7 +24,7 @@ from django.utils import timezone
import phonenumbers
from accounts import permission
from goodtimes import constants
from goodtimes.services import EmailService
from goodtimes.services import EmailService, Encryptor
from goodtimes.utils import JsonResponseUtil
from manage_events.models import EventCategory, PrincipalPreference
from manage_referrals.models import ReferralCode
@@ -608,7 +608,7 @@ class CustomerCreateView(LoginRequiredMixin, generic.View):
def post(self, request, *args, **kwargs):
print(request.POST)
# return redirect(self.success_url)
form = self.form_class(request.POST)
form = self.form_class(request.POST, request.FILES)
context = self.get_context_data(form=form)
if not form.is_valid():
return render(request, self.template_name, context=context)
@@ -621,14 +621,21 @@ class CustomerCreateView(LoginRequiredMixin, generic.View):
try:
with transaction.atomic():
random_password = IAmPrincipal.generate_random_password()
# Encrypt the password
encryptor = Encryptor()
encrypted_password = encryptor.encrypt(random_password)
# save principal data
principal_obj = IAmPrincipal.objects.create(
profile_photo = form.cleaned_data.get("profile_photo"),
email=form.cleaned_data.get('email'),
first_name=form.cleaned_data.get('first_name'),
last_name=form.cleaned_data.get('last_name'),
business_name=form.cleaned_data.get('business_name'),
phone_no=form.cleaned_data.get('phone_no'),
password=make_password("goodtimes#2024"),
password=make_password(random_password),
username=form.cleaned_data.get("email"),
email_verified=True,
register_complete=True,
@@ -651,6 +658,7 @@ class CustomerCreateView(LoginRequiredMixin, generic.View):
IAmPrincipalExtendedData.objects.create(
principal=principal_obj,
is_onboarded=True,
encrypted_pass=encrypted_password, # Save encrypted password
)
# save principal preferences record
@@ -665,7 +673,6 @@ class CustomerCreateView(LoginRequiredMixin, generic.View):
is_paid=True,
subscription=free_subscription
)
messages.success(self.request, constants.REGISTRATION_SUCCESS)
return redirect(self.success_url)
except Exception as e:
@@ -704,6 +711,7 @@ class CustomerUpdateView(LoginRequiredMixin, generic.View):
print(f"principal address is {principal_obj.address_line1}")
initial_data = {
"profile_photo": principal_obj.profile_photo,
"first_name": principal_obj.first_name,
"last_name": principal_obj.last_name,
"email": principal_obj.email,
@@ -746,13 +754,15 @@ class CustomerUpdateView(LoginRequiredMixin, generic.View):
except Exception as e:
messages.error(request, f"No Record of customer id {principal_id} is found")
return redirect(self.success_url)
form = self.form_class(request.POST)
form = self.form_class(request.POST, request.FILES)
print(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.profile_photo = form.cleaned_data.get('profile_photo')
principal_obj.first_name = form.cleaned_data.get('first_name')
principal_obj.last_name = form.cleaned_data.get('last_name')
principal_obj.business_name = form.cleaned_data.get("business_name")
@@ -862,6 +872,27 @@ class CustomerListView(LoginRequiredMixin, generic.ListView):
context = super().get_context_data(**kwargs)
context["page_name"] = self.page_name
return context
class GetDecryptedPasswordView(generic.View):
def get(self, request, customer_id):
try:
# Fetch the extended data for the customer
extended_data = IAmPrincipalExtendedData.objects.get(principal_id=customer_id)
if not extended_data.encrypted_pass:
return JsonResponse({"success": False, "message": "No password found."})
# Use Encryptor to decrypt the password
encryptor = Encryptor()
decrypted_password = encryptor.decrypt(extended_data.encrypted_pass)
return JsonResponse({"success": True, "decrypted_password": decrypted_password})
except IAmPrincipalExtendedData.DoesNotExist:
return JsonResponse({"success": False, "message": "Customer not found."})
except Exception as e:
return JsonResponse({"success": False, "message": str(e)})
import pandas as pd
from openpyxl import Workbook, load_workbook
@@ -1022,17 +1053,23 @@ class CustomerTransferView(LoginRequiredMixin, generic.View):
# Send the email
try:
temp_password = "goodtimes#2024"
principal_preference = IAmPrincipalExtendedData.objects.get(principal=principal_obj)
# Use Encryptor to decrypt the password
encryptor = Encryptor()
temp_password = encryptor.decrypt(principal_preference.encrypted_pass)
# updating password
principal_obj.password = make_password(temp_password)
principal_obj.save()
principal_preference.is_transferred = True
principal_preference.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)}")
@@ -1159,12 +1196,18 @@ class CustomerImportView(LoginRequiredMixin, generic.View):
error_log.append(f"Row {idx}: One or more preferences are invalid.")
continue
random_password = IAmPrincipal.generate_random_password()
# Encrypt the password
encryptor = Encryptor()
encrypted_password = encryptor.encrypt(random_password)
# collect the principals
principal = IAmPrincipal(
first_name=first_name.strip().capitalize(),
last_name=last_name.strip().capitalize(),
email=email.strip(),
password=make_password("goodtimes#2024"),
password=make_password(random_password),
username=email.strip(),
email_verified=True,
register_complete=True,
@@ -1207,7 +1250,7 @@ class CustomerImportView(LoginRequiredMixin, generic.View):
ReferralCode.create_referral_code_for_user_manager(principal=principal, principal_type=principal_type)
# Create IAmPrincipalExtendedData record
IAmPrincipalExtendedData.objects.create(principal=principal, is_onboarded=True)
IAmPrincipalExtendedData.objects.create(principal=principal, is_onboarded=True,encrypted_pass=encrypted_password,)
# Create PrincipalSubscription record
subscription = PrincipalSubscription(

View File

@@ -8,13 +8,15 @@ https://docs.djangoproject.com/en/5.0/howto/deployment/asgi/
"""
import os
import django
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "goodtimes.settings")
django.setup()
from django.urls import path
from django.core.asgi import get_asgi_application
from channels.routing import ProtocolTypeRouter, URLRouter
from chat.routing import websocket_urlpatterns
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "goodtimes.settings")
django_asgi_app = get_asgi_application()
application = ProtocolTypeRouter(
{

View File

@@ -695,58 +695,19 @@ class FacebookAPI:
self.app_id = settings.FACEBOOK_APP_ID
self.app_secret = settings.FACEBOOK_APP_SECRET
self.page_id = settings.FACEBOOK_PAGE_ID
self.page_access_token = None
def _get_short_lived_user_access_token(self):
try:
url = f"https://graph.facebook.com/oauth/access_token?grant_type=client_credentials&client_id={self.app_id}&client_secret={self.app_secret}"
response = requests.get(url)
# response.raise_for_status()
print(f"short lived token {response.json()}")
return response.json()['access_token']
except requests.exceptions.RequestException as e:
print(f"Error getting short-lived user access token: {e}")
return None
def _get_long_lived_user_access_token(self, short_lived_token):
try:
url = f"https://graph.facebook.com/v20.0/oauth/access_token?grant_type=fb_exchange_token&client_id={self.app_id}&client_secret={self.app_secret}&fb_exchange_token={short_lived_token}"
response = requests.get(url)
# response.raise_for_status()
print(f"long lived access token : {response.json()}")
return response.json()['access_token']
except requests.exceptions.RequestException as e:
print(f"Error getting long-lived user access token: {e}")
return None
def _get_page_access_token(self, long_lived_token):
url = f"https://graph.facebook.com/{self.page_id}?fields=access_token&access_token={long_lived_token}"
response = requests.get(url)
# response.raise_for_status()
print(f"page access token is {response.json()}")
# self.page_access_token = response.json()["access_token"]
def authenticate(self):
# short_lived_token = self._get_short_lived_user_access_token()
# if not short_lived_token:
# return False
# long_lived_token = self._get_long_lived_user_access_token(short_lived_token)
# if not long_lived_token:
# return False
# self._get_page_access_token(short_lived_token)
self.page_access_token = settings.FACEBOOK_ACCESS_TOKEN
return True
self.graph_api_version = settings.FACEBOOK_GRAPH_VERSION_API
self.access_token = settings.FACEBOOK_ACCESS_TOKEN # long live access token
def post_photo(self, image_url, caption):
if not self.page_access_token:
if not self.access_token:
print("Page access token not obtained. Call authenticate() first.")
return False
try:
url = f"https://graph.facebook.com/v20.0/{self.page_id}/photos"
url = f"https://graph.facebook.com/{self.graph_api_version}/{self.page_id}/photos"
params = {
"message": caption,
"url": image_url,
"access_token": self.page_access_token,
"access_token": self.access_token,
}
response = requests.post(url, params=params)
# response.raise_for_status()
@@ -765,9 +726,6 @@ class FacebookPoster:
self.facebook_api = facebook_api
def post_photo(self, image_url, caption):
if not self.facebook_api.authenticate():
print("Authentication failed. Please try again.")
return {'success': False, 'message': 'Error posting photo. Authenticate failed'}
result = self.facebook_api.post_photo(image_url, caption)
if not result:
return {'success': False, 'message': 'Error posting photo in Facebook'}
@@ -1169,4 +1127,18 @@ class StripeService:
)
return {'success': True, 'data': subscription}
except stripe.error.StripeError as e:
return {'success': False, 'message': f'Error cancelling subscription auto-renewal: {e}'}
return {'success': False, 'message': f'Error cancelling subscription auto-renewal: {e}'}
from cryptography.fernet import Fernet
class Encryptor:
def __init__(self):
self.key = "paMSf3Ny8KAMs1tRLcVOQQhRxTnInHLwP7WtVdm8O_4="
self.fernet = Fernet(self.key)
def encrypt(self, plaintext):
return self.fernet.encrypt(plaintext.encode()).decode()
def decrypt(self, encrypted_text):
return self.fernet.decrypt(encrypted_text.encode()).decode()

View File

@@ -77,13 +77,14 @@ THIRD_PARTY_APPS = [
"taggit",
"django_quill",
"corsheaders",
'django_extensions',
"allauth",
"allauth.account",
"allauth.socialaccount",
"allauth.socialaccount.providers.apple",
"allauth.socialaccount.providers.google",
"django_filters",
# "django_crontab",
"django_crontab",
# "django_celery_results",
# "django_celery_beat",
]
@@ -211,6 +212,7 @@ TIME_FORMAT = "H:i p"
# otp expire time limit
OTP_EXPIRE_TIME = 1 # mins
DEFAULT_CHARSET = 'utf-8'
# Default primary key field type
@@ -336,7 +338,7 @@ CHANNEL_LAYERS = {
WEBSOCKET_TIMEOUT = 30
CRONJOBS = [
# ("0 9 * * 1-5", "manage_games.cron.update_game_status_live"),
# ('0 0 * * *', 'myapp.cron.daily_task >> /path/to/logfile.log 2>&1'),
]
GOOGLE_MAPS_API_KEY = env.str("GOOGLE_MAPS_API_KEY")
@@ -356,6 +358,7 @@ TWITTER_ACCESS_TOKEN_SECRET = env.str("TWITTER_ACCESS_TOKEN_SECRET")
FACEBOOK_APP_ID = env.str("FACEBOOK_APP_ID")
FACEBOOK_APP_SECRET = env.str("FACEBOOK_APP_SECRET")
FACEBOOK_PAGE_ID = env.str("FACEBOOK_PAGE_ID")
FACEBOOK_GRAPH_VERSION_API = env.str("FACEBOOK_GRAPH_VERSION_API")
FACEBOOK_ACCESS_TOKEN = env.str("FACEBOOK_ACCESS_TOKEN")
# Instagram Key

View File

@@ -6,7 +6,7 @@ import colorlog
# from logging.handlers import TimedRotatingFileHandler
DEBUG = False
ALLOWED_HOSTS = ["staging.goodtimesltd.co.uk", "77.68.8.229"]
ALLOWED_HOSTS = ["staging.goodtimesltd.co.uk", "77.68.8.229",".staging.goodtimesltd.co.uk"]
LOGGING_DIR = os.path.join(

View File

@@ -63,9 +63,9 @@ urlpatterns = [
# path('api/', include("accounts.api.urls")),
]
# if settings.DEBUG:
# import debug_toolbar
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
if settings.DEBUG:
import debug_toolbar
# urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
# urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
# urlpatterns += [path("__debug__/", include(debug_toolbar.urls))]
urlpatterns += [path("__debug__/", include(debug_toolbar.urls))]

View File

@@ -7,7 +7,7 @@ class EventFilter(filters.FilterSet):
"""
FilterSet for Event model.
"""
title = filters.CharFilter(field_name="title", lookup_expr="icontains")
title = filters.CharFilter(method="filter_title")
location = filters.CharFilter(field_name="venue__address", lookup_expr="icontains")
category = filters.CharFilter(method="filter_category")
start_date = filters.DateFilter(field_name="start_date", lookup_expr="gte")
@@ -29,6 +29,13 @@ class EventFilter(filters.FilterSet):
'age_group',
]
def filter_title(self, queryset, name, value):
if value:
return queryset.filter(
Q(title__icontains=value) | Q(tags__name__icontains=value)
).distinct()
return queryset
def filter_category(self, queryset, name, value):
category = value.split(',')
return queryset.filter(category__title__in=category)

View File

@@ -99,6 +99,7 @@ class EventListSerializer(serializers.ModelSerializer):
"entry_fee",
"key_guest",
"age_group",
"link",
# "images",
# "is_favorited",
# "reviews",
@@ -107,6 +108,12 @@ class EventListSerializer(serializers.ModelSerializer):
# "draft",
]
# def to_representation(self, instance):
# """Customize the representation of the instance."""
# representation = super().to_representation(instance)
# representation['key_guest'] = instance.get_key_guests() # Return as a list
# return representation
class EventDetailSerializer(serializers.ModelSerializer):
tags = TagSerializer(many=True, read_only=True)
@@ -138,6 +145,7 @@ class EventDetailSerializer(serializers.ModelSerializer):
"key_guest",
"coupon_code",
"coupon_description",
"link",
"age_group",
"images",
"is_favorited",
@@ -178,6 +186,12 @@ class EventDetailSerializer(serializers.ModelSerializer):
"status_display": interaction.get_status_display(),
}
return None
# def to_representation(self, instance):
# """Customize the representation of the instance."""
# representation = super().to_representation(instance)
# representation['key_guest'] = instance.get_key_guests() # Return as a list
# return representation
class CreateEventSerializer(serializers.ModelSerializer):
@@ -210,11 +224,19 @@ class CreateEventSerializer(serializers.ModelSerializer):
"tags",
"coupon_code",
"coupon_description",
"link"
]
def validate_key_guest(self, value):
if value and not isinstance(value, str):
raise serializers.ValidationError("key_guest must be a string")
return value
def create(self, validated_data):
tags = validated_data.pop("tags", None)
images_data = validated_data.pop("images", None)
key_guest = validated_data.pop("key_guest", None)
event = Event.objects.create(**validated_data)
if tags:
@@ -224,11 +246,15 @@ class CreateEventSerializer(serializers.ModelSerializer):
for image_data in images_data:
EventImage.objects.create(event=event, image=image_data)
if key_guest:
event.set_key_guests(key_guest)
event.save()
return event
def update(self, instance, validated_data):
tags = validated_data.pop("tags", None)
images_data = validated_data.pop("images", None)
key_guest = validated_data.pop("key_guest", None)
# Update fields if there is any change.
if tags is not None:
@@ -241,6 +267,9 @@ class CreateEventSerializer(serializers.ModelSerializer):
for image_data in images_data:
EventImage.objects.create(event=instance, image=image_data)
if key_guest is not None:
instance.set_key_guests(key_guest)
# Update other fields
for attr, value in validated_data.items():
setattr(instance, attr, value)
@@ -248,6 +277,12 @@ class CreateEventSerializer(serializers.ModelSerializer):
return instance
# def to_representation(self, instance):
# """Customize the representation of the instance."""
# representation = super().to_representation(instance)
# representation['key_guest'] = instance.get_key_guests() # Return as a list
# return representation
class CreateVenueSerializer(serializers.ModelSerializer):
class Meta:

View File

@@ -11,7 +11,7 @@ urlpatterns = [
name="add_event",
),
path("edit-event/<int:pk>/", views.EventEditAPIView.as_view(), name="event-edit"),
path("get-events/", views.EventsAPIView.as_view(), name="events"),
path(
"event/<int:pk>/",
views.EventDetailAPIView.as_view(),
@@ -135,6 +135,8 @@ urlpatterns = [
name="age_group_list"
),
path("get-events/calendar/", views.EventsCalenderAPIView.as_view(), name="events-calendar"),
# event list with filter
path(
"events/",

View File

@@ -139,6 +139,7 @@ class CreateVenueApi(APIView):
def post(self, request):
data = request.data.copy()
print("prindata is ", data)
# Convert latitude and longitude to float and round to 8 decimal places
data["latitude"] = round(float(data["latitude"]), 8)
@@ -199,67 +200,6 @@ class VenueDeleteAPIView(APIView):
)
class EventsAPIView(APIView):
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
def get(self, request, *args, **kwargs):
filter = request.query_params.get("filter", None)
query = request.query_params.get("query", None)
category_id = request.query_params.get("category_id", None)
params = [
"expensive",
"cheap",
"preference",
"today",
"tomorrow",
"category",
"key_guest",
"tags",
]
if filter not in params:
return ApiResponse.error(
status=status.HTTP_400_BAD_REQUEST,
message=constants.FAILURE,
errors="No filter found",
)
try:
if filter == "today":
events = services.EventFilterService.filter_events_for_today()
elif filter == "tomorrow":
events = services.EventFilterService.filter_events_for_tomorrow()
elif filter == "key_guest":
events = services.EventFilterService.filter_events_by_search(
search_query=query
)
elif filter == "category" and category_id is not None:
events = services.EventFilterService.filter_events_by_category(
int(category_id)
)
else:
events = services.EventFilterService.filter_events(
filter_type=filter, principal=request.user
)
# serializer = EventDetailSerializer(
# events, context={"request": request}, many=True
# )
serializer = EventListSerializer(
events, context={"request": request}, 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 MyEventsAPIView(APIView):
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
@@ -1040,6 +980,46 @@ class AgeGroupListView(APIView):
return ApiResponse.success(message=constants.SUCCESS, data=serializer.data)
class EventsCalenderAPIView(APIView):
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
def get(self, request, *args, **kwargs):
try:
principal = request.user
queryset = Event.objects.filter(
active=True,
draft=False,
deleted=False,
end_date__gte=timezone.now().date()
)
# queryset = Event.objects.filter(
# ((Q(active=True) & Q(draft=False) & Q(deleted=False) & Q(end_date__gte=timezone.now().date())))
# )
preferences = PrincipalPreference.objects.get(principal=principal)
preferred_categories_ids = preferences.preferred_categories.values_list("id", flat=True)
# Filter the queryset to only include events in the user's preferred categories
queryset = queryset.filter(Q(category__in=preferred_categories_ids) | Q(principal=principal))
serializer = EventListSerializer(
queryset, context={"request": request}, 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 EventListView(generics.ListAPIView):
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]

View File

@@ -46,6 +46,7 @@ class EventForm(forms.ModelForm):
"title",
# "event_master",
"description",
"link",
"image",
"event_images",
# "status",
@@ -148,6 +149,7 @@ class VenueForm(forms.ModelForm):
required=True
)
image = forms.ImageField(required=True)
postcode = forms.CharField(required=True, max_length=10)
latitude = forms.DecimalField(
widget=forms.NumberInput()
)
@@ -161,6 +163,7 @@ class VenueForm(forms.ModelForm):
"principal",
"title",
"address",
"postcode",
"image",
"latitude",
"longitude",

View File

@@ -0,0 +1,60 @@
from django.core.mail import EmailMessage
from django.conf import settings
from django.core.management.base import BaseCommand
from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta
from django.utils.timezone import now
from django.contrib.auth import get_user_model
import calendar
from manage_events.report import generate_event_report, generate_event_report_pdf_three
class Command(BaseCommand):
help = 'Getting event reports of specific event managers'
def add_arguments(self, parser):
parser.add_argument('month', type=int, help='Month number (1-12)')
parser.add_argument('email', type=str, help='User email address')
parser.add_argument('mail_send_on', type=str, help='Email to send the report')
def handle(self, *args, **kwargs):
month = kwargs['month']
email = kwargs['email']
mail_send_on = kwargs['mail_send_on']
# Validate the month
if month < 1 or month > 12:
self.stdout.write(self.style.ERROR("Invalid month. Must be between 1 and 12."))
return
User = get_user_model()
try:
user = User.objects.get(email=email)
except User.DoesNotExist:
self.stdout.write(self.style.ERROR(f"User with email {email} does not exist."))
return
# Calculate start and end dates of the month
year = now().year # Assuming the current year
start_date = datetime(year, month, 1)
last_day = calendar.monthrange(year, month)[1]
end_date = datetime(year, month, last_day)
report_data = generate_event_report(user.id, start_date, end_date)
if report_data:
pdf_data, filename = generate_event_report_pdf_three(user, report_data, start_date)
self.send_email_with_attachment(mail_send_on, pdf_data, filename)
def send_email_with_attachment(self, email, pdf_data, filename):
try:
email_message = EmailMessage(
subject="Monthly Event Report",
body="Please find the attached report for the last month.",
to=[email],
from_email=settings.DEFAULT_FROM_EMAIL,
)
email_message.attach(filename, pdf_data, "application/pdf")
email_message.send()
self.stdout.write(self.style.SUCCESS(f"Email successfully sent to {email} with attachment {filename}."))
except Exception as e:
self.stdout.write(self.style.ERROR(f"Failed to send email to {email}. Error: {str(e)}"))

View File

@@ -17,7 +17,7 @@ class Command(BaseCommand):
users = event_managers()
for user in users:
report_data = generate_event_report(user.id)
report_data = generate_event_report(user.id, start_date, end_date)
if report_data:
pdf_data, filename = generate_event_report_pdf_three(user, report_data)
self.send_email_with_attachment(user.email, pdf_data, filename)

View File

@@ -0,0 +1,103 @@
import os
from django.conf import settings
import requests
from dotenv import load_dotenv
from django.core.management.base import BaseCommand
# Load .env variables
load_dotenv()
class Command(BaseCommand):
help = 'Update Facebook long-lived access tokens and page access token'
def __init__(self):
super().__init__()
self.app_id = settings.FACEBOOK_APP_ID
self.app_secret = settings.FACEBOOK_APP_SECRET
self.page_id = settings.FACEBOOK_PAGE_ID
self.graph_api_version = settings.FACEBOOK_GRAPH_VERSION_API
self.page_access_token = settings.FACEBOOK_ACCESS_TOKEN
self.long_lived_token = settings.FACEBOOK_ACCESS_TOKEN
def handle(self, *args, **kwargs):
"""Handle the token refresh and update .env file."""
if self.refresh_access_tokens():
self.stdout.write(self.style.SUCCESS("Successfully refreshed Facebook tokens."))
else:
self.stdout.write(self.style.ERROR("Failed to refresh Facebook tokens."))
def _exchange_short_to_long_lived_token(self, short_lived_token):
"""Exchange short-lived token for long-lived token."""
try:
url = f"https://graph.facebook.com/{self.graph_api_version}/oauth/access_token"
params = {
"grant_type": "fb_exchange_token",
"client_id": self.app_id,
"client_secret": self.app_secret,
"fb_exchange_token": short_lived_token,
}
response = requests.get(url, params=params)
response.raise_for_status()
long_lived_token = response.json().get("access_token")
self.stdout.write(self.style.SUCCESS("Successfully exchanged for long-lived user access token."))
return long_lived_token
except requests.exceptions.RequestException as e:
self.stdout.write(self.style.ERROR(f"Error exchanging short-lived token: {e}"))
return None
def _get_page_access_token(self, user_token):
"""Retrieve Page Access Token."""
try:
url = f"https://graph.facebook.com/{self.graph_api_version}/{self.page_id}"
params = {
"fields": "access_token",
"access_token": user_token,
}
response = requests.get(url, params=params)
response.raise_for_status()
page_access_token = response.json().get("access_token")
self.stdout.write(self.style.SUCCESS("Successfully obtained page access token."))
return page_access_token
except requests.exceptions.RequestException as e:
self.stdout.write(self.style.ERROR(f"Error retrieving page access token: {e}"))
return None
def _update_env_variable(self, key, value):
"""Update a variable in the .env file."""
with open('.env', 'r') as file:
lines = file.readlines()
with open('.env', 'w') as file:
updated = False
for line in lines:
if line.startswith(key):
file.write(f"{key}={value}\n")
updated = True
else:
file.write(line)
if not updated:
file.write(f"{key}={value}\n")
def refresh_access_tokens(self):
"""Refresh long-lived user access token and page access token."""
if not self.long_lived_token:
self.stdout.write(self.style.ERROR("No valid long-lived user access token found."))
return False
# Refresh long-lived user token (optional, based on expiry)
refreshed_user_token = self._exchange_short_to_long_lived_token(self.long_lived_token)
if refreshed_user_token:
self.long_lived_token = refreshed_user_token
# Refresh page access token
# page_access_token = self._get_page_access_token(self.long_lived_token)
# if page_access_token:
# self.page_access_token = page_access_token
# # Update tokens in .env file
self._update_env_variable("FACEBOOK_ACCESS_TOKEN", self.long_lived_token)
# self._update_env_variable("FACEBOOK_PAGE_ACCESS_TOKEN", self.page_access_token)
return True
self.stdout.write(self.style.ERROR("Failed to refresh page access token."))
return False

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.0.2 on 2024-12-20 09:29
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('manage_events', '0016_freeusagefeaturelimit'),
]
operations = [
migrations.AddField(
model_name='venue',
name='postcode',
field=models.CharField(blank=True, max_length=20, null=True),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.0.2 on 2024-12-24 11:20
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('manage_events', '0017_venue_postcode'),
]
operations = [
migrations.AddField(
model_name='event',
name='link',
field=models.URLField(blank=True, max_length=255, null=True),
),
]

View File

@@ -51,13 +51,14 @@ class Venue(BaseModel):
longitude = models.DecimalField(
max_digits=14, decimal_places=8, blank=True, null=True
)
postcode = models.CharField(max_length=20, blank=True, null=True)
def __str__(self):
return self.title
class AgeGroups(BaseModel):
name = models.CharField(max_length=10, unique=True)
class Meta:
db_table = "age_group"
@@ -124,11 +125,25 @@ class Event(BaseModel):
social_media_shares_count = models.IntegerField(default=0)
coupon_code = models.CharField(max_length=255, blank=True, null=True)
coupon_description = models.TextField(blank=True, null=True)
link = models.URLField(max_length=255, blank=True, null=True)
def increment_shares(self):
self.social_media_shares_count += 1
self.save()
def set_key_guests(self, guests):
"""Set the key guests as a comma-seperated string."""
if isinstance(guests, list):
self.key_guest = ",".join(guests)
elif isinstance(guests, str):
self.key_guest = guests
else:
raise ValueError("Guests must be a comma-seperated string")
def get_key_guests(self):
"""Return the key guests as a list of strings."""
return self.key_guest.split(",") if self.key_guest else []
def __str__(self):
return self.title

View File

@@ -52,8 +52,8 @@ def get_previous_month_date_range():
return first_day_of_previous_month, last_day_of_previous_month
def generate_event_report(user_id):
start_date, end_date = get_previous_month_date_range()
def generate_event_report(user_id, start_date, end_date):
# start_date, end_date = get_previous_month_date_range()
user = User.objects.get(id=user_id)
# events = Event.objects.filter(
@@ -167,8 +167,8 @@ def generate_event_report(user_id):
return report_data
def generate_event_report_pdf_three(user, report_data):
start_date, _ = get_previous_month_date_range()
def generate_event_report_pdf_three(user, report_data, start_date):
# start_date, _ = get_previous_month_date_range()
filename = generate_filename(user.email, start_date)
buffer = BytesIO()

View File

@@ -584,7 +584,7 @@ class SocialMediaPostView(generic.View):
'success_messages': success_messages
}, status=400)
caption = f"{event.title}\nDuration: {event.start_date} to {event.end_date}\nAddress: {event.venue.address}"
caption = f"Venue: {event.venue.title} \n Event: {event.title}\n Description: {event.description} \n Date: {event.start_date} to {event.end_date}\n Time: {event.from_time} - {event.to_time} \n Address: {event.venue.address}"
if platform in ['instagram', 'facebook', 'twitter', 'all']:
if platform in ['twitter', 'all']:
@@ -599,12 +599,17 @@ class SocialMediaPostView(generic.View):
image_url = request.build_absolute_uri(event.image.url)
if platform in ['facebook', 'all']:
facebook_api = FacebookAPI()
facebook_poster = FacebookPoster(facebook_api)
result = facebook_poster.post_photo(image_url, caption)
if result["success"]:
success_messages.append("Posted to Facebook successfully")
else:
try:
print("facebook is called")
facebook_api = FacebookAPI()
facebook_poster = FacebookPoster(facebook_api)
result = facebook_poster.post_photo(image_url, caption)
if result["success"]:
success_messages.append("Posted to Facebook successfully")
else:
errors.append("Fail to post on Facebook")
except Exception as e:
print(f"facebook error {e}")
errors.append("Fail to post on Facebook")
if platform in ['instagram', 'all']:

View File

@@ -0,0 +1,19 @@
# Generated by Django 5.0.2 on 2024-12-20 09:29
import django_quill.fields
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('manage_subscriptions', '0014_alter_subscription_long_description'),
]
operations = [
migrations.AlterField(
model_name='subscription',
name='long_description',
field=django_quill.fields.QuillField(),
),
]

View File

@@ -19,6 +19,7 @@ defusedxml==0.7.1
Django==5.0.2
django-allauth==0.61.1
django-cors-headers==4.3.1
django-crontab==0.7.1
django-debug-toolbar==4.3.0
django-environ==0.11.2
django-extensions==3.2.3
@@ -32,6 +33,7 @@ djangorestframework==3.14.0
djangorestframework-simplejwt==5.3.1
et-xmlfile==1.1.0
googlemaps==4.10.0
gunicorn==23.0.0
h11==0.14.0
httpcore==1.0.4
httpx==0.27.0
@@ -46,6 +48,7 @@ oauthlib==3.2.2
onesignal-sdk==2.0.0
openpyxl==3.1.4
orjson==3.9.15
packaging==24.2
pandas==2.2.2
phonenumbers==8.13.30
pillow==10.2.0
@@ -56,6 +59,7 @@ PyJWT==2.8.0
pyngrok==7.1.2
pyOpenSSL==24.0.0
python-dateutil==2.9.0.post0
python-dotenv==1.0.1
python3-openid==3.2.0
pytz==2024.1
PyYAML==6.0.1
@@ -71,7 +75,7 @@ stripe==8.2.0
tqdm==4.66.2
tweepy==4.14.0
Twisted==23.10.0
#twisted-iocpsupport==1.0.4
# twisted-iocpsupport==1.0.4
txaio==23.1.1
typing_extensions==4.9.0
tzdata==2024.1

View File

@@ -1,9 +1,30 @@
{% 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" %}
<!-- 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" %}
{% include "cdn_through_html/filepond_cdn_css.html" %}
<style>
/* Adjust the width and height of the FilePond container */
.filepond--root {
max-width: 200px; /* Set your desired width */
height: auto; /* Adjust height based on content */
margin: auto; /* Center the input */
}
/* Control the size of the preview panel */
.filepond--panel {
height: 200px !important; /* Set a fixed height for the panel */
}
/* Optionally customize the labels or placeholders */
.filepond--label-action {
font-size: 14px; /* Adjust font size */
}
</style>
{% endblock %}
{% block content %}
@@ -12,10 +33,12 @@
<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 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>
@@ -24,14 +47,15 @@
<div class="statbox widget box box-shadow">
<div class="widget-content widget-content-area">
<form method="post" id="addCustomer">
<form method="post" id="addCustomer" 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 class="d-grid"><button class="btn btn-primary btn-block"
type="submit">Submit</button></div>
</div>
</form>
</div>
</div>
</div>
@@ -43,155 +67,216 @@
{% 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>
<!-- include required js cdn link through html here -->
{% include "cdn_through_html/filepond_cdn_js.html" %}
{% 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() {
<script>
var start_date = flatpickr(document.getElementById('id_free_start_date'), {
// minDate: "today",
onChange: function(selectedDates, dateStr, instance) {
end_date.set('minDate', selectedDates[0]);
// Register required FilePond plugins
FilePond.registerPlugin(
FilePondPluginFileValidateType,
FilePondPluginImageExifOrientation,
FilePondPluginImagePreview,
FilePondPluginImageCrop,
FilePondPluginImageResize,
FilePondPluginImageTransform,
);
// Initialize FilePond for profile photo input
document.addEventListener("DOMContentLoaded", function () {
const profileInput = FilePond.create(
document.querySelector('.filepond'), // Target elements with class "filepond"
{
storeAsFile: true,
imagePreviewHeight: 170,
imageCropAspectRatio: '1:1',
imageResizeTargetWidth: 200,
imageResizeTargetHeight: 200,
stylePanelLayout: 'compact circle',
styleLoadIndicatorPosition: 'center bottom',
styleProgressIndicatorPosition: 'right bottom',
styleButtonRemoveItemPosition: 'left bottom',
styleButtonProcessItemPosition: 'right bottom',
labelIdle: `
<span class="no-image-placeholder">
<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-user">
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
<circle cx="12" cy="7" r="4"></circle>
</svg>
</span>
<p class="drag-para">Drag & Drop your picture or
<span class="filepond--label-action" tabindex="0">Browse</span>
</p>
`,
// files: [
// {
// // Preload the server file reference if it exists
// source: "{% if form.profile_photo.value %}{{ form.profile_photo.value.url }}{% endif %}",
// options: {
// type: 'local',
// },
// },
// ],
}
);
});
$(document).ready(function () {
// Initialize FilePond
// var profile = initializeFilePond('id_profile_photo');
// var profileUrl = "{% if form.profile_photo.value %}{{ form.profile_photo.value.url }}{% endif %}";
// if (profileUrl) {
// profile.addFile(profileUrl)
// }
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,
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.");
// Add custom validation method for greater than start date
$.validator.addMethod("greaterThanStartDate", function (value, element) {
var startDate = $('#id_free_start_date').val();
if (!startDate || !value) {
return true;
}
return new Date(value) > new Date(startDate);
}, "The end date must be after the start date.");
$("#addCustomer").validate({
rules: {
first_name: {
required: true,
maxlength: 15,
noSpecialChars: true,
startsWithLetter: true,
},
last_name: {
required: true,
maxlength: 15,
noSpecialChars: true,
startsWithLetter: true,
},
email: {
required: true,
validEmail: true,
},
preferences: {
required: true,
minlength: 1
},
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)
}
});
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,
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.");
// Add custom validation method for greater than start date
$.validator.addMethod("greaterThanStartDate", function(value, element) {
var startDate = $('#id_free_start_date').val();
if (!startDate || !value) {
return true;
},
messages: {
first_name: {
required: "Please enter your first name.",
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.",
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."
}
return new Date(value) > new Date(startDate);
}, "The end date must be after the start date.");
},
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) {
$("#addCustomer").validate({
rules: {
first_name: {
required: true,
maxlength:15,
noSpecialChars: true,
startsWithLetter: true,
},
last_name: {
required: true,
maxlength: 15,
noSpecialChars: true,
startsWithLetter: true,
},
email: {
required: true,
validEmail: true,
},
preferences: {
required: true,
minlength: 1
},
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.",
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.",
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."
}
},
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) {
// Check if form is valid before submission
if ($(form).valid()) {
// Disable the submit button to prevent multiple submissions
$('button[type="submit"]').prop('disabled', true);
form.submit();
}
// Check if form is valid before submission
if ($(form).valid()) {
// Disable the submit button to prevent multiple submissions
$('button[type="submit"]').prop('disabled', true);
form.submit();
}
});
}
});
// Trigger validation for select2 fields on change
$('#id_preferences').on('change', function() {
$(this).valid();
});
// Trigger validation for select2 fields on change
$('#id_preferences').on('change', function () {
$(this).valid();
});
// Trigger validation for flatpickr fields on change
$('#id_free_start_date').on('change', function() {
$(this).valid();
});
$('#id_free_end_date').on('change', function() {
$(this).valid();
});
})
</script>
// Trigger validation for flatpickr fields on change
$('#id_free_start_date').on('change', function () {
$(this).valid();
});
$('#id_free_end_date').on('change', function () {
$(this).valid();
});
})
</script>
{% endblock %}

View File

@@ -4,6 +4,26 @@
<!-- 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" %}
{% include "cdn_through_html/filepond_cdn_css.html" %}
<style>
/* Adjust the width and height of the FilePond container */
.filepond--root {
max-width: 200px; /* Set your desired width */
height: auto; /* Adjust height based on content */
margin: auto; /* Center the input */
}
/* Control the size of the preview panel */
.filepond--panel {
height: 200px !important; /* Set a fixed height for the panel */
}
/* Optionally customize the labels or placeholders */
.filepond--label-action {
font-size: 14px; /* Adjust font size */
}
</style>
{% endblock %}
{% block content %}
@@ -33,7 +53,7 @@
<div class="statbox widget box box-shadow">
<div class="widget-content widget-content-area">
<form method="post" id="addCustomer">
<form method="post" id="addCustomer" enctype="multipart/form-data">
{% csrf_token %}
{% include 'includes/dynamic_template_form.html' with form=form %}
<div class="mt-4 mb-0">
@@ -53,12 +73,73 @@
{% block javascript %}
<!-- include required js cdn link through html here -->
{% include "cdn_through_html/filepond_cdn_js.html" %}
{% 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>
// Register required FilePond plugins
FilePond.registerPlugin(
FilePondPluginFileValidateType,
FilePondPluginImageExifOrientation,
FilePondPluginImagePreview,
FilePondPluginImageCrop,
FilePondPluginImageResize,
FilePondPluginImageTransform
);
// Initialize FilePond for profile photo input
document.addEventListener("DOMContentLoaded", function () {
const existingImageUrl = "{% if form.profile_photo.value %}{{ form.profile_photo.value.url }}{% endif %}";
const profileInput = FilePond.create(
document.querySelector('#id_profile_photo'), // Target the file input by its ID
{
storeAsFile: true,
imagePreviewHeight: 170,
imageCropAspectRatio: '1:1', // Square aspect ratio for cropping
imageResizeTargetWidth: 200, // Resize to 200px width
imageResizeTargetHeight: 200, // Resize to 200px height
stylePanelLayout: 'compact circle', // Circular layout for the preview
styleLoadIndicatorPosition: 'center bottom',
styleProgressIndicatorPosition: 'right bottom',
styleButtonRemoveItemPosition: 'left bottom',
styleButtonProcessItemPosition: 'right bottom',
labelIdle: `
<span class="no-image-placeholder">
<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-user">
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
<circle cx="12" cy="7" r="4"></circle>
</svg>
</span>
<p class="drag-para">Drag & Drop your picture or
<span class="filepond--label-action" tabindex="0">Browse</span>
</p>
`,
allowImageCrop: true, // Enable cropping
allowImageResize: true, // Enable resizing
imageTransformOutputMimeType: 'image/jpeg', // Ensure the output is a JPEG
imageTransformOutputQuality: 90, // Adjust the quality of the output image
allowProcess: false, // Prevent automatic processing to maintain cropping
}
);
// If there's an existing image, preload it
if (existingImageUrl) {
fetch(existingImageUrl)
.then((response) => response.blob())
.then((blob) => {
const file = new File([blob], "profile_photo.jpg", { type: blob.type });
profileInput.addFile(file);
})
.catch((error) => console.error("Error loading existing image:", error));
}
});
$(document).ready(function() {
var start_date = flatpickr(document.getElementById('id_free_start_date'), {

View File

@@ -1,8 +1,10 @@
{% extends 'layout/base_template.html' %}
{% load static %}
{% block stylesheet %}
<!-- include required css cdn link through html here -->
{% include "cdn_through_html/datatable_cdn_css.html" %}
<!-- include required css cdn link through html here -->
{% include "cdn_through_html/datatable_cdn_css.html" %}
{% include "cdn_through_html/modal_cdn_css.html" %}
{% include "cdn_through_html/sweetalert2_cdn_css.html" %}
{% endblock %}
@@ -19,7 +21,7 @@
<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>
@@ -27,12 +29,12 @@
<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>
<div class="row layout-spacing">
<div class="col-lg-12">
<div class="statbox widget box box-shadow">
@@ -46,79 +48,93 @@
<th class="checkbox-column text-center sorting_asc" tabindex="0"
aria-controls="style-3" rowspan="1" colspan="1" aria-sort="ascending"
style="width: 69.2656px;"> Record Id </th>
<th class="text-center sorting" tabindex="0" aria-controls="style-3" rowspan="1"
style="width: 44.2344px;">Image</th>
<th class="text-center sorting" tabindex="0" aria-controls="style-3" rowspan="1" colspan="1"
style="width: 79.7969px;">First Name</th>
<th class="text-center sorting" tabindex="0" aria-controls="style-3" rowspan="1" colspan="1"
style="width: 77.3281px;">Last Name</th>
<th class="text-center sorting" tabindex="0" aria-controls="style-3" rowspan="1" colspan="1"
style="width: 143.516px;">Email</th>
<!-- <th class="text-center sorting" tabindex="0" aria-controls="style-3" rowspan="1" colspan="1"
style="width: 98.875px;">Mobile No.</th> -->
<th class="text-center sorting" tabindex="0" aria-controls="style-3" rowspan="1" colspan="1"
style="width: 98.875px;">Principal Type</th>
<th class="text-center sorting" tabindex="0" aria-controls="style-3"
rowspan="1" style="width: 44.2344px;">Image</th>
<th class="text-center sorting" tabindex="0" aria-controls="style-3"
rowspan="1" colspan="1" style="width: 79.7969px;">First Name</th>
<th class="text-center sorting" tabindex="0" aria-controls="style-3"
rowspan="1" colspan="1" style="width: 77.3281px;">Last Name</th>
<th class="text-center sorting" tabindex="0" aria-controls="style-3"
rowspan="1" colspan="1" style="width: 143.516px;">Email</th>
<th class="text-center sorting" tabindex="0" aria-controls="style-3"
rowspan="1" colspan="1" style="width: 98.875px;">#</th>
<th class="text-center sorting" tabindex="0" aria-controls="style-3"
rowspan="1" colspan="1" style="width: 98.875px;">Principal Type</th>
<!-- <th class="text-center sorting" tabindex="0" aria-controls="style-3" rowspan="1" colspan="1"
style="width: 98.875px;">Phone Verified</th> -->
<th class="text-center sorting" tabindex="0" aria-controls="style-3" rowspan="1" colspan="1"
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"
style="width: 98.875px;">Modified On</th>
<th class="text-center sorting" tabindex="0" aria-controls="style-3" rowspan="1" colspan="1"
style="width: 98.875px;">Active</th>
<th class="text-center sorting" tabindex="0" aria-controls="style-3"
rowspan="1" colspan="1" 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" style="width: 98.875px;">Modified On</th>
<th class="text-center sorting" tabindex="0" aria-controls="style-3"
rowspan="1" colspan="1" style="width: 98.875px;">Active</th>
<!-- <th class="text-center sorting" tabindex="0" aria-controls="style-3" rowspan="1" colspan="1"
style="width: 98.875px;">Delete</th> -->
<th class="text-center dt-no-sorting" tabindex="0"
aria-controls="style-3" rowspan="1" colspan="1"
style="width: 51.625px;">Action</th>
<th class="text-center dt-no-sorting" tabindex="0" aria-controls="style-3"
rowspan="1" colspan="1" style="width: 51.625px;">Action</th>
</tr>
</thead>
<tbody>
{% for data_obj in data_objs %}
<tr role="row">
<td class="checkbox-column text-center sorting_1"> {{ data_obj.id }} </td>
<td class="checkbox-column text-center sorting_1"> {{ data_obj.id }}</td>
<td class="text-center">
<span><img src="../src/assets/img/profile-17.jpeg" class="profile-img"
alt="avatar"></span>
<span>
<img
src="{% if data_obj.profile_photo %}{{ data_obj.profile_photo.url }}{% else %}{% endif %}"
class="profile-img"
>
</span>
</td>
<td>{{ data_obj.first_name }}</td>
<td>{{ data_obj.last_name }}</td>
<td>{{ data_obj.email }}</td>
<!-- <td>{{ data_obj.phone_no }}</td> -->
<td>
{% if data_obj.extended_data and data_obj.extended_data.is_onboarded and not data_obj.extended_data.is_transferred %}
<button class="btn btn-primary btn-sm view-password-btn"
data-id="{{ data_obj.id }}" data-email="{{ data_obj.email }}">
View
</button>
{% endif %}
</td>
<td>{{ data_obj.principal_type.name }}</td>
<!-- <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 and data_obj.extended_data.is_onboarded %}badge-primary{% else %}badge-danger{% endif %}">
<span
class="shadow-none badge {% if data_obj.extended_data and data_obj.extended_data.is_onboarded %}badge-primary{% else %}badge-danger{% endif %}">
{% if data_obj.extended_data %}
{{ data_obj.extended_data.is_onboarded }}
{{ data_obj.extended_data.is_onboarded }}
{% else %}
False
False
{% endif %}
</span>
</td>
<td class="text-center">
<span class="shadow-none badge {% if data_obj.extended_data and data_obj.extended_data.is_transferred %}badge-primary{% else %}badge-danger{% endif %}">
<span
class="shadow-none badge {% if data_obj.extended_data and data_obj.extended_data.is_transferred %}badge-primary{% else %}badge-danger{% endif %}">
{% if data_obj.extended_data %}
{{ data_obj.extended_data.is_transferred }}
{{ data_obj.extended_data.is_transferred }}
{% else %}
False
False
{% endif %}
</span>
</td>
<td>{{ data_obj.created_on }}</td>
<td>{{ data_obj.modified_on }}</td>
<td class="text-center">
<span class="shadow-none badge {% if data_obj.is_active %}badge-primary{% else %}badge-danger{% endif %}">
<span
class="shadow-none badge {% if data_obj.is_active %}badge-primary{% else %}badge-danger{% endif %}">
{{ data_obj.is_active }}
</span>
</td>
@@ -129,11 +145,12 @@
</td> -->
<td class="text-center">
<ul class="table-controls">
<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"
width="24" height="24" viewBox="0 0 24 24" fill="none"
<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" 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-edit-2 p-1 br-8 mb-1">
@@ -141,9 +158,20 @@
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="{% 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>
<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>
@@ -159,31 +187,89 @@
</div>
</div>
<div class="modal fade" id="passwordModal" tabindex="-1" role="dialog" aria-labelledby="passwordModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="passwordModalLabel">Customer Details</h5>
</div>
<div class="modal-body">
<p><strong>Email:</strong> <span id="modalEmail"></span></p>
<p><strong>Password:</strong> <span id="modalPassword"></span></p>
</div>
</div>
</div>
</div>
{% endblock content %}
{% block javascript %}
<!-- include required js cdn link through html here -->
{% include "cdn_through_html/datatable_cdn_js.html" %}
<script>
c3 = $('#style-3').DataTable({
"dom": "<'dt--top-section'<'row'<'col-12 col-sm-6 d-flex justify-content-sm-start justify-content-center'l><'col-12 col-sm-6 d-flex justify-content-sm-end justify-content-center mt-sm-0 mt-3'f>>>" +
"<'table-responsive'tr>" +
"<'dt--bottom-section d-sm-flex justify-content-sm-between text-center'<'dt--pages-count mb-sm-0 mb-3'i><'dt--pagination'p>>",
"oLanguage": {
"oPaginate": { "sPrevious": '<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-arrow-left"><line x1="19" y1="12" x2="5" y2="12"></line><polyline points="12 19 5 12 12 5"></polyline></svg>', "sNext": '<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-arrow-right"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg>' },
"sInfo": "Showing page _PAGE_ of _PAGES_",
"sSearch": '<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-search"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>',
"sSearchPlaceholder": "Search...",
"sLengthMenu": "Results : _MENU_",
},
"order": [[ 0, "desc" ]],
"stripeClasses": [],
"lengthMenu": [5, 10, 20, 50],
"pageLength": 10
<!-- include required js cdn link through html here -->
{% include "cdn_through_html/datatable_cdn_js.html" %}
{% include "cdn_through_html/sweetalert2_cdn_js.html" %}
<script>
c3 = $('#style-3').DataTable({
"dom": "<'dt--top-section'<'row'<'col-12 col-sm-6 d-flex justify-content-sm-start justify-content-center'l><'col-12 col-sm-6 d-flex justify-content-sm-end justify-content-center mt-sm-0 mt-3'f>>>" +
"<'table-responsive'tr>" +
"<'dt--bottom-section d-sm-flex justify-content-sm-between text-center'<'dt--pages-count mb-sm-0 mb-3'i><'dt--pagination'p>>",
"oLanguage": {
"oPaginate": { "sPrevious": '<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-arrow-left"><line x1="19" y1="12" x2="5" y2="12"></line><polyline points="12 19 5 12 12 5"></polyline></svg>', "sNext": '<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-arrow-right"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg>' },
"sInfo": "Showing page _PAGE_ of _PAGES_",
"sSearch": '<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-search"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>',
"sSearchPlaceholder": "Search...",
"sLengthMenu": "Results : _MENU_",
},
"order": [[0, "desc"]],
"stripeClasses": [],
"lengthMenu": [5, 10, 20, 50],
"pageLength": 10
});
multiCheck(c3);
var viewUrl = "{% url 'accounts:get_decrypted_password' customer_id=0 %}"
$("#style-3").on("click", ".view-password-btn", function () {
console.log("passowrd calling")
const customerId = $(this).data("id");
const email = $(this).data("email");
// Make the AJAX call
$.ajax({
// url: `accounts//get-decrypted-password/${customerId}/`,
url: viewUrl.replace('0',customerId),
type: "GET",
dataType: "json",
success: function (response) {
if (response.success) {
// Populate modal with email and decrypted password
$("#modalEmail").text(email);
$("#modalPassword").text(response.decrypted_password);
// Show the modal
$("#passwordModal").modal("show");
} else {
Swal.fire({
title: 'Error!',
text: response.message || "Failed to fetch the password. Please try again.",
icon: 'error',
showConfirmButton: true
});
}
},
error: function (xhr, status, error) {
console.error("AJAX Error:", error);
Swal.fire({
title: 'Error!',
text: "An error occurred. Please try again.",
icon: 'error',
showConfirmButton: true
});
},
});
});
multiCheck(c3);
</script>
</script>
{% endblock %}

View File

@@ -57,22 +57,19 @@
{% include "cdn_through_html/sweetalert2_cdn_js.html" %}
{% include "cdn_through_html/jquery_validate_cdn_js.html" %}
<script>
// Function to initialize FilePond
function initializeFilePond(selector, allowMultiple = false) {
FilePond.registerPlugin(
FilePondPluginImagePreview,
FilePondPluginImageExifOrientation,
FilePondPluginFileValidateSize
FilePondPluginFileValidateSize,
);
return FilePond.create(document.getElementById(selector),{
return FilePond.create(document.getElementById(selector), {
allowMultiple: allowMultiple,
acceptedFileTypes: ['image/*'],
storeAsFile: true,
dropOnPage: true
dropOnPage: true,
});
}
@@ -195,6 +192,7 @@
// Initialize Tagify
initializeTagify('#id_tags');
initializeTagify('#id_key_guest');
// Handle principal change
handlePrincipalChange();

View File

@@ -45,6 +45,8 @@
aria-sort="ascending" style="width: 69.2656px;"> Title </th>
<th class="checkbox-column sorting_asc" tabindex="0" aria-controls="style-3"
aria-sort="ascending" style="width: 69.2656px;"> Address </th>
<th class="checkbox-column sorting_asc" tabindex="0" aria-controls="style-3"
aria-sort="ascending" style="width: 69.2656px;"> Postcode </th>
<th class="checkbox-column sorting_asc" tabindex="0" aria-controls="style-3"
aria-sort="ascending" style="width: 69.2656px;"> Latitude </th>
<th class="checkbox-column sorting_asc" tabindex="0" aria-controls="style-3"
@@ -68,6 +70,7 @@
</td>
<td>{{data_obj.title}}</td>
<td>{{data_obj.address}}</td>
<td>{{data_obj.postcode}}</td>
<td>{{data_obj.latitude}}</td>
<td>{{data_obj.longitude}}</td>
<td class="text-center">