Update(Customer): added profile photo field

This commit is contained in:
bobbyvish
2025-01-08 12:37:07 +05:30
parent b559c62926
commit bec8f9d608
9 changed files with 370 additions and 175 deletions

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

@@ -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)
@@ -629,6 +629,7 @@ class CustomerCreateView(LoginRequiredMixin, generic.View):
# 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'),
@@ -710,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,
@@ -752,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")
@@ -1049,17 +1053,23 @@ class CustomerTransferView(LoginRequiredMixin, generic.View):
# Send the email
try:
temp_password = "GoodTimes@2025"
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)}")

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

@@ -99,6 +99,7 @@ class EventListSerializer(serializers.ModelSerializer):
"entry_fee",
"key_guest",
"age_group",
"link",
# "images",
# "is_favorited",
# "reviews",

View File

@@ -46,6 +46,7 @@ class EventForm(forms.ModelForm):
"title",
# "event_master",
"description",
"link",
"image",
"event_images",
# "status",

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

@@ -86,16 +86,20 @@
<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>
{% if data_obj.extended_data %}
{% 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

View File

@@ -57,22 +57,33 @@
{% 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,
FilePondPluginImageTransform
);
return FilePond.create(document.getElementById(selector),{
return FilePond.create(document.getElementById(selector), {
allowMultiple: allowMultiple,
acceptedFileTypes: ['image/*'],
storeAsFile: true,
dropOnPage: true
dropOnPage: true,
imageTransformOutputQuality: 90, // Set output quality (optional)
imageTransformVariants: {
thumbnail: (transforms) => ({
...transforms,
resize: {
size: {
width: 512,
height: 512,
},
mode: 'cover', // Ensures the image is cropped to fit the size
}
})
}
});
}