refactor: validation of all forms

This commit is contained in:
bobbyvish
2024-04-08 00:33:02 +05:30
parent 8878dc89ac
commit 524186b801
25 changed files with 475 additions and 83 deletions

View File

@@ -25,6 +25,7 @@ urlpatterns = [
path("meal-symptoms/<int:pk>/", views.MealSymptomAPIView.as_view()),
path("meal/", views.MealAPIView.as_view()),
path("meal/duplicate/", views.MealDuplicateAPIView.as_view()),
path("meal/date/", views.MealDateAPIView.as_view()),
path("meal/<int:pk>/", views.MealAPIView.as_view()),

View File

@@ -621,6 +621,45 @@ class MealAPIView(APIView):
message=constants.RECORD_DELETED, status=status.HTTP_204_NO_CONTENT
)
class MealDuplicateAPIView(APIView):
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
serializer_class = MealRecordSerializer
model = MealRecord
def post(self, request):
meal_id = request.POST.get("id")
meal_time = request.POST.get("time")
try:
original_meal = self.model.objects.get(pk=meal_id)
except Exception as e:
return ApiResponse.error(message="Meal not found", errors=str(e))
instance = self.model(
principal=original_meal.principal,
date=original_meal.date,
time=meal_time,
meal_type=original_meal.meal_type
)
instance.save()
for food_obj in original_meal.food_records.all():
instance.food_records.add(food_obj)
for beverage_obj in original_meal.beverage_records.all():
instance.beverage_records.add(beverage_obj)
for ingredient_obj in original_meal.food_ingredient_records.all():
instance.food_ingredient_records.add(ingredient_obj)
instance.save()
serializer_obj = self.serializer_class(instance).data
return ApiResponse.success(message=constants.SUCCESS, data=serializer_obj)
class FoodDataAPIView(APIView):
authentication_classes = []
permission_classes = []

View File

@@ -98,6 +98,11 @@ class BaseListJson(BaseDatatableView):
model = Intolerance
columns = ["id", "name", "duration", "active", "deleted"]
order_columns = ["id", "name", "duration", "active", "deleted"]
FILTER_ICONTAINS = "icontains"
def get_filter_method(self):
"""Returns preferred filter method"""
return self.FILTER_ICONTAINS
def get_initial_queryset(self):
principal_id = self.kwargs.get("principal_id")
@@ -105,12 +110,18 @@ class BaseListJson(BaseDatatableView):
return self.model.objects.filter(principal=principal_id, deleted=deleted_flag)
def filter_queryset(self, qs):
search_value = self.request.GET.get("search[value]", None)
if search_value:
qs = qs.filter(
Q(name__icontains=search_value) | Q(duration__icontains=search_value)
)
def ordering(self, qs):
print(f"request is {self.request.GET}")
order = self.request.GET.get('order[0][dir]', None)
if order:
column_index = int(self.request.GET.get('order[0][column]', None)) - 1
order_column = self.order_columns[column_index]
print(f"order column is {order_column}")
if order == "asc":
qs = qs.order_by(order_column)
elif order == "desc":
qs = qs.order_by("-" + order_column)
return qs
@@ -317,6 +328,11 @@ class MealListJsonView(BaseDatatableView):
model = MealRecord
columns = ["id", "date", "time", "meal_type"]
order_columns = ["id", "date", "time", "meal_type"]
FILTER_ICONTAINS = "icontains"
def get_filter_method(self):
"""Returns preferred filter method"""
return self.FILTER_ICONTAINS
def get_initial_queryset(self):
principal_id = self.kwargs.get("principal_id")

View File

@@ -249,6 +249,7 @@ class UserRecordView(permission.ResourcePermissionRequiredMixin, LoginRequiredMi
recent_meal_symptom_records = MealSymptomRecord.objects.filter(
principal=id
).order_by('-id')[:5]
return recent_meal_symptom_records
def get(self, request, id):
# Retrieve the IAmPrincipal instance

View File

@@ -100,6 +100,7 @@ class IAmPrincipalForm(forms.ModelForm):
if principal_type == models.IAmPrincipalType.objects.get(name=PRINCIPAL_TYPE_ADMIN):
instance.is_superuser = True
elif principal_type == models.IAmPrincipalType.objects.get(name=PRINCIPAL_TYPE_SUBADMIN):
instance.is_superuser = False
instance.is_staff = True
if commit:
instance.save()
@@ -221,6 +222,9 @@ class ProfileEditForm(forms.ModelForm):
"gender",
"phone_no"
]
labels = {
'phone_no': 'Phone Number', # Map phone_no field to phone_number_label variable
}
class IAmPrincipalResourceLinkForm(IAmPrincipalForm):

View File

@@ -0,0 +1,34 @@
# Generated by Django 5.0.2 on 2024-04-05 07:06
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('module_iam', '0008_appversion_device_type_alter_appversion_version'),
]
operations = [
migrations.AlterField(
model_name='appversion',
name='device_type',
field=models.CharField(choices=[('apple', 'apple'), ('android', 'android')], max_length=10, verbose_name='Device Type (apple / android)'),
),
migrations.AlterField(
model_name='appversion',
name='force_upgrade',
field=models.BooleanField(default=False, help_text='Indicates whether a force upgrade is needed for this app version.', verbose_name='Force Upgrade'),
),
migrations.AlterField(
model_name='appversion',
name='recommend_upgrade',
field=models.BooleanField(default=False, help_text='Indicates whether a recommend upgrade is needed for this app version.', verbose_name='Recommend Upgrade'),
),
migrations.AlterField(
model_name='appversion',
name='version',
field=models.CharField(max_length=5, validators=[django.core.validators.RegexValidator('^\\d+\\.\\d+\\.\\d+$')], verbose_name='Version'),
),
]

View File

@@ -452,10 +452,10 @@ class AppVersion(models.Model):
('apple', 'apple'),
('android', 'android'),
]
device_type = models.CharField(max_length=10, choices=DEVICE_CHOICES)
version = models.CharField(max_length=10, validators=[RegexValidator(r'^\d+\.\d+$')])
force_upgrade = models.BooleanField(default=False, help_text='Indicates whether a force upgrade is needed for this app version.')
recommend_upgrade = models.BooleanField(default=False, help_text='Indicates whether a recommend upgrade is needed for this app version.')
device_type = models.CharField(max_length=10, choices=DEVICE_CHOICES, verbose_name='Device Type (apple / android)')
version = models.CharField(max_length=5, validators=[RegexValidator(r'^\d+\.\d+\.\d+$')], verbose_name='Version')
force_upgrade = models.BooleanField(default=False, verbose_name='Force Upgrade', help_text='Indicates whether a force upgrade is needed for this app version.')
recommend_upgrade = models.BooleanField(default=False, verbose_name='Recommend Upgrade', help_text='Indicates whether a recommend upgrade is needed for this app version.')
class Meta:
db_table = "app_version"

View File

@@ -51,8 +51,8 @@
<a href="{% url 'module_iam:principal_group' %}"> IAM Group </a>
</li>
<li class="{% if page_name == iam_constants_context.RESOURCE_IAM_ROLE %}active{% endif %}">
<a href="{% url 'module_iam:role' %}"> IAM Role </a> -->
</li>
<a href="{% url 'module_iam:role' %}"> IAM Role </a>
</li> -->
</ul>
</li>
{% endif %}

View File

@@ -47,6 +47,7 @@
<!-- BEGIN GLOBAL MANDATORY SCRIPTS -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<script src="{% static 'src/bootstrap/js/bootstrap.bundle.min.js' %}"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.3/jquery.validate.min.js"></script>
<!-- END GLOBAL MANDATORY SCRIPTS -->
<!-- BEGIN PAGE LEVEL PLUGINS/CUSTOM SCRIPTS -->

View File

@@ -71,6 +71,7 @@
<script src="{% static 'src/plugins/src/mousetrap/mousetrap.min.js' %}"></script>
<script src="{% static 'src/plugins/src/waves/waves.min.js' %}"></script>
<script src="{% static 'layouts/collapsible-menu/app.js' %}"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.3/jquery.validate.min.js"></script>
<script src="{% static 'src/assets/js/custom.js' %}"></script>
<!-- END GLOBAL MANDATORY SCRIPTS -->

View File

@@ -12,16 +12,12 @@
<div class="col-lg-12">
<div class="row mb-2">
<div class="col">
<h3>Bowel Details</h3>
</div>
<div class="col text-end">
{% comment %} <button class="btn btn-dark mb-2 me-4" onclick="history.back()">
<i class="fa fa-arrow-left"></i>
Back
</button> {% endcomment %}
{% comment %} <a class="btn btn-success mb-2 me-4" href="{% url 'module_cms:faq_category_add' %}">Add
Category</a> {% endcomment %}
{% comment %} <a class="btn btn-primary mb-2 me-4" href="">Add User</a> {% endcomment %}
<a onclick="history.back()" 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>Bowel Details</span></h3>
</a>
</div>
</div>

View File

@@ -38,8 +38,10 @@
<thead>
<tr role="row">
<th class="sorting_asc text-center dt-no-sorting" tabindex="0" aria-controls="style-3"
aria-sort="ascending" style="width: 50.2656px;">Sr no.</th>
<th class="sorting_asc text-center" tabindex="0" aria-controls="style-3"
aria-sort="ascending" style="width: 50.2656px;">#</th>
aria-sort="ascending" style="width: 50.2656px;">Record Id</th>
<th class="sorting text-center" tabindex="1" aria-controls="style-3"
colspan="1" style="width: 44.2344px;">User Symptoms</th>
<th class="sorting text-center" tabindex="2" aria-controls="style-3"
@@ -95,13 +97,23 @@ function initializeDataTable(dataTableInstance, mainUrl) {
type: "GET",
},
columns: [
{
data: null,
className: "text-center",
render: function(data, type, row, meta) {
// Calculate sequence number based on page, page length, and index
var sequenceNumber = meta.row + meta.settings._iDisplayStart + 1;
return sequenceNumber;
}
},
{ data: "id", className: "text-center" },
{ data: "name", className: "text-center" },
{ data: "duration", className: "text-center" }
],
debug: true,
columnDefs: [
{ targets: [1, 2, 0], searchable: true, orderable: true }
{ targets: [1, 2, 3], searchable: true, orderable: true },
{ targets: [0], searchable: false, orderable: false }
],
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'Bf>>>" +
"<'table-responsive'tr>" +

View File

@@ -38,8 +38,10 @@
<thead>
<tr role="row">
<th class="sorting_asc text-center dt-no-sorting" tabindex="0" aria-controls="style-3"
aria-sort="ascending" style="width: 50.2656px;">Sr no</th>
<th class="sorting_asc text-center" tabindex="0" aria-controls="style-3"
aria-sort="ascending" style="width: 50.2656px;">#</th>
aria-sort="ascending" style="width: 50.2656px;">Record Id</th>
<th class="sorting text-center" tabindex="1" aria-controls="style-3"
colspan="1" style="width: 44.2344px;">User Intolerances</th>
<th class="sorting text-center" tabindex="2" aria-controls="style-3"
@@ -97,13 +99,23 @@ function initializeDataTable(tableName, mainUrl) {
type: "GET",
},
columns: [
{
data: null,
className: "text-center",
render: function(data, type, row, meta) {
// Calculate sequence number based on page, page length, and index
var sequenceNumber = meta.row + meta.settings._iDisplayStart + 1;
return sequenceNumber;
}
},
{ data: "id", className: "text-center" },
{ data: "name", className: "text-center" },
{ data: "duration", className: "text-center" },
],
debug: true,
columnDefs: [
{ targets: [0, 1, 2], searchable: true, orderable: true }
{ targets: [1, 2, 3], searchable: true, orderable: true },
{ targets: [0], searchable: false, orderable: false }
],
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'Bf>>>" +
"<'table-responsive'tr>" +

View File

@@ -12,16 +12,12 @@
<div class="col-lg-12">
<div class="row mb-2">
<div class="col">
<h3>Meal Details</h3>
</div>
<div class="col text-end">
{% comment %} <button class="btn btn-dark mb-2 me-4" onclick="history.back()">
<i class="fa fa-arrow-left"></i>
Back
</button> {% endcomment %}
{% comment %} <a class="btn btn-success mb-2 me-4" href="{% url 'module_cms:faq_category_add' %}">Add
Category</a> {% endcomment %}
{% comment %} <a class="btn btn-primary mb-2 me-4" href="">Add User</a> {% endcomment %}
<a onclick="history.back()" 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>Meal Details</span></h3>
</a>
</div>
</div>

View File

@@ -12,16 +12,12 @@
<div class="col-lg-12">
<div class="row mb-2">
<div class="col">
<h3>Meal Symptoms Details</h3>
</div>
<div class="col text-end">
{% comment %} <button class="btn btn-dark mb-2 me-4" onclick="history.back()">
<i class="fa fa-arrow-left"></i>
Back
</button> {% endcomment %}
{% comment %} <a class="btn btn-success mb-2 me-4" href="{% url 'module_cms:faq_category_add' %}">Add
Category</a> {% endcomment %}
{% comment %} <a class="btn btn-primary mb-2 me-4" href="">Add User</a> {% endcomment %}
<a onclick="history.back()" 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>Meal Symptoms Details</span></h3>
</a>
</div>
</div>

View File

@@ -12,16 +12,12 @@
<div class="col-lg-12">
<div class="row mb-2">
<div class="col">
<h3>Meal Details</h3>
</div>
<div class="col text-end">
{% comment %} <button class="btn btn-dark mb-2 me-4" onclick="history.back()">
<i class="fa fa-arrow-left"></i>
Back
</button> {% endcomment %}
{% comment %} <a class="btn btn-success mb-2 me-4" href="{% url 'module_cms:faq_category_add' %}">Add
Category</a> {% endcomment %}
{% comment %} <a class="btn btn-primary mb-2 me-4" href="">Add User</a> {% endcomment %}
<a onclick="history.back()" 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>Medication Details</span></h3>
</a>
</div>
</div>

View File

@@ -39,8 +39,10 @@
<thead>
<tr role="row">
<th class="sorting_asc text-center dt-no-sorting" tabindex="0" aria-controls="style-3"
aria-sort="ascending" style="width: 50.2656px;">Sr no.</th>
<th class="sorting_asc text-center" tabindex="0" aria-controls="style-3"
aria-sort="ascending" style="width: 50.2656px;">#</th>
aria-sort="ascending" style="width: 50.2656px;">Record Id</th>
<th class="sorting text-center" tabindex="1" aria-controls="style-3"
colspan="1" style="width: 44.2344px;">User Past Treatment</th>
<th class="sorting text-center" tabindex="2" aria-controls="style-3"
@@ -97,13 +99,23 @@ function initializeDataTable(dataTableInstance, mainUrl) {
type: "GET",
},
columns: [
{
data: null,
className: "text-center",
render: function(data, type, row, meta) {
// Calculate sequence number based on page, page length, and index
var sequenceNumber = meta.row + meta.settings._iDisplayStart + 1;
return sequenceNumber;
}
},
{ data: "id", className: "text-center" },
{ data: "name", className: "text-center" },
{ data: "duration", className: "text-center" },
],
debug: true,
columnDefs: [
{ targets: [1, 2, 0], searchable: true, orderable: true }
{ targets: [1, 2, 3], searchable: true, orderable: true },
{ targets: [0], searchable: false, orderable: false }
],
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'Bf>>>" +
"<'table-responsive'tr>" +

View File

@@ -38,8 +38,10 @@
<thead>
<tr role="row">
<th class="sorting_asc text-center dt-no-sorting" tabindex="0" aria-controls="style-3"
aria-sort="ascending" style="width: 50.2656px;">Sr no</th>
<th class="sorting_asc text-center" tabindex="0" aria-controls="style-3"
aria-sort="ascending" style="width: 50.2656px;">#</th>
aria-sort="ascending" style="width: 50.2656px;">Record Id</th>
<th class="sorting text-center" tabindex="1" aria-controls="style-3"
colspan="1" style="width: 44.2344px;">User Symptoms</th>
<th class="sorting text-center" tabindex="2" aria-controls="style-3"
@@ -97,13 +99,23 @@ function initializeDataTable(dataTableInstance, mainUrl) {
type: "GET",
},
columns: [
{
data: null,
className: "text-center",
render: function(data, type, row, meta) {
// Calculate sequence number based on page, page length, and index
var sequenceNumber = meta.row + meta.settings._iDisplayStart + 1;
return sequenceNumber;
}
},
{ data: "id", className: "text-center" },
{ data: "name", className: "text-center" },
{ data: "duration", className: "text-center" }
],
debug: true,
columnDefs: [
{ targets: [0, 1, 2], searchable: true, orderable: true }
{ targets: [1, 2, 3], searchable: true, orderable: true },
{ targets: [0], searchable: false, orderable: false }
],
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'Bf>>>" +
"<'table-responsive'tr>" +

View File

@@ -9,21 +9,21 @@
</div>
<div class="card-body">
{% if validlink %}
<form method="post">
<form method="post" id="changePasswordForm">
{% csrf_token %}
<div class="form-group">
<input type="hidden" class="form-control" id="id_username" name="username"
value="{{ form.user.get_username }}" autocomplete="username">
</div>
<div class="form-group">
{{ form.new_password1.errors }}
<label for="id_new_password1">{% translate 'New password:' %}</label>
<input type="password" class="form-control" id="id_new_password1" name="new_password1">
{{ form.new_password1.errors }}
</div>
<div class="form-group">
{{ form.new_password2.errors }}
<label for="id_new_password2">{% translate 'Confirm password:' %}</label>
<input type="password" class="form-control" id="id_new_password2" name="new_password2">
{{ form.new_password2.errors }}
</div>
<button type="submit" class="btn btn-primary mt-4">{% translate 'Change my password' %}</button>
</form>
@@ -40,5 +40,49 @@
{% block javascript %}
<!-- include required js cdn link through html here -->
<script>
$(document).ready(function() {
$('#changePasswordForm').validate({
rules: {
new_password1: {
required: true,
// Add custom method for password complexity
complexPassword: true
},
new_password2: {
required: true,
equalTo: '#id_new_password1'
}
},
messages: {
new_password1: {
required: "Please enter a new password."
},
new_password2: {
required: "Please confirm your new password.",
equalTo: "Passwords do not match."
}
},
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');
}
});
// Add custom validation method for password complexity
$.validator.addMethod("complexPassword", function(value, element) {
return /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*()_+}{":;'?/>.<,])(?=.*[^\w\d\s])/.test(value);
}, "Password must include at least one uppercase letter, one lowercase letter, one number, and special characters.");
});
</script>
{% endblock %}

View File

@@ -22,7 +22,7 @@
<div class="statbox widget box box-shadow">
<div class="widget-content widget-content-area">
<form method="post">
<form method="post" id="userForm">
{% csrf_token %}
{% include 'base_structure/includes/dynamic_template_form.html' with form=form %}
<div class="mt-4 mb-0">
@@ -42,5 +42,81 @@
{% block javascript %}
<!-- include required js cdn link through html here -->
<script>
$(document).ready(function() {
// 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 password complexity
$.validator.addMethod("complexPassword", function(value, element) {
return /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*()_+}{":;'?/>.<,])(?=.*[^\w\d\s])/.test(value);
}, "Password must include at least one uppercase letter, one lowercase letter, one number, and special characters.");
// Initialize form validation
$("#userForm").validate({
rules: {
first_name: {
required: true,
maxlength: 50, // Example: Set maximum length for first_name to 50 characters
startsWithLetter: true,
noSpecialChars: true,
},
email: {
required: true,
email: true // Check for valid email format
},
password: {
required: true,
complexPassword: true
},
confirm_password: {
required: true,
equalTo: "#id_password" // Ensure confirm_password matches password field
}
},
messages: {
first_name: {
required: "Please enter your name.",
maxlength: "Name must not exceed 50 characters.",
startsWithLetter: "First name must start with a letter.",
noSpecialChars: "Please enter only letters and spaces.",
},
email: {
required: "Please enter your email address.",
email: "Please enter a valid email address."
},
password: {
required: "Please enter your password.",
},
confirm_password: {
required: "Please confirm your password.",
equalTo: "Passwords do not match."
}
},
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');
}
});
});
</script>
{% endblock %}

View File

@@ -24,7 +24,7 @@
<div class="statbox widget box box-shadow">
<div class="widget-content widget-content-area">
<form method="POST">
<form method="POST" id="faqs_form">
{% csrf_token %}
{% include 'base_structure/includes/dynamic_template_form.html' with form=form %}
<div class="mt-4 mb-0">
@@ -47,6 +47,37 @@
<script>
$(document).ready(function() {
// Initialize form validation
$("#faqs_form").validate({
rules: {
question: {
required: true
},
answer: {
required: true
}
},
messages: {
question: {
required: "Please enter a question."
},
answer: {
required: "Please enter an answer."
}
},
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');
}
});
});
</script>
{% endblock %}

View File

@@ -3,8 +3,8 @@
{% block stylesheet %}
<!-- include required css cdn link through html here -->
{% include "cdn_through_html/quill_cdn_css.html" %}
{{form.media}}
{% include "cdn_through_html/quill_cdn_css.html" %}
{{form.media}}
{% endblock %}
@@ -26,9 +26,10 @@
<div class="statbox widget box box-shadow">
<div class="widget-content widget-content-area">
<form method="POST">
<form method="POST" id="privacy_policy_form">
{% csrf_token %}
{% include 'base_structure/includes/dynamic_template_form.html' with form=form %}
<div id="privacy_policy_error" class="text-danger"></div>
<div class="mt-4 mb-0">
<div class="d-grid"><button class="btn btn-primary btn-block" type="submit">Submit</button></div>
</div>
@@ -48,11 +49,22 @@
<!-- include required css cdn link through html here -->
{% include "cdn_through_html/quill_cdn_js.html" %}
<script>
$(document).ready(function() {
// Initialize form submission event handler
$("#privacy_policy_form").submit(function(event) {
console.log("function called")
// Check if the Quill editor has any content
var quillContent = $("#quill-id_privacy_policy .ql-editor").html().trim();
if (quillContent === '<p><br></p>') {
// If Quill editor content is empty, prevent form submission and display an error message
event.preventDefault();
$("#privacy_policy_error").text("Please enter some text.").show();
} else {
// If Quill editor content is not empty, allow form submission
$("#privacy_policy_error").hide();
}
});
});
</script>
{% endblock %}

View File

@@ -26,9 +26,10 @@
<div class="statbox widget box box-shadow">
<div class="widget-content widget-content-area">
<form method="POST">
<form method="POST" id="terms_and_condition">
{% csrf_token %}
{% include 'base_structure/includes/dynamic_template_form.html' with form=form %}
<div id="terms_condition_error" class="text-danger"></div>
<div class="mt-4 mb-0">
<div class="d-grid"><button class="btn btn-primary btn-block" type="submit">Submit</button></div>
</div>
@@ -48,11 +49,21 @@
<!-- include required css cdn link through html here -->
{% include "cdn_through_html/quill_cdn_js.html" %}
<script>
$(document).ready(function() {
// Initialize form submission event handler
$("#terms_and_condition").submit(function(event) {
// Check if the Quill editor has any content
var quillContent = $("#quill-id_terms_condition .ql-editor").html().trim();
if (quillContent === '<p><br></p>') {
// If Quill editor content is empty, prevent form submission and display an error message
event.preventDefault();
$("#terms_condition_error").text("Please enter some text for the Terms and Conditions.").show();
} else {
// If Quill editor content is not empty, allow form submission
$("#terms_condition_error").hide();
}
});
});
</script>
{% endblock %}

View File

@@ -75,7 +75,7 @@
</div>
</div>
</form> -->
<form method="POST" enctype="multipart/form-data">
<form method="POST" enctype="multipart/form-data" id="profileEditForm">
{% csrf_token %}
{% include 'base_structure/includes/dynamic_template_form.html' with form=form %}
<div class="mt-4 mb-0">
@@ -127,7 +127,96 @@
if (profilePhotoUrl) {
// If the URL exists, add the profile photo to FilePond
pond.addFile(profilePhotoUrl);
}
}
$(document).ready(function() {
// 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.");
// Initialize form validation
$("#profileEditForm").validate({
rules: {
first_name: {
required: true,
minlength: 2,
maxlength:15,
noSpecialChars: true,
startsWithLetter: true
},
last_name: {
required: true,
minlength: 2,
maxlength: 15,
noSpecialChars: true,
startsWithLetter: true
},
date_of_birth: {
required: true,
date: true,
dateISO: true, // Check for ISO date format (YYYY-MM-DD)
pattern: /^\d{4}-\d{2}-\d{2}$/ // Custom pattern for YYYY-MM-DD format
},
gender: {
required: true
},
phone_no: {
required: true,
digits: true,
minlength: 10,
maxlength: 10
}
},
messages: {
first_name: {
required: "Please enter your first name.",
minlength: "First name must be at least 2 characters.",
maxlength: "First name must not exceed 15 characters.",
noSpecialChars: "Please enter only letters and spaces.",
startsWithLetter: "First name must start with a letter."
},
last_name: {
required: "Please enter your last name.",
minlength: "Last name must be at least 2 characters.",
maxlength: "First name must not exceed 15 characters.",
noSpecialChars: "Please enter only letters and spaces.",
startsWithLetter: "Last name must start with a letter."
},
date_of_birth: {
required: "Please enter your date of birth.",
date: "Please enter a valid date in the format YYYY-MM-DD.",
dateISO: "Please enter a valid date in the format YYYY-MM-DD.",
pattern: "Please enter a valid date in the format YYYY-MM-DD."
},
gender: {
required: "Please select your gender."
},
phone_no: {
required: "Please enter your phone number.",
digits: "Please enter only digits.",
minlength: "Phone number must be 10 digits long.",
maxlength: "Phone number must be 10 digits long."
}
},
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');
}
});
});
</script>
{% endblock %}

View File

@@ -115,7 +115,7 @@
<button class="nav-link" id="pills-reply-tab" data-bs-toggle="pill" data-bs-target="#pills-reply" type="button" role="tab" aria-controls="pills-reply" aria-selected="false">Reply</button>
</li>
</ul>
<div class="tab-content" id="pills-tabContent">
<div class="tab-content" id="pills-tabContent" style="max-height: 200px; overflow-y: auto;">
<div class="tab-pane fade show active" id="pills-message" role="tabpanel" aria-labelledby="pills-message-tab" tabindex="0">
<div class="card">
<div class="card-body">