new changes removed the encryption
This commit is contained in:
parent
c83b878be6
commit
d2a86f12c3
@ -62,7 +62,7 @@ INSTALLED_APPS = [
|
||||
"django_q",
|
||||
"widget_tweaks",
|
||||
"easyaudit",
|
||||
"encrypted_model_fields",
|
||||
"mathfilters"
|
||||
]
|
||||
|
||||
|
||||
@ -534,4 +534,3 @@ LOGGING={
|
||||
}
|
||||
|
||||
|
||||
FIELD_ENCRYPTION_KEY="PWQimxxcDjlRsSSof2gaj42a3frmrLt2xgCTa4R06pE="
|
||||
@ -285,6 +285,42 @@ class PersonForm(forms.ModelForm):
|
||||
"gpa": forms.TextInput(attrs={'class': 'custom-decimal-input'}),
|
||||
"national_id":forms.NumberInput(attrs={'min': 0, 'step': 1}),
|
||||
}
|
||||
def clean_email(self):
|
||||
email = self.cleaned_data.get('email')
|
||||
|
||||
if not email:
|
||||
|
||||
return email
|
||||
|
||||
|
||||
if email:
|
||||
instance = self.instance
|
||||
qs = CustomUser.objects.filter(email=email) | CustomUser.objects.filter(username=email)
|
||||
if not instance.pk: # Creating new instance
|
||||
|
||||
|
||||
if qs.exists():
|
||||
raise ValidationError(_("A user account with this email address already exists. Please use a different email."))
|
||||
|
||||
else: # Editing existing instance
|
||||
# if (
|
||||
# qs
|
||||
# .exclude(pk=instance.user.pk)
|
||||
# .exists()
|
||||
# ):
|
||||
|
||||
# raise ValidationError(_("An user with this email already exists."))
|
||||
pass
|
||||
|
||||
return email.strip()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
return email
|
||||
|
||||
class ApplicationForm(forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
@ -987,7 +1023,7 @@ class HiringAgencyForm(forms.ModelForm):
|
||||
}
|
||||
),
|
||||
"email": forms.EmailInput(
|
||||
attrs={"class": "form-control"}
|
||||
attrs={"class": "form-control","required": True}
|
||||
),
|
||||
"phone": forms.TextInput(
|
||||
attrs={"class": "form-control"}
|
||||
@ -1076,6 +1112,7 @@ class HiringAgencyForm(forms.ModelForm):
|
||||
# instance = self.instance
|
||||
email = email.lower().strip()
|
||||
if not instance.pk: # Creating new instance
|
||||
print("created ....")
|
||||
if HiringAgency.objects.filter(email=email).exists():
|
||||
raise ValidationError("An agency with this email already exists.")
|
||||
else: # Editing existing instance
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.2.7 on 2025-12-07 13:15
|
||||
# Generated by Django 5.2.7 on 2025-12-08 15:04
|
||||
|
||||
import django.contrib.auth.models
|
||||
import django.contrib.auth.validators
|
||||
@ -8,7 +8,6 @@ import django.utils.timezone
|
||||
import django_ckeditor_5.fields
|
||||
import django_countries.fields
|
||||
import django_extensions.db.fields
|
||||
import encrypted_model_fields.fields
|
||||
import recruitment.validators
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
@ -153,10 +152,10 @@ class Migration(migrations.Migration):
|
||||
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
|
||||
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
|
||||
('user_type', models.CharField(choices=[('staff', 'Staff'), ('agency', 'Agency'), ('candidate', 'Candidate')], default='staff', max_length=20, verbose_name='User Type')),
|
||||
('phone', encrypted_model_fields.fields.EncryptedCharField(blank=True, null=True, verbose_name='Phone')),
|
||||
('phone', models.CharField(blank=True, null=True, verbose_name='Phone')),
|
||||
('profile_image', models.ImageField(blank=True, null=True, upload_to='profile_pic/', validators=[recruitment.validators.validate_image_size], verbose_name='Profile Image')),
|
||||
('designation', models.CharField(blank=True, max_length=100, null=True, verbose_name='Designation')),
|
||||
('email', encrypted_model_fields.fields.EncryptedEmailField(error_messages={'unique': 'A user with this email already exists.'}, unique=True)),
|
||||
('email', models.EmailField(error_messages={'unique': 'A user with this email already exists.'}, max_length=254, unique=True)),
|
||||
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
|
||||
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
|
||||
],
|
||||
@ -245,8 +244,8 @@ class Migration(migrations.Migration):
|
||||
('slug', django_extensions.db.fields.RandomCharField(blank=True, editable=False, length=8, unique=True, verbose_name='Slug')),
|
||||
('name', models.CharField(max_length=200, unique=True, verbose_name='Agency Name')),
|
||||
('contact_person', models.CharField(blank=True, max_length=150, verbose_name='Contact Person')),
|
||||
('email', models.EmailField(blank=True, max_length=254)),
|
||||
('phone', models.CharField(blank=True, max_length=20)),
|
||||
('email', models.EmailField(max_length=254, unique=True)),
|
||||
('phone', models.CharField(blank=True, max_length=20, null=True)),
|
||||
('website', models.URLField(blank=True)),
|
||||
('notes', models.TextField(blank=True, help_text='Internal notes about the agency')),
|
||||
('country', django_countries.fields.CountryField(blank=True, max_length=2, null=True)),
|
||||
@ -487,14 +486,14 @@ class Migration(migrations.Migration):
|
||||
('first_name', models.CharField(max_length=255, verbose_name='First Name')),
|
||||
('last_name', models.CharField(max_length=255, verbose_name='Last Name')),
|
||||
('middle_name', models.CharField(blank=True, max_length=255, null=True, verbose_name='Middle Name')),
|
||||
('email', encrypted_model_fields.fields.EncryptedEmailField(db_index=True, unique=True, verbose_name='Email')),
|
||||
('phone', encrypted_model_fields.fields.EncryptedPositiveIntegerField(blank=True, null=True, verbose_name='Phone')),
|
||||
('email', models.EmailField(db_index=True, max_length=254, unique=True, verbose_name='Email')),
|
||||
('phone', models.CharField(blank=True, null=True, verbose_name='Phone')),
|
||||
('date_of_birth', models.DateField(blank=True, null=True, verbose_name='Date of Birth')),
|
||||
('gender', models.CharField(blank=True, choices=[('M', 'Male'), ('F', 'Female')], max_length=1, null=True, verbose_name='Gender')),
|
||||
('gpa', models.DecimalField(decimal_places=2, help_text='GPA must be between 0 and 4.', max_digits=3, verbose_name='GPA')),
|
||||
('national_id', encrypted_model_fields.fields.EncryptedPositiveIntegerField(help_text='Enter the national id or iqama number')),
|
||||
('national_id', models.CharField(help_text='Enter the national id or iqama number')),
|
||||
('nationality', django_countries.fields.CountryField(blank=True, max_length=2, null=True, verbose_name='Nationality')),
|
||||
('address', encrypted_model_fields.fields.EncryptedCharField(blank=True, null=True, verbose_name='Address')),
|
||||
('address', models.TextField(blank=True, null=True, verbose_name='Address')),
|
||||
('profile_image', models.ImageField(blank=True, null=True, upload_to='profile_pic/', validators=[recruitment.validators.validate_image_size], verbose_name='Profile Image')),
|
||||
('linkedin_profile', models.URLField(blank=True, null=True, verbose_name='LinkedIn Profile URL')),
|
||||
('agency', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='recruitment.hiringagency', verbose_name='Hiring Agency')),
|
||||
|
||||
@ -1,19 +0,0 @@
|
||||
# Generated by Django 5.2.7 on 2025-12-07 13:38
|
||||
|
||||
import encrypted_model_fields.fields
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('recruitment', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='person',
|
||||
name='address',
|
||||
field=encrypted_model_fields.fields.EncryptedTextField(blank=True, null=True, verbose_name='Address'),
|
||||
),
|
||||
]
|
||||
@ -1,24 +0,0 @@
|
||||
# Generated by Django 5.2.7 on 2025-12-07 13:43
|
||||
|
||||
import encrypted_model_fields.fields
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('recruitment', '0002_alter_person_address'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='person',
|
||||
name='national_id',
|
||||
field=encrypted_model_fields.fields.EncryptedCharField(help_text='Enter the national id or iqama number'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='person',
|
||||
name='phone',
|
||||
field=encrypted_model_fields.fields.EncryptedCharField(blank=True, null=True, verbose_name='Phone'),
|
||||
),
|
||||
]
|
||||
@ -1,39 +0,0 @@
|
||||
# Generated by Django 5.2.7 on 2025-12-07 13:59
|
||||
|
||||
import encrypted_model_fields.fields
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('recruitment', '0003_alter_person_national_id_alter_person_phone'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='formsubmission',
|
||||
name='applicant_email',
|
||||
field=encrypted_model_fields.fields.EncryptedEmailField(blank=True, db_index=True, help_text='Email of the applicant'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='hiringagency',
|
||||
name='email',
|
||||
field=encrypted_model_fields.fields.EncryptedEmailField(blank=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='hiringagency',
|
||||
name='phone',
|
||||
field=encrypted_model_fields.fields.EncryptedCharField(blank=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='participants',
|
||||
name='email',
|
||||
field=encrypted_model_fields.fields.EncryptedEmailField(verbose_name='Email'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='participants',
|
||||
name='phone',
|
||||
field=encrypted_model_fields.fields.EncryptedCharField(blank=True, null=True, verbose_name='Phone Number'),
|
||||
),
|
||||
]
|
||||
@ -21,7 +21,7 @@ from django.contrib.contenttypes.models import ContentType
|
||||
from django.db.models import F, Value, IntegerField, CharField,Q
|
||||
from django.db.models.functions import Coalesce, Cast
|
||||
from django.db.models.fields.json import KeyTransform, KeyTextTransform
|
||||
from encrypted_model_fields.fields import EncryptedCharField,EncryptedEmailField,EncryptedTextField
|
||||
|
||||
|
||||
class EmailContent(models.Model):
|
||||
subject = models.CharField(max_length=255, verbose_name=_("Subject"))
|
||||
@ -47,7 +47,7 @@ class CustomUser(AbstractUser):
|
||||
user_type = models.CharField(
|
||||
max_length=20, choices=USER_TYPES, default="staff", verbose_name=_("User Type")
|
||||
)
|
||||
phone = EncryptedCharField(
|
||||
phone = models.CharField(
|
||||
blank=True, null=True, verbose_name=_("Phone")
|
||||
)
|
||||
profile_image = models.ImageField(
|
||||
@ -60,7 +60,7 @@ class CustomUser(AbstractUser):
|
||||
designation = models.CharField(
|
||||
max_length=100, blank=True, null=True, verbose_name=_("Designation")
|
||||
)
|
||||
email = EncryptedEmailField(
|
||||
email = models.EmailField(
|
||||
unique=True,
|
||||
error_messages={
|
||||
"unique": _("A user with this email already exists."),
|
||||
@ -517,12 +517,12 @@ class Person(Base):
|
||||
middle_name = models.CharField(
|
||||
max_length=255, blank=True, null=True, verbose_name=_("Middle Name")
|
||||
)
|
||||
email = EncryptedEmailField(
|
||||
email = models.EmailField(
|
||||
unique=True,
|
||||
db_index=True,
|
||||
verbose_name=_("Email"),
|
||||
)
|
||||
phone = EncryptedCharField(
|
||||
phone = models.CharField(
|
||||
blank=True, null=True, verbose_name=_("Phone")
|
||||
)
|
||||
date_of_birth = models.DateField(
|
||||
@ -538,11 +538,11 @@ class Person(Base):
|
||||
gpa = models.DecimalField(
|
||||
max_digits=3, decimal_places=2, verbose_name=_("GPA"),help_text=_("GPA must be between 0 and 4.")
|
||||
)
|
||||
national_id = EncryptedCharField(
|
||||
national_id = models.CharField(
|
||||
help_text=_("Enter the national id or iqama number")
|
||||
)
|
||||
nationality = CountryField(blank=True, null=True, verbose_name=_("Nationality"))
|
||||
address = EncryptedTextField(blank=True, null=True, verbose_name=_("Address"))
|
||||
address = models.TextField(blank=True, null=True, verbose_name=_("Address"))
|
||||
|
||||
# Optional linking to user account
|
||||
user = models.OneToOneField(
|
||||
@ -1790,7 +1790,7 @@ class FormSubmission(Base):
|
||||
applicant_name = models.CharField(
|
||||
max_length=200, blank=True, help_text="Name of the applicant"
|
||||
)
|
||||
applicant_email = EncryptedEmailField(
|
||||
applicant_email = models.EmailField(
|
||||
db_index=True, blank=True, help_text="Email of the applicant"
|
||||
) # Added index
|
||||
|
||||
@ -2092,8 +2092,8 @@ class HiringAgency(Base):
|
||||
contact_person = models.CharField(
|
||||
max_length=150, blank=True, verbose_name=_("Contact Person")
|
||||
)
|
||||
email = EncryptedEmailField(blank=True)
|
||||
phone = EncryptedCharField(max_length=20, blank=True)
|
||||
email = models.EmailField(unique=True)
|
||||
phone = models.CharField(max_length=20, blank=True,null=True)
|
||||
website = models.URLField(blank=True)
|
||||
notes = models.TextField(blank=True, help_text=_("Internal notes about the agency"))
|
||||
country = CountryField(blank=True, null=True, blank_label=_("Select country"))
|
||||
@ -2503,8 +2503,8 @@ class Participants(Base):
|
||||
name = models.CharField(
|
||||
max_length=255, verbose_name=_("Participant Name"), null=True, blank=True
|
||||
)
|
||||
email =EncryptedEmailField(verbose_name=_("Email"))
|
||||
phone = EncryptedCharField(
|
||||
email =models.EmailField(verbose_name=_("Email"))
|
||||
phone = models.CharField(
|
||||
max_length=12, verbose_name=_("Phone Number"), null=True, blank=True
|
||||
)
|
||||
designation = models.CharField(
|
||||
|
||||
@ -459,16 +459,19 @@ def hiring_agency_created(sender, instance, created, **kwargs):
|
||||
def person_created(sender, instance, created, **kwargs):
|
||||
if created and not instance.user:
|
||||
logger.info(f"New Person created: {instance.pk} - {instance.email}")
|
||||
user = User.objects.create_user(
|
||||
username=instance.email,
|
||||
first_name=instance.first_name,
|
||||
last_name=instance.last_name,
|
||||
email=instance.email,
|
||||
phone=instance.phone,
|
||||
user_type="candidate",
|
||||
)
|
||||
instance.user = user
|
||||
instance.save()
|
||||
try:
|
||||
user = User.objects.create_user(
|
||||
username=instance.email,
|
||||
first_name=instance.first_name,
|
||||
last_name=instance.last_name,
|
||||
email=instance.email,
|
||||
phone=instance.phone,
|
||||
user_type="candidate",
|
||||
)
|
||||
instance.user = user
|
||||
instance.save()
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
|
||||
@receiver(post_save, sender=Source)
|
||||
|
||||
@ -242,6 +242,7 @@ class PersonUpdateView( UpdateView,LoginRequiredMixin,StaffOrAgencyRequiredMixin
|
||||
success_url = reverse_lazy("person_list")
|
||||
|
||||
def form_valid(self, form):
|
||||
|
||||
if self.request.POST.get("view") == "portal":
|
||||
form.save()
|
||||
return redirect("agency_portal_persons_list")
|
||||
@ -5994,9 +5995,12 @@ def application_signup(request, slug):
|
||||
address = form.cleaned_data["address"]
|
||||
# gpa = form.cleaned_data["gpa"]
|
||||
password = form.cleaned_data["password"]
|
||||
gpa=form.cleaned_data["gpa"]
|
||||
natiional_id=form.cleaned_data["national_id"]
|
||||
|
||||
user = User.objects.create_user(
|
||||
username = email,email=email,first_name=first_name,last_name=last_name,phone=phone,user_type="candidate"
|
||||
username = email,email=email,first_name=first_name,last_name=last_name,phone=phone,user_type="candidate",
|
||||
gpa=gpa,natiional_id=natiional_id
|
||||
)
|
||||
user.set_password(password)
|
||||
user.save()
|
||||
|
||||
@ -47,8 +47,7 @@ django-ckeditor-5==0.2.18
|
||||
django-cors-headers==4.9.0
|
||||
django-countries==7.6.1
|
||||
django-crispy-forms==2.4
|
||||
django-easy-audit==1.3.7
|
||||
django-encrypted-model-fields==0.6.5
|
||||
django-easy-audit==1.3.7s
|
||||
django-extensions==4.1
|
||||
django-filter==25.1
|
||||
django-js-asset==3.1.2
|
||||
|
||||
@ -252,7 +252,7 @@
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if user.is_staff %}
|
||||
{% comment %} {% if user.is_staff %}
|
||||
<div class="d-flex gap-2">
|
||||
<a href="{% url 'person_update' person.slug %}" class="btn btn-light">
|
||||
<i class="fas fa-edit me-1"></i> {% trans "Edit Applicant" %}
|
||||
@ -261,7 +261,7 @@
|
||||
<i class="fas fa-trash-alt me-1"></i> {% trans "Delete" %}
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %} {% endcomment %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -278,6 +278,14 @@
|
||||
<label for="{{ form.phone.id_for_label }}">{{ form.phone.label }}</label>
|
||||
{% include "bootstrap5/field.html" with field=form.phone %}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="{{ form.gpa.id_for_label }}">{{ form.gpa.label }}</label>
|
||||
{% include "bootstrap5/field.html" with field=form.gpa %}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="{{ form.national_id.id_for_label }}">{{ form.national_id.label }}</label>
|
||||
{% include "bootstrap5/field.html" with field=form.national_id %}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="{{ form.date_of_birth.id_for_label }}">{{ form.date_of_birth.label }}</label>
|
||||
{% include "bootstrap5/field.html" with field=form.date_of_birth %}
|
||||
|
||||
@ -326,6 +326,7 @@
|
||||
<div class="modal-body" id="personModalBody">
|
||||
<form id="person_form" hx-post="{% url 'person_create' %}" hx-vals='{"view":"portal","agency":"{{ agency.slug }}"}' hx-select=".persons-list" hx-target=".persons-list" hx-swap="outerHTML"
|
||||
hx-on:afterRequest="$('#personModal').modal('hide')">
|
||||
|
||||
{% csrf_token %}
|
||||
<div class="row g-4">
|
||||
<div class="col-md-4">
|
||||
@ -346,6 +347,14 @@
|
||||
{{ person_form.phone|as_crispy_field }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-4">
|
||||
<div class="col-md-6">
|
||||
{{ person_form.gpa|as_crispy_field }}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
{{ person_form.national_id|as_crispy_field }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-4">
|
||||
<div class="col-md-6">
|
||||
{{ person_form.date_of_birth|as_crispy_field }}
|
||||
|
||||
@ -106,6 +106,32 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4 mb-3">
|
||||
<label for="{{ form.gpa.id_for_label }}" class="form-label">
|
||||
{% trans "GPA" %} <span class="text-danger">*</span>
|
||||
</label>
|
||||
{{ form.gpa }}
|
||||
{% if form.gpa.errors %}
|
||||
<div class="text-danger small">
|
||||
{{ form.gpa.errors.0 }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="col-md-4 mb-3">
|
||||
<label for="{{ form.national_id.id_for_label }}" class="form-label">
|
||||
{% trans "National Id Or Iqama" %} <span class="text-danger">*</span>
|
||||
</label>
|
||||
{{ form.national_id }}
|
||||
{% if form.national_id.errors %}
|
||||
<div class="text-danger small">
|
||||
{{ form.national_id.errors.0 }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4 mb-3">
|
||||
<label for="{{ form.phone.id_for_label }}" class="form-label">
|
||||
|
||||
@ -1,68 +1,395 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static i18n %}
|
||||
|
||||
{% block title %}{% trans "Confirm Delete" %} - {{ block.super }}{% endblock %}
|
||||
{% block title %}{% trans "Delete Application" %} - {{ block.super }}{% endblock %}
|
||||
|
||||
{% block customCSS %}
|
||||
<style>
|
||||
/* KAAT-S UI Variables */
|
||||
:root {
|
||||
--kaauh-teal: #00636e;
|
||||
--kaauh-teal-dark: #004a53;
|
||||
--kaauh-border: #eaeff3;
|
||||
--kaauh-primary-text: #343a40;
|
||||
--kaauh-danger: #dc3545;
|
||||
--kaauh-warning: #ffc107;
|
||||
}
|
||||
|
||||
/* Main Container & Card Styling */
|
||||
.kaauh-card {
|
||||
border: 1px solid var(--kaauh-border);
|
||||
border-radius: 0.75rem;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
/* Warning Section */
|
||||
.warning-section {
|
||||
background: linear-gradient(135deg, #fff3cd 0%, #ffeeba 100%);
|
||||
border: 1px solid #ffeeba;
|
||||
border-radius: 0.75rem;
|
||||
padding: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.warning-icon {
|
||||
font-size: 4rem;
|
||||
color: var(--kaauh-warning);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.warning-title {
|
||||
color: #856404;
|
||||
font-weight: 700;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.warning-text {
|
||||
color: #856404;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* Info Card */
|
||||
.app-info {
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 0.75rem;
|
||||
padding: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
border: 1px solid var(--kaauh-border);
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 1rem;
|
||||
padding-bottom: 1rem;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.info-item:last-child {
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.info-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background-color: var(--kaauh-teal);
|
||||
color: white;
|
||||
border-radius: 0.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 1rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.info-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-weight: 600;
|
||||
color: var(--kaauh-primary-text);
|
||||
margin-bottom: 0.25rem;
|
||||
font-size: 0.875rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: #6c757d;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
/* Button Styling */
|
||||
.btn-danger {
|
||||
background-color: var(--kaauh-danger);
|
||||
border-color: var(--kaauh-danger);
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.btn-danger:hover {
|
||||
background-color: #c82333;
|
||||
border-color: #bd2130;
|
||||
box-shadow: 0 4px 8px rgba(220, 53, 69, 0.3);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: #6c757d;
|
||||
border-color: #6c757d;
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Consequence List */
|
||||
.consequence-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.consequence-list li {
|
||||
padding: 0.5rem 0;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.consequence-list li:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.consequence-list li i {
|
||||
color: var(--kaauh-danger);
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
/* Candidate Info Style */
|
||||
.candidate-avatar {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
border: 3px solid var(--kaauh-teal);
|
||||
}
|
||||
|
||||
.avatar-placeholder {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
background-color: #e9ecef;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 3px solid var(--kaauh-teal);
|
||||
}
|
||||
|
||||
/* Heroicon adjustments to match font-awesome size */
|
||||
.heroicon {
|
||||
width: 1.2em;
|
||||
height: 1.2em;
|
||||
vertical-align: -0.125em;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container my-5">
|
||||
<div class="container-fluid py-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h1 class="h3 mb-1" style="color: var(--kaauh-teal-dark); font-weight: 700;">
|
||||
<i class="fas fa-exclamation-triangle me-2"></i>
|
||||
{% trans "Delete Application" %}
|
||||
</h1>
|
||||
<p class="text-muted mb-0">
|
||||
{% trans "You are about to delete an Application record. This action cannot be undone." %}
|
||||
</p>
|
||||
</div>
|
||||
{# Assuming application_detail URL takes object.pk or object.slug #}
|
||||
<a href="{% url 'application_detail' object.pk %}" class="btn btn-secondary">
|
||||
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Application" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-8 col-xl-6">
|
||||
<div class="card border-danger shadow-lg">
|
||||
<div class="card-header bg-danger text-white">
|
||||
<h2 class="h4 mb-0 d-flex align-items-center">
|
||||
<svg class="heroicon me-2" viewBox="0 0 24 24" width="24" height="24" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M10 11V17"></path>
|
||||
<path d="M14 11V17"></path>
|
||||
<path d="M4 7H20"></path>
|
||||
<path d="M6 7H18V19C18 19.5304 17.7893 20.0391 17.4142 20.4142C17.0391 20.7893 16.5304 21 16 21H8C7.46957 21 6.96086 20.7893 6.58579 20.4142C6.21071 20.0391 6 19.5304 6 19V7Z"></path>
|
||||
<path d="M9 7V4C9 3.46957 9.21071 2.96086 9.58579 2.58579C9.96086 2.21071 10.4696 2 11 2H13C13.5304 2 14.0391 2.21071 14.4142 2.58579C14.7893 2.96086 15 3.46957 15 4V7"></path>
|
||||
</svg>
|
||||
{% trans "Confirm Deletion" %}
|
||||
</h2>
|
||||
<div class="col-lg-8">
|
||||
<div class="warning-section">
|
||||
<div class="warning-icon">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
</div>
|
||||
<h3 class="warning-title">{% trans "Warning: This action cannot be undone!" %}</h3>
|
||||
<p class="warning-text">
|
||||
{% blocktrans with candidate_name=object.candidate.full_name job_title=object.job.title %}
|
||||
Deleting the application submitted by **{{ candidate_name }}** for the job **{{ job_title }}** will permanently remove all associated data. Please review the information below carefully before proceeding.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="card kaauh-card mb-4">
|
||||
<div class="card-header bg-white border-bottom">
|
||||
<h5 class="mb-0" style="color: var(--kaauh-teal-dark);">
|
||||
<i class="fas fa-file-alt me-2"></i>
|
||||
{% trans "Application to be Deleted" %}
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="lead text-danger fw-bold">
|
||||
{% trans "You are about to permanently delete the following Application." %}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{% blocktrans with candidate_name=object.candidate.full_name job_title=object.job.title %}
|
||||
**Are you absolutely sure** you want to delete the application submitted by **{{ object}}** ?
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
|
||||
<blockquote class="blockquote border-start border-danger border-4 ps-3 py-2 bg-light rounded-end">
|
||||
<h5 class="mb-1 text-dark">{% trans "Application Details" %}</h5>
|
||||
<div class="app-info">
|
||||
{% if object.candidate %}
|
||||
<p class="mb-0"><strong>{% trans "Candidate:" %}</strong> {{ object.candidate.full_name }}</p>
|
||||
<div class="d-flex align-items-center mb-4">
|
||||
{# Assuming candidate has a profile_image field #}
|
||||
{% if object.candidate.profile_image %}
|
||||
<img src="{{ object.candidate.profile_image.url }}" alt="{{ object.candidate.full_name }}" class="candidate-avatar me-3">
|
||||
{% else %}
|
||||
<div class="avatar-placeholder me-3">
|
||||
<i class="fas fa-user text-muted fa-2x"></i>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div>
|
||||
<h4 class="mb-1">{{ object.candidate.full_name }}</h4>
|
||||
{% if object.candidate.email %}
|
||||
<p class="text-muted mb-0">{{ object.candidate.email }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if object.job %}
|
||||
<p class="mb-0"><strong>{% trans "Job Title:" %}</strong> {{ object.job.title }}</p>
|
||||
{% endif %}
|
||||
<p class="mb-0"><strong>{% trans "Applied On:" %}</strong> {{ object.created_at|date:"M d, Y \a\t P" }}</p>
|
||||
</blockquote>
|
||||
|
||||
<p class="mt-4 text-muted">
|
||||
{% trans "This action is **irreversible** and all associated data will be lost." %}
|
||||
</p>
|
||||
|
||||
{% if object.job %}
|
||||
<div class="info-item">
|
||||
<div class="info-icon">
|
||||
<i class="fas fa-briefcase"></i>
|
||||
</div>
|
||||
<div class="info-content">
|
||||
<div class="info-label">{% trans "Job Title" %}</div>
|
||||
<div class="info-value">{{ object.job.title }}</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="info-item">
|
||||
<div class="info-icon">
|
||||
<i class="fas fa-calendar-alt"></i>
|
||||
</div>
|
||||
<div class="info-content">
|
||||
<div class="info-label">{% trans "Applied On" %}</div>
|
||||
<div class="info-value">{{ object.created_at|date:"F d, Y \a\t P" }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if object.status %}
|
||||
<div class="info-item">
|
||||
<div class="info-icon">
|
||||
<i class="fas fa-cogs"></i>
|
||||
</div>
|
||||
<div class="info-content">
|
||||
<div class="info-label">{% trans "Current Status" %}</div>
|
||||
<div class="info-value">{{ object.get_status_display }}</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer d-flex justify-content-between align-items-center">
|
||||
<a href="{% url 'application_list' %}" class="btn btn-secondary">
|
||||
{% trans "Cancel" %}
|
||||
</a>
|
||||
<form method="post">
|
||||
</div>
|
||||
|
||||
<div class="card kaauh-card mb-4">
|
||||
<div class="card-header bg-white border-bottom">
|
||||
<h5 class="mb-0" style="color: var(--kaauh-teal-dark);">
|
||||
<i class="fas fa-list me-2"></i>
|
||||
{% trans "What will happen when you delete this Application?" %}
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<ul class="consequence-list">
|
||||
<li>
|
||||
<i class="fas fa-times-circle"></i>
|
||||
{% trans "The Application record and all status history will be permanently deleted." %}
|
||||
</li>
|
||||
<li>
|
||||
<i class="fas fa-times-circle"></i>
|
||||
{% trans "All associated screening results, scores, and evaluations will be removed." %}
|
||||
</li>
|
||||
<li>
|
||||
<i class="fas fa-times-circle"></i>
|
||||
{% trans "Any linked documents (CV, cover letter) specific to this application will be lost." %}
|
||||
</li>
|
||||
<li>
|
||||
<i class="fas fa-times-circle"></i>
|
||||
{% trans "The Candidate will still exist, but this specific application history will be lost." %}
|
||||
</li>
|
||||
<li>
|
||||
<i class="fas fa-times-circle"></i>
|
||||
{% trans "This action cannot be undone under any circumstances." %}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card kaauh-card">
|
||||
<div class="card-body">
|
||||
<form method="post" id="deleteForm">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="btn btn-danger d-flex align-items-center">
|
||||
<svg class="heroicon me-1" viewBox="0 0 24 24" width="20" height="20" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
|
||||
</svg>
|
||||
{% trans "Yes, Permanently Delete" %}
|
||||
</button>
|
||||
|
||||
<div class="mb-4">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="confirm_delete" name="confirm_delete" required>
|
||||
<label class="form-check-label" for="confirm_delete">
|
||||
<strong>{% trans "I understand that this action cannot be undone and I want to permanently delete this application." %}</strong>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between">
|
||||
<a href="{% url 'application_detail' object.pk %}" class="btn btn-secondary btn-lg">
|
||||
<i class="fas fa-times me-2"></i>
|
||||
{% trans "Cancel" %}
|
||||
</a>
|
||||
<button type="submit"
|
||||
class="btn btn-danger btn-lg"
|
||||
id="deleteButton"
|
||||
disabled>
|
||||
<i class="fas fa-trash me-2"></i>
|
||||
{% trans "Delete Application Permanently" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const confirmDeleteCheckbox = document.getElementById('confirm_delete');
|
||||
const deleteButton = document.getElementById('deleteButton');
|
||||
const deleteForm = document.getElementById('deleteForm');
|
||||
|
||||
function validateForm() {
|
||||
const checkboxChecked = confirmDeleteCheckbox.checked;
|
||||
deleteButton.disabled = !checkboxChecked;
|
||||
|
||||
// Toggle button classes for visual feedback
|
||||
if (checkboxChecked) {
|
||||
deleteButton.classList.remove('btn-secondary');
|
||||
deleteButton.classList.add('btn-danger');
|
||||
} else {
|
||||
deleteButton.classList.remove('btn-danger');
|
||||
deleteButton.classList.add('btn-secondary');
|
||||
}
|
||||
}
|
||||
|
||||
confirmDeleteCheckbox.addEventListener('change', validateForm);
|
||||
// Initialize state on page load
|
||||
validateForm();
|
||||
|
||||
// Add confirmation and prevent double submission before final submission
|
||||
deleteForm.addEventListener('submit', function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
const candidateName = "{{ object.candidate.full_name|escapejs }}";
|
||||
const jobTitle = "{{ object.job.title|escapejs }}";
|
||||
|
||||
// Construct a confirmation message using string replacement for safety
|
||||
const confirmationMessageTemplate = "{% blocktrans with candidate_name='CANDIDATE_PLACEHOLDER' job_title='JOB_PLACEHOLDER' %}Are you absolutely sure you want to permanently delete the application by CANDIDATE_PLACEHOLDER for JOB_PLACEHOLDER? This action cannot be reversed!{% endblocktrans %}";
|
||||
|
||||
const confirmationMessage = confirmationMessageTemplate
|
||||
.replace('CANDIDATE_PLACEHOLDER', candidateName)
|
||||
.replace('JOB_PLACEHOLDER', jobTitle);
|
||||
|
||||
if (confirm(confirmationMessage)) {
|
||||
// Disable button and show loading state
|
||||
deleteButton.disabled = true;
|
||||
deleteButton.innerHTML = '<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>{% trans "Deleting..." %}';
|
||||
|
||||
// Submit the form programmatically
|
||||
deleteForm.submit();
|
||||
} else {
|
||||
// If the user cancels the dialog, ensure the button state is reset/validated
|
||||
validateForm();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
Loading…
x
Reference in New Issue
Block a user