Merge pull request #58 from WDI-Ideas/development

Development
This commit is contained in:
BOBBY VISHWAKARMA
2024-07-02 15:39:59 +05:30
committed by GitHub
15 changed files with 623 additions and 292 deletions

View File

@@ -1000,6 +1000,16 @@ class AccountTransferCheckView(APIView):
def post(self, request, *args, **kwargs):
try:
obj = IAmPrincipalExtendedData.objects.get(principal=request.user)
except IAmPrincipalExtendedData.DoesNotExist:
# Create a dummy serializer record
obj = {
'principal': request.user.id,
'is_onboarded': False,
'is_transferred': False,
'transferred_on': None,
'pwd_changed_post_transfer': False
}
return ApiResponse.success(message=constants.SUCCESS, data=obj)
except Exception as e:
error_response = {
"status": status.HTTP_404_NOT_FOUND,

View File

@@ -51,7 +51,6 @@ urlpatterns = [
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
path('principal/example/', TemplateView.as_view(template_name="accounts/iam_module/example_form.html"), name="example_add"),
path('datatable/', views.DatatableListView.as_view(), name="serverside_list"),

View File

@@ -754,7 +754,7 @@ class CustomerDetailView(LoginRequiredMixin, generic.DetailView):
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")
principal_subscription = PrincipalSubscription.objects.filter(principal=principal_obj).order_by("-start_date").first()
return render(request, self.template_name, locals())
class CustomerListView(LoginRequiredMixin, generic.ListView):
@@ -996,11 +996,11 @@ class CustomerImportView(LoginRequiredMixin, generic.View):
# collect the principals
principal = IAmPrincipal(
first_name=first_name,
last_name=last_name,
email=email,
first_name=first_name.strip().capitalize(),
last_name=last_name.strip().capitalize(),
email=email.strip(),
password=make_password("goodtimes#2024"),
username=email,
username=email.strip(),
email_verified=True,
register_complete=True,
principal_type=principal_type
@@ -1018,7 +1018,8 @@ class CustomerImportView(LoginRequiredMixin, generic.View):
# Use transaction.atomic to ensure all-or-nothing
with transaction.atomic():
# Bulk create principals
IAmPrincipal.objects.bulk_create(principals)
for principal in principals:
principal.save()
# 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])
@@ -1069,16 +1070,16 @@ class CustomerExportView(LoginRequiredMixin, generic.View):
principal.email,
principal.first_name,
principal.last_name,
str(principal.phone_no),
str(principal.phone_no) if principal.phone_no else "N/A",
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'
# 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"]
columns = ["Email", "First Name", "Last Name", "Phone Number", "Email Verified", "Active"]
# Create a workbook and select the active worksheet
wb = Workbook()

View File

@@ -38,6 +38,11 @@ class VenueSerializer(serializers.ModelSerializer):
fields = "__all__"
read_only_fields = ("created_by",)
class VenueShortSerializer(serializers.ModelSerializer):
class Meta:
model = Venue
fields= ["id","title"]
class EventCategorySerializer(serializers.ModelSerializer):
class Meta:

View File

@@ -1,6 +1,6 @@
from django import forms
from accounts.models import IAmPrincipal, IAmPrincipalExtendedData
from manage_events.models import EventMaster, Event, EventCategory, Venue
from manage_events.models import EventImage, EventMaster, Event, EventCategory, Venue
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
@@ -17,6 +17,12 @@ class EventCategoryForm(forms.ModelForm):
class EventForm(forms.ModelForm):
AGE_GROUP_CHOICES = [
('18 & Under', '18 & Under'),
('18-30', '18-30'),
('30+', '30+'),
('Family Event', 'Family Event'),
]
principal = forms.ModelChoiceField(
queryset=IAmPrincipal.objects.select_related("extended_data").filter(
extended_data__is_onboarded=True,
@@ -26,27 +32,30 @@ class EventForm(forms.ModelForm):
required=True
)
venue = forms.ModelChoiceField(
queryset=Venue.objects.filter(
active=True
),
queryset=Venue.objects.none(),
label="venue",
required=True
)
image = forms.ImageField(label="Thumbnail")
event_images = forms.ImageField(label="Event Images")
age_group = forms.ChoiceField(choices=AGE_GROUP_CHOICES, label="Age Group", required=True)
class Meta:
model = Event
fields = [
"principal",
"venue",
"title",
# "event_master",
"description",
"image",
"status",
"event_images",
# "status",
"start_date",
"end_date",
"from_time",
"to_time",
"category",
"venue",
"venue_capacity",
# "video_url",
"entry_type",
@@ -61,57 +70,61 @@ class EventForm(forms.ModelForm):
"deleted",
]
widgets = {
"title": forms.TextInput(attrs={"class": "form-control"}),
"description": forms.Textarea(attrs={"class": "form-control", "rows": 4}),
"status": forms.Select(attrs={"class": "form-control"}),
"description": forms.Textarea(attrs={"rows": 4}),
"start_date": forms.DateInput(
attrs={"class": "form-control", "type": "date"}
attrs={"type": "date"}
),
"end_date": forms.DateInput(
attrs={"class": "form-control", "type": "date"}
attrs={"type": "date"}
),
"from_time": forms.TimeInput(
attrs={"class": "form-control", "type": "time"}
attrs={"type": "time"}
),
"to_time": forms.TimeInput(attrs={"class": "form-control", "type": "time"}),
"venue_capacity": forms.NumberInput(attrs={"class": "form-control"}),
"video_url": forms.URLInput(attrs={"class": "form-control"}),
"entry_type": forms.Select(attrs={"class": "form-control"}),
"entry_fee": forms.NumberInput(attrs={"class": "form-control"}),
"key_guest": forms.Textarea(attrs={"class": "form-control", "rows": 3}),
"age_group": forms.TextInput(attrs={"class": "form-control"}),
"draft": forms.CheckboxInput(attrs={"class": "form-check-input"}),
# For the 'image' field, you might not need to specify a widget since the default is appropriate.
# However, if you want to add specific classes or attributes, you can do it like this:
"image": forms.FileInput(attrs={"class": "form-control-file"}),
# For ForeignKey fields like 'EventMaster' and 'venue', Django uses a select widget by default.
# You can customize it further if needed:
# "event_master": forms.Select(attrs={"class": "form-control"}),
"venue": forms.Select(attrs={"class": "form-control"}),
"category": forms.Select(attrs={"class": "form-control"}),
"to_time": forms.TimeInput(attrs={"type": "time"}),
"key_guest": forms.Textarea(attrs={"rows": 3}),
"coupon_description": forms.Textarea(attrs={"rows": 3}),
}
def __init__(self, *args, **kwargs):
instance = kwargs.get('instance')
principal_id = kwargs.pop('principal_id', None)
super().__init__(*args, **kwargs)
if instance:
event_images = EventImage.objects.filter(event=instance)
if event_images.exists():
self.fields['event_images'].initial = [image.image.url for image in event_images]
if principal_id:
self.fields['venue'].queryset = Venue.objects.filter(principal_id=principal_id, active=True)
else:
self.fields['venue'].queryset = Venue.objects.none()
def clean(self):
cleaned_data = super().clean()
# Get the start and end dates from cleaned_data
start_date = cleaned_data.get("start_date")
end_date = cleaned_data.get("end_date")
# Validation 1: end_date should not be less than start_date
if end_date and start_date and end_date < start_date:
self.add_error("end_date", _("End date cannot be before the start date."))
# Get the from and to times from cleaned_data
from_time = cleaned_data.get("from_time")
to_time = cleaned_data.get("to_time")
# Validation 2: to_time should not be less than or equal to from_time
# Validation 1: end_date should not be less than start_date
if start_date and end_date and end_date < start_date:
self.add_error("end_date", _("End date cannot be before the start date."))
if end_date == start_date:
if to_time and from_time and to_time <= from_time:
self.add_error("to_time", _("End time must be after the start time."))
return cleaned_data
class EventImageForm(forms.ModelForm):
class Meta:
model = EventImage
fields = ['image']
class EventMasterForm(forms.ModelForm):
class Meta:
@@ -134,19 +147,14 @@ class VenueForm(forms.ModelForm):
fields = [
"principal",
"title",
"description",
# "description",
"address",
"image",
"url",
# "url",
"latitude",
"longitude",
]
widgets = {
"title": forms.TextInput(attrs={"class": "form-control"}),
"description": forms.Textarea(attrs={"class": "form-control", "rows": 4}),
"address": forms.Textarea(attrs={"class": "form-control", "rows": 3}),
"image": forms.FileInput(attrs={"class": "form-control"}),
"url": forms.URLInput(attrs={"class": "form-control"}),
"latitude": forms.NumberInput(attrs={"class": "form-control"}),
"longitude": forms.NumberInput(attrs={"class": "form-control"}),
"description": forms.Textarea(attrs={"rows": 4}),
"address": forms.Textarea(attrs={"rows": 3}),
}

View File

@@ -89,6 +89,11 @@ urlpatterns = [
views.VenueDeleteView.as_view(),
name="venue_delete",
),
path(
"venue/customer/",
views.CustomerVenueFilterView.as_view(),
name="venue_customer_filter",
),
path(
"generate-event-report/<int:user_id>/",
views.GenerateEventReportView.as_view(),

View File

@@ -1,5 +1,7 @@
from django.shortcuts import get_object_or_404, redirect, render
from accounts import resource_action
from goodtimes.utils import JsonResponseUtil
from manage_events.api.serializers import VenueSerializer, VenueShortSerializer
from manage_events.forms import (
EventMasterForm,
EventCategoryForm,
@@ -7,7 +9,7 @@ from manage_events.forms import (
VenueForm,
)
from django.core.paginator import Paginator
from .models import EventMaster, Event, EventCategory, EventPrincipalInteraction, Venue
from .models import EventImage, EventMaster, Event, EventCategory, EventPrincipalInteraction, Venue
from django.views import generic
from django.contrib.auth.mixins import LoginRequiredMixin
from django.urls import reverse_lazy
@@ -222,7 +224,7 @@ class EventMasterDeleteView(LoginRequiredMixin, generic.View):
return redirect(self.success_url)
from django.core.files.storage import default_storage
class EventCreateOrUpdateView(LoginRequiredMixin, generic.View):
# Set the page_name and resource
page_name = resource_action.RESOURCE_MANAGE_EVENTS
@@ -258,6 +260,9 @@ class EventCreateOrUpdateView(LoginRequiredMixin, generic.View):
context.update(kwargs) # Include any additional context data passed to the view
return context
def get_event_images(self):
return [image.image.url for image in EventImage.objects.filter(event=self.object)]
def get(self, request, *args, **kwargs):
self.object = self.get_object()
@@ -266,25 +271,46 @@ class EventCreateOrUpdateView(LoginRequiredMixin, generic.View):
self.action = resource_action.ACTION_UPDATE
form = self.form_class(instance=self.object)
context = self.get_context_data(form=form)
context = self.get_context_data(form=form, event_images_urls=self.get_event_images())
return render(request, self.template_name, context=context)
def post(self, request, *args, **kwargs):
print(request.POST)
print(request.FILES)
self.object = self.get_object()
# If an object is found, change action to ACTION_UPDATE
if self.object is not None:
self.action = resource_action.ACTION_UPDATE
form = self.form_class(request.POST, request.FILES, instance=self.object)
principal_id = request.POST.get('principal')
form = self.form_class(request.POST, request.FILES, instance=self.object, principal_id=principal_id)
if not form.is_valid():
print(form.errors)
context = self.get_context_data(form=form)
print(f"form error is {form.errors}")
context = self.get_context_data(form=form, event_images_urls=self.get_event_images())
return render(request, self.template_name, context=context)
instance = form.save()
instance.created_by = form.cleaned_data.get("principal")
instance.save()
# Delete old images from storage
old_images = EventImage.objects.filter(event=instance)
for old_image in old_images:
if default_storage.exists(old_image.image.name):
default_storage.delete(old_image.image.name)
# Delete old images from database
old_images.delete()
event_images = request.FILES.getlist("event_images")
event_image_objects = [EventImage(event=instance, image=image) for image in event_images]
EventImage.objects.bulk_create(event_image_objects)
messages.success(self.request, self.get_success_message())
return redirect(self.success_url)
@@ -487,6 +513,23 @@ class VenueDeleteView(LoginRequiredMixin, generic.View):
return redirect(self.success_url)
class CustomerVenueFilterView(LoginRequiredMixin, generic.View):
model = Venue
serializer_class = VenueShortSerializer
def get(self, request, *args, **kwargs):
pk = request.GET.get("pk", None)
if not pk:
return JsonResponseUtil.error(message="Non transfer user list field is required")
obj = self.model.objects.filter(principal=pk)
if not obj.exists():
return JsonResponseUtil.error(message="No venue found for the given user.")
serializer = self.serializer_class(obj, many=True)
return JsonResponseUtil.success(message=constants.SUCCESS, data=serializer.data)
User = get_user_model()
from .report import generate_event_report, generate_event_report_pdf_three
from django.http import HttpResponse

View File

@@ -57,7 +57,7 @@
// });
var start_date = flatpickr(document.getElementById('id_free_start_date'), {
minDate: "today",
// minDate: "today",
onChange: function(selectedDates, dateStr, instance) {
end_date.set('minDate', selectedDates[0]);
}
@@ -113,30 +113,30 @@
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)
}
},
// 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
minlength: 1
},
free_start_date: {
required: true,

View File

@@ -31,27 +31,16 @@
<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">
@@ -63,26 +52,18 @@
</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 class="col-md-9">{% if principal_subscription %}{{ principal_subscription.start_date }}{% else %}No subscription found{% endif %}</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 class="col-md-9">{% if principal_subscription %}{{ principal_subscription.end_date }}{% else %}No subscription found{% endif %}</div>
</div>
{% if not principal_obj.extended_data.is_transferred %}
{% if principal_obj.extended_data and not principal_obj.extended_data.transferred and principal_obj.extended_data.onboarded and principal_obj.principal_type.name == 'event_manager' %}
<div class="col text-end">
<a class="btn btn-primary mb-3" href="{% url 'accounts:customer_transfer' principal_obj.id%}">
<a class="btn btn-dark mb-2 me-4" href="{% url 'accounts:customer_transfer' principal_obj.id %}">
Transfer Account
</a>
</div>
@@ -92,6 +73,7 @@
</div>
</div>
</div>
</div>
</div>
{% endblock content %}

View File

@@ -19,13 +19,14 @@
</a>
</div>
{% if not principal_obj.extended_data.is_transferred %}
{% if principal_obj.extended_data and not principal_obj.extended_data.transferred and principal_obj.extended_data.onboarded and principal_obj.principal_type.name == 'event_manager' %}
<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">
@@ -65,7 +66,7 @@
// });
var start_date = flatpickr(document.getElementById('id_free_start_date'), {
minDate: "today",
// minDate: "today",
onChange: function(selectedDates, dateStr, instance) {
end_date.set('minDate', selectedDates[0]);
}
@@ -121,30 +122,30 @@
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)
}
},
// 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
minlength: 1
},
free_start_date: {
required: true,
@@ -186,16 +187,16 @@
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;
}
},
// 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');

View File

@@ -98,13 +98,21 @@
<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 %}">
<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 }}
{% else %}
False
{% endif %}
</span>
</td>
<td class="text-center">
<span class="shadow-none badge {% if 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 }}
{% else %}
False
{% endif %}
</span>
</td>
<td>{{ data_obj.created_on }}</td>

View File

@@ -0,0 +1,3 @@
{% load static%}
<link rel="stylesheet" type="text/css" href="{% static 'src/plugins/src/sweetalerts2/sweetalerts2.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'src/plugins/css/light/sweetalerts2/custom-sweetalert.css' %}">

View File

@@ -0,0 +1,3 @@
{% load static%}
<script src="{% static 'src/plugins/src/sweetalerts2/sweetalerts2.min.js' %}"></script>
<script src="{% static 'src/plugins/src/sweetalerts2/custom-sweetalert.js' %}"></script>

View File

@@ -4,8 +4,10 @@
<!-- include required css cdn link through html here -->
{% include "cdn_through_html/filepond_cdn_css.html" %}
{% include "cdn_through_html/flatpicker_cdn_css.html" %}
{% include "cdn_through_html/quill_cdn_css.html" %}
{% include "cdn_through_html/tagify_cdn_css.html" %}
{% include "cdn_through_html/sweetalert2_cdn_css.html" %}
{{form.media}}
{% endblock %}
@@ -28,32 +30,13 @@
<div class="statbox widget box box-shadow">
<div class="widget-content widget-content-area">
<form method="POST" enctype="multipart/form-data" novalidate>
<form method="POST" enctype="multipart/form-data" id="eventForm">
{% 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">Submit</button></div>
</div>
{% comment %} <div class="mb-3">
<label for="title" class="form-label">Title</label>
<input type="text" class="form-control" id="title" aria-describedby="title">
<div id="emailHelp" class="form-text" style="color: grey;">We'll never share your email with anyone else.</div>
</div>
<div class="mb-3">
<label class="form-label">Description</label>
<div id="description"></div>
</div>
<div class="mb-3">
<label for="product-images">Image</label>
<div class="multiple-file-upload">
<div class="filepond--root filepond file-upload-multiple filepond--hopper" id="images" data-style-button-remove-item-position="left" data-style-button-process-item-position="right" data-style-load-indicator-position="right" data-style-progress-indicator-position="right" data-style-button-remove-item-align="false" style="height: 57px;"><input class="filepond--browser" type="file" id="filepond--browser-feeq8o6dj" name="filepond" aria-controls="filepond--assistant-feeq8o6dj" aria-labelledby="filepond--drop-label-feeq8o6dj" multiple=""><a class="filepond--credits" aria-hidden="true" href="https://pqina.nl/" target="_blank" rel="noopener noreferrer" style="transform: translateY(49px);">Powered by PQINA</a><div class="filepond--drop-label" style="transform: translate3d(0px, 0px, 0px); opacity: 1;"><label for="filepond--browser-feeq8o6dj" id="filepond--drop-label-feeq8o6dj" aria-hidden="true">Drag &amp; Drop your files or <span class="filepond--label-action" tabindex="0">Browse</span></label></div><div class="filepond--list-scroller" style="transform: translate3d(0px, 41px, 0px);"><ul class="filepond--list" role="list"></ul></div><div class="filepond--panel filepond--panel-root" data-scalable="true"><div class="filepond--panel-top filepond--panel-root"></div><div class="filepond--panel-center filepond--panel-root" style="transform: translate3d(0px, 8px, 0px) scale3d(1, 0.41, 1);"></div><div class="filepond--panel-bottom filepond--panel-root" style="transform: translate3d(0px, 49px, 0px);"></div></div><span class="filepond--assistant" id="filepond--assistant-feeq8o6dj" role="status" aria-live="polite" aria-relevant="additions"></span><div class="filepond--drip"></div><fieldset class="filepond--data"></fieldset></div>
</div>
</div>
<div class="mb-3">
<label for="tags">Tags</label>
<input id="tags" class="tags" value="">
</div>
<button type="submit" class="btn btn-primary">Submit</button> {% endcomment %}
</form>
</div>
@@ -70,17 +53,16 @@
<!-- include required css cdn link through html here -->
{% include "cdn_through_html/filepond_cdn_js.html" %}
{% include "cdn_through_html/quill_cdn_js.html" %}
{% include "cdn_through_html/flatpicker_cdn_js.html" %}
{% include "cdn_through_html/tagify_cdn_js.html" %}
{% include "cdn_through_html/sweetalert2_cdn_js.html" %}
{% include "cdn_through_html/jquery_validate_cdn_js.html" %}
<script>
/**
* ===================================
* Blog Description Editor
* ===================================
*/
var quill = new Quill('#description', {
// Function to initialize Quill editor
function initializeQuillEditor(selector, placeholder) {
return new Quill(selector, {
modules: {
toolbar: [
[{ header: [1, 2, false] }],
@@ -88,46 +70,288 @@
['image', 'code-block']
]
},
placeholder: 'Write description...',
placeholder: placeholder,
theme: 'snow' // or 'bubble'
});
}
/**
* ====================
* File Pond
* ====================
*/
// We want to preview images, so we register
// the Image Preview plugin, We also register
// exif orientation (to correct mobile image
// orientation) and size validation, to prevent
// large files from being added
// Function to initialize FilePond
function initializeFilePond(selector, allowMultiple = false) {
FilePond.registerPlugin(
FilePondPluginImagePreview,
FilePondPluginImageExifOrientation,
FilePondPluginFileValidateSize,
// FilePondPluginImageEdit
FilePondPluginFileValidateSize
);
return FilePond.create(document.getElementById(selector),{
allowMultiple: allowMultiple,
storeAsFile: true,
dropOnPage: true
});
}
// Select the file input and use
// create() to turn it into a pond
var ecommerce = FilePond.create(document.querySelector('.file-upload-multiple'));
// Function to initialize Flatpickr
function initializeFlatpickr(selector, config) {
return flatpickr(document.getElementById(selector), config);
}
/**
* =====================
* Blog Tags
* =====================
*/
// The DOM element you wish to replace with Tagify
var input = document.querySelector('#id_tags');
// initialize Tagify on the above input node reference
new Tagify(input,{
// Function to initialize Tagify
function initializeTagify(selector) {
var input = document.querySelector(selector);
return new Tagify(input, {
originalInputValueFormat: valuesArr => valuesArr.map(item => item.value).join(', ')
})
});
}
// Function to handle principal change event and fetch venues
function handlePrincipalChange() {
$("#id_principal").change(function(){
var selectedPrincipalId = $(this).val();
// clear existing venue options
$("#id_venue").empty().append($("<option value='' selected=''>---------</option>"));
if (selectedPrincipalId){
$.ajax({
url: "{% url 'manage_events:venue_customer_filter'%}",
type: "GET",
data: { pk: selectedPrincipalId },
success: function(data) {
// Process and populate venue options
$.each(data.data, function(index, venue) {
$("#id_venue").append($("<option></option>").val(venue.id).text(venue.title));
});
},
error: function(jqXHR, textStatus, errorThrown) {
console.error("Error fetching venue data:", textStatus, errorThrown);
console.error("Error fetching venue data:", jqXHR.responseJSON.message);
// Display error alert using SweetAlert2
Swal.fire({
icon: 'error',
text: jqXHR.responseJSON.message,
position: 'center',
});
}
});
}
});
}
$(document).ready(function() {
// Set multiple attribute for event images
document.getElementById('id_event_images').setAttribute('multiple', '');
// Initialize Quill editor
var quill = initializeQuillEditor('#description', 'Write description...');
// Initialize FilePond
var thumbnail = initializeFilePond('id_image');
var thumbnailUrl = "{% if form.image.value %}{{ form.image.value.url }}{% endif %}";
if (thumbnailUrl){
thumbnail.addFile(thumbnailUrl)
}
var eventImages = initializeFilePond('id_event_images', true);
// var eventImagesUrl = "{% if form.event_images.value %}{{ form.event_images.value.url}}{%endif %}";
var eventImagesUrls = {{ event_images_urls|safe }};
if (Array.isArray(eventImagesUrls) && eventImagesUrls.length) {
eventImagesUrls.forEach(function(url) {
eventImages.addFile(url);
});
}
// Initialize Flatpickr
var startDate = initializeFlatpickr('id_start_date', {
minDate: "today",
onChange: function(selectedDates, dateStr, instance) {
endDate.set('minDate', selectedDates[0]);
}
});
var endDate = initializeFlatpickr('id_end_date', { minDate: null });
var fromTimeField = document.getElementById('id_from_time');
var fromTimeValue = fromTimeField.value; // Get the current value of the form field
initializeFlatpickr('id_from_time', {
enableTime: true,
noCalendar: true,
dateFormat: "H:i",
defaultDate: fromTimeValue ? fromTimeValue : new Date(), // Use form field value if available, otherwise use current time
allowInput: true,
});
var fromTimeField = document.getElementById('id_to_time');
var fromTimeValue = fromTimeField.value; // Get the current value of the form field
initializeFlatpickr('id_to_time', {
enableTime: true,
noCalendar: true,
dateFormat: "H:i",
defaultDate: fromTimeValue ? fromTimeValue : new Date(),
allowInput: true,
});
// Initialize Tagify
initializeTagify('#id_tags');
// Handle principal change
handlePrincipalChange();
// Initialize jQuery Validate
$("#eventForm").validate({
rules: {
principal: {
required: true
},
venue: {
required: true
},
title: {
required: true
},
description: {
required: true
},
image: {
required: true,
accept: "image/*"
},
event_images: {
required: true,
accept: "image/*"
},
start_date: {
required: true,
date: true
},
end_date: {
required: true,
date: true
},
from_time: {
required: true
},
to_time: {
required: true
},
category:{
required: true
},
venue_capacity:{
required: true
},
entry_type: {
required: true
},
age_group: {
required: true
},
tags: {
required: true
}
// Add other fields with similar structure
},
messages: {
principal: {
required: "Please select a principal"
},
venue: {
required: "Please select a venue"
},
title: {
required: "Please enter a title",
},
description: {
required: "Please enter a description",
},
image: {
required: "Please upload a thumbnail",
accept: "Please upload a valid image file"
},
event_images: {
required: "Please upload event images",
accept: "Please upload valid image files"
},
start_date: {
required: "Please select a start date",
date: "Please enter a valid date"
},
end_date: {
required: "Please select an end date",
date: "Please enter a valid date",
greaterThanStartDate: "The end date must be after or equal to the start date."
},
from_time: {
required: "Please select a start time"
},
to_time: {
required: "Please select an end time",
greaterThanFromTime: "End time must be greater than start time on the same day"
},
category:{
required: "Please select a category"
},
venue_capacity:{
required: "Please enter a venue capacity"
},
entry_type: {
required: "Please select a event type"
},
age_group: {
required: "Please select a age group"
},
tags: {
required: "Please enter tags"
}
},
customMethods: {
greaterThanStartDate: function(element) {
var startDate = $("#id_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;
},
greaterThanFromTime: function(element){
var startDateVal = $("#id_start_date").val();
var endDateVal = $("#id_end_date").val();
var fromTime = $("#id_from_time").val();
var toTime = $(element).val();
if (!toTime || !fromTime) {
return true; // Allow if either time is not selected (prevents errors)
}
if (startDateVal !== endDateVal) {
return true
}
return toTime > fromTime;
}
},
errorElement: "div",
errorPlacement: function(error, element) {
error.addClass("invalid-feedback");
if (element.prop("type") === "checkbox") {
error.insertAfter(element.next("label"));
} else {
error.insertAfter(element);
}
},
highlight: function(element) {
$(element).addClass("is-invalid").removeClass("is-valid");
},
unhighlight: function(element) {
$(element).addClass("is-valid").removeClass("is-invalid");
},
submitHandler: function(form) {
// Disable the submit button to prevent multiple submissions
$('button[type="submit"]').prop('disabled', true);
event.prevent()
//form.submit();
}
});
});
</script>
{% endblock %}

View File

@@ -4,8 +4,6 @@
<!-- include required css cdn link through html here -->
{% include "cdn_through_html/filepond_cdn_css.html" %}
{% include "cdn_through_html/quill_cdn_css.html" %}
{% include "cdn_through_html/tagify_cdn_css.html" %}
{{form.media}}
{% endblock %}
@@ -28,7 +26,7 @@
<div class="statbox widget box box-shadow">
<div class="widget-content widget-content-area">
<form method="POST" enctype="multipart/form-data" novalidate>
<form method="POST" enctype="multipart/form-data" id="venueForm">
{% csrf_token %}
{% include 'includes/dynamic_template_form.html' with form=form %}
<div class="mt-4 mb-0">
@@ -56,64 +54,105 @@
<!-- include required css cdn link through html here -->
{% include "cdn_through_html/filepond_cdn_js.html" %}
{% include "cdn_through_html/quill_cdn_js.html" %}
{% include "cdn_through_html/tagify_cdn_js.html" %}
{% include "cdn_through_html/jquery_validate_cdn_js.html" %}
<script>
/**
* ===================================
* Blog Description Editor
* ===================================
*/
var quill = new Quill('#description', {
modules: {
toolbar: [
[{ header: [1, 2, false] }],
['bold', 'italic', 'underline'],
['image', 'code-block']
]
$(document).ready(function(){
// initialize filepond
var image = initializeFilePond('id_image');
var imageUrl = "{% if form.image.value %}{{ form.image.value.url }}{% endif %}";
if (imageUrl){
image.addFile(imageUrl)
}
// Validate the form
$("#venueForm").validate({
rules: {
principal: {
required: true
},
placeholder: 'Write description...',
theme: 'snow' // or 'bubble'
title: {
required: true,
noSpace: true
},
address: {
required: true
},
image: {
required: true,
accept: "image/*"
},
latitude: {
required: true,
number: true,
},
longitude: {
required: true,
number: true,
}
},
messages: {
principal: {
required: "Please select a principal"
},
title: {
required: "Please enter a title"
},
address: {
required: "Please enter an address"
},
image: {
required: "Please upload an image",
accept: "Please upload a valid image file"
},
latitude: {
required: "Please enter a latitude",
number: "Please enter a valid number",
},
longitude: {
required: "Please enter a longitude",
number: "Please enter a valid number",
}
},
errorElement: "div",
errorPlacement: function(error, element) {
error.addClass("invalid-feedback");
if (element.prop("type") === "checkbox") {
error.insertAfter(element.next("label"));
} else {
error.insertAfter(element);
}
},
highlight: function(element) {
$(element).addClass("is-invalid").removeClass("is-valid");
},
unhighlight: function(element) {
$(element).addClass("is-valid").removeClass("is-invalid");
},
submitHandler: function(form) {
console.log("Form submission is valid");
form.submit();
}
});
})
/**
* ====================
* File Pond
* ====================
*/
// We want to preview images, so we register
// the Image Preview plugin, We also register
// exif orientation (to correct mobile image
// orientation) and size validation, to prevent
// large files from being added
// Function to initialize FilePond
function initializeFilePond(selector, allowMultiple = false) {
FilePond.registerPlugin(
FilePondPluginImagePreview,
FilePondPluginImageExifOrientation,
FilePondPluginFileValidateSize,
// FilePondPluginImageEdit
FilePondPluginFileValidateSize
);
// Select the file input and use
// create() to turn it into a pond
var ecommerce = FilePond.create(document.querySelector('.file-upload-multiple'));
/**
* =====================
* Blog Tags
* =====================
*/
// The DOM element you wish to replace with Tagify
var input = document.querySelector('#id_tags');
// initialize Tagify on the above input node reference
new Tagify(input, {
originalInputValueFormat: valuesArr => valuesArr.map(item => item.value).join(', ')
})
return FilePond.create(document.getElementById(selector),{
allowMultiple: allowMultiple,
storeAsFile: true,
dropOnPage: true
});
}
</script>
{% endblock %}