Compare commits
No commits in common. "13687718967e391e76c648bfb88b0f37c3680145" and "a181e845698769d2dc6980ec56e9aa7fd0df8502" have entirely different histories.
1368771896
...
a181e84569
Binary file not shown.
@ -155,27 +155,19 @@ DATABASES = {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
# AUTH_PASSWORD_VALIDATORS = [
|
|
||||||
# {
|
|
||||||
# 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
|
||||||
# },
|
|
||||||
# ]
|
|
||||||
|
|
||||||
# settings.py
|
|
||||||
|
|
||||||
AUTH_PASSWORD_VALIDATORS = [
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,7 +1,3 @@
|
|||||||
from pathlib import Path
|
|
||||||
from rich import print
|
|
||||||
from django.conf import settings
|
|
||||||
import os
|
|
||||||
import uuid
|
import uuid
|
||||||
import random
|
import random
|
||||||
from datetime import date, timedelta
|
from datetime import date, timedelta
|
||||||
@ -43,9 +39,9 @@ class Command(BaseCommand):
|
|||||||
self.stdout.write(f"Preparing to create {jobs_count} jobs and {candidates_count} candidates.")
|
self.stdout.write(f"Preparing to create {jobs_count} jobs and {candidates_count} candidates.")
|
||||||
|
|
||||||
# 1. Clear existing data (Optional, but useful for clean seeding)
|
# 1. Clear existing data (Optional, but useful for clean seeding)
|
||||||
JobPosting.objects.all().delete()
|
|
||||||
FormTemplate.objects.all().delete()
|
|
||||||
Candidate.objects.all().delete()
|
Candidate.objects.all().delete()
|
||||||
|
JobPosting.objects.all().delete()
|
||||||
|
Source.objects.all().delete()
|
||||||
self.stdout.write(self.style.WARNING("Existing JobPostings and Candidates cleared."))
|
self.stdout.write(self.style.WARNING("Existing JobPostings and Candidates cleared."))
|
||||||
|
|
||||||
# 2. Create Foreign Key dependency: Source
|
# 2. Create Foreign Key dependency: Source
|
||||||
@ -79,6 +75,7 @@ class Command(BaseCommand):
|
|||||||
# Random dates
|
# Random dates
|
||||||
start_date = fake.date_object()
|
start_date = fake.date_object()
|
||||||
deadline_date = start_date + timedelta(days=random.randint(14, 60))
|
deadline_date = start_date + timedelta(days=random.randint(14, 60))
|
||||||
|
joining_date = deadline_date + timedelta(days=random.randint(30, 90))
|
||||||
|
|
||||||
# Use Faker's HTML generation for CKEditor5 fields
|
# Use Faker's HTML generation for CKEditor5 fields
|
||||||
description_html = f"<h1>{title} Role</h1>" + "".join(f"<p>{fake.paragraph(nb_sentences=3, variable_nb_sentences=True)}</p>" for _ in range(3))
|
description_html = f"<h1>{title} Role</h1>" + "".join(f"<p>{fake.paragraph(nb_sentences=3, variable_nb_sentences=True)}</p>" for _ in range(3))
|
||||||
@ -91,11 +88,26 @@ class Command(BaseCommand):
|
|||||||
"department": department,
|
"department": department,
|
||||||
"job_type": job_type,
|
"job_type": job_type,
|
||||||
"workplace_type": random.choice(WORKPLACE_TYPES),
|
"workplace_type": random.choice(WORKPLACE_TYPES),
|
||||||
|
"location_city": fake.city(),
|
||||||
|
"location_state": fake.state_abbr(),
|
||||||
"location_country": "Saudia Arabia",
|
"location_country": "Saudia Arabia",
|
||||||
"description": description_html,
|
"description": description_html,
|
||||||
"qualifications": qualifications_html,
|
"qualifications": qualifications_html,
|
||||||
|
"salary_range": salary_range,
|
||||||
|
"benefits": benefits_html,
|
||||||
|
"application_url": fake.url(),
|
||||||
|
"application_start_date": start_date,
|
||||||
"application_deadline": deadline_date,
|
"application_deadline": deadline_date,
|
||||||
|
"application_instructions": instructions_html,
|
||||||
|
"created_by": "Faker Script",
|
||||||
"status": random.choice(STATUS_CHOICES),
|
"status": random.choice(STATUS_CHOICES),
|
||||||
|
"hash_tags": f"#{department.lower().replace(' ', '')},#jobopening,#{fake.word()}",
|
||||||
|
"position_number": f"{department[:3].upper()}{random.randint(100, 999)}",
|
||||||
|
"reporting_to": random.choice(REPORTING_TO),
|
||||||
|
"joining_date": joining_date,
|
||||||
|
"open_positions": random.randint(1, 5),
|
||||||
|
"source": default_source,
|
||||||
|
"published_at": timezone.now() if random.random() < 0.7 else None,
|
||||||
}
|
}
|
||||||
|
|
||||||
job = JobPosting.objects.create(
|
job = JobPosting.objects.create(
|
||||||
@ -111,33 +123,28 @@ class Command(BaseCommand):
|
|||||||
for i in range(candidates_count):
|
for i in range(candidates_count):
|
||||||
# Link candidate to a random job
|
# Link candidate to a random job
|
||||||
target_job = random.choice(created_jobs)
|
target_job = random.choice(created_jobs)
|
||||||
print(target_job)
|
|
||||||
first_name = fake.first_name()
|
first_name = fake.first_name()
|
||||||
last_name = fake.last_name()
|
last_name = fake.last_name()
|
||||||
path = os.path.join(settings.BASE_DIR,'media/resumes/')
|
|
||||||
|
|
||||||
# path = Path('media/resumes/') # <-- CORRECT
|
|
||||||
file = random.choice(os.listdir(path))
|
|
||||||
print(file)
|
|
||||||
# file = os.path.abspath(file)
|
|
||||||
candidate_data = {
|
candidate_data = {
|
||||||
"first_name": first_name,
|
"first_name": first_name,
|
||||||
"last_name": last_name,
|
"last_name": last_name,
|
||||||
# Create a plausible email based on name
|
# Create a plausible email based on name
|
||||||
"email": f"{first_name.lower()}.{last_name.lower()}@{fake.domain_name()}",
|
"email": f"{first_name.lower()}.{last_name.lower()}@{fake.domain_name()}",
|
||||||
"phone": "0566987458",
|
"phone": fake.phone_number(),
|
||||||
"address": fake.address(),
|
"address": fake.address(),
|
||||||
# Placeholder resume path
|
# Placeholder resume path
|
||||||
"resume": 'resumes/'+ file,
|
'match_score': random.randint(0, 100),
|
||||||
|
"resume": f"resumes/{last_name.lower()}_{target_job.internal_job_id}_{fake.file_name(extension='pdf')}",
|
||||||
"job": target_job,
|
"job": target_job,
|
||||||
}
|
}
|
||||||
print(candidate_data)
|
|
||||||
|
|
||||||
Candidate.objects.create(**candidate_data)
|
Candidate.objects.create(**candidate_data)
|
||||||
self.stdout.write(self.style.NOTICE(
|
self.stdout.write(self.style.NOTICE(
|
||||||
f'Created Candidate {i+1}/{candidates_count}: {first_name} for {target_job.title[:30]}...'
|
f'Created Candidate {i+1}/{candidates_count}: {first_name} for {target_job.title[:30]}...'
|
||||||
))
|
))
|
||||||
print("done")
|
|
||||||
else:
|
else:
|
||||||
self.stdout.write(self.style.WARNING("No jobs created, skipping candidate generation."))
|
self.stdout.write(self.style.WARNING("No jobs created, skipping candidate generation."))
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-10-23 14:08
|
# Generated by Django 5.2.7 on 2025-10-22 16:33
|
||||||
|
|
||||||
import django.core.validators
|
import django.core.validators
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
@ -221,7 +221,6 @@ class Migration(migrations.Migration):
|
|||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='JobPosting',
|
name='JobPosting',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')),
|
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')),
|
||||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')),
|
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')),
|
||||||
('slug', django_extensions.db.fields.RandomCharField(blank=True, editable=False, length=8, unique=True, verbose_name='Slug')),
|
('slug', django_extensions.db.fields.RandomCharField(blank=True, editable=False, length=8, unique=True, verbose_name='Slug')),
|
||||||
@ -239,7 +238,7 @@ class Migration(migrations.Migration):
|
|||||||
('application_url', models.URLField(blank=True, help_text='URL where candidates apply', null=True, validators=[django.core.validators.URLValidator()])),
|
('application_url', models.URLField(blank=True, help_text='URL where candidates apply', null=True, validators=[django.core.validators.URLValidator()])),
|
||||||
('application_deadline', models.DateField(db_index=True)),
|
('application_deadline', models.DateField(db_index=True)),
|
||||||
('application_instructions', django_ckeditor_5.fields.CKEditor5Field(blank=True, null=True)),
|
('application_instructions', django_ckeditor_5.fields.CKEditor5Field(blank=True, null=True)),
|
||||||
('internal_job_id', models.CharField(editable=False, max_length=50)),
|
('internal_job_id', models.CharField(editable=False, max_length=50, primary_key=True, serialize=False)),
|
||||||
('created_by', models.CharField(blank=True, help_text='Name of person who created this job', max_length=100)),
|
('created_by', models.CharField(blank=True, help_text='Name of person who created this job', max_length=100)),
|
||||||
('status', models.CharField(choices=[('DRAFT', 'Draft'), ('ACTIVE', 'Active'), ('CLOSED', 'Closed'), ('CANCELLED', 'Cancelled'), ('ARCHIVED', 'Archived')], db_index=True, default='DRAFT', max_length=20)),
|
('status', models.CharField(choices=[('DRAFT', 'Draft'), ('ACTIVE', 'Active'), ('CLOSED', 'Closed'), ('CANCELLED', 'Cancelled'), ('ARCHIVED', 'Archived')], db_index=True, default='DRAFT', max_length=20)),
|
||||||
('hash_tags', models.CharField(blank=True, help_text='Comma-separated hashtags for linkedin post like #hiring,#jobopening', max_length=200, validators=[recruitment.validators.validate_hash_tags])),
|
('hash_tags', models.CharField(blank=True, help_text='Comma-separated hashtags for linkedin post like #hiring,#jobopening', max_length=200, validators=[recruitment.validators.validate_hash_tags])),
|
||||||
|
|||||||
@ -92,7 +92,7 @@ class JobPosting(Base):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Internal Tracking
|
# Internal Tracking
|
||||||
internal_job_id = models.CharField(max_length=50, editable=False)
|
internal_job_id = models.CharField(max_length=50, primary_key=True, editable=False)
|
||||||
created_by = models.CharField(
|
created_by = models.CharField(
|
||||||
max_length=100, blank=True, help_text="Name of person who created this job"
|
max_length=100, blank=True, help_text="Name of person who created this job"
|
||||||
)
|
)
|
||||||
@ -193,29 +193,26 @@ class JobPosting(Base):
|
|||||||
return self.source.name if self.source else "System"
|
return self.source.name if self.source else "System"
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
# from django.db import transaction
|
|
||||||
|
|
||||||
# Generate unique internal job ID if not exists
|
# Generate unique internal job ID if not exists
|
||||||
# with transaction.atomic():
|
if not self.internal_job_id:
|
||||||
# if not self.internal_job_id:
|
prefix = "KAAUH"
|
||||||
# prefix = "KAAUH"
|
year = timezone.now().year
|
||||||
# year = timezone.now().year
|
# Get next sequential number
|
||||||
# # Get next sequential number
|
last_job = (
|
||||||
# last_job = (
|
JobPosting.objects.filter(
|
||||||
# JobPosting.objects.select_for_update().filter(
|
internal_job_id__startswith=f"{prefix}-{year}-"
|
||||||
# internal_job_id__startswith=f"{prefix}-{year}-"
|
)
|
||||||
# )
|
.order_by("internal_job_id")
|
||||||
# .order_by("internal_job_id")
|
.last()
|
||||||
# .last()
|
)
|
||||||
# )
|
|
||||||
|
|
||||||
# if last_job:
|
if last_job:
|
||||||
# last_num = int(last_job.internal_job_id.split("-")[-1])
|
last_num = int(last_job.internal_job_id.split("-")[-1])
|
||||||
# next_num = last_num + 1
|
next_num = last_num + 1
|
||||||
# else:
|
else:
|
||||||
# next_num = 1
|
next_num = 1
|
||||||
|
|
||||||
# self.internal_job_id = f"{prefix}-{year}-{next_num:06d}"
|
self.internal_job_id = f"{prefix}-{year}-{next_num:06d}"
|
||||||
|
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
|||||||
@ -13,7 +13,7 @@ logger = logging.getLogger(__name__)
|
|||||||
@receiver(post_save, sender=JobPosting)
|
@receiver(post_save, sender=JobPosting)
|
||||||
def format_job(sender, instance, created, **kwargs):
|
def format_job(sender, instance, created, **kwargs):
|
||||||
if created:
|
if created:
|
||||||
# FormTemplate.objects.create(job=instance, is_active=False, name=instance.title)
|
FormTemplate.objects.create(job=instance, is_active=True, name=instance.title)
|
||||||
async_task(
|
async_task(
|
||||||
'recruitment.tasks.format_job_description',
|
'recruitment.tasks.format_job_description',
|
||||||
instance.pk,
|
instance.pk,
|
||||||
|
|||||||
@ -290,7 +290,7 @@ def handle_reume_parsing_and_scoring(pk):
|
|||||||
"strengths": "Brief summary of strengths",
|
"strengths": "Brief summary of strengths",
|
||||||
"weaknesses": "Brief summary of weaknesses",
|
"weaknesses": "Brief summary of weaknesses",
|
||||||
"years_of_experience": "Total years of experience (float, e.g., 6.5)",
|
"years_of_experience": "Total years of experience (float, e.g., 6.5)",
|
||||||
"criteria_checklist": List of job requirements if any {{ "Python": "Met", "AWS": "Not Met"}} only output the criteria_checklist in one of ('Met','Not Met') don't output any extra text,
|
"criteria_checklist": {{ "Python": "Met", "AWS": "Not Mentioned"}},
|
||||||
"category": "Most fitting professional field (e.g., Data Science), only output the category name and no other text example ('Software Development', 'correct') , ('Software Development and devops','wrong') ('Software Development / Backend Development','wrong')",
|
"category": "Most fitting professional field (e.g., Data Science), only output the category name and no other text example ('Software Development', 'correct') , ('Software Development and devops','wrong') ('Software Development / Backend Development','wrong')",
|
||||||
"most_recent_job_title": "Candidate's most recent job title",
|
"most_recent_job_title": "Candidate's most recent job title",
|
||||||
"recommendation": "Detailed hiring recommendation narrative",
|
"recommendation": "Detailed hiring recommendation narrative",
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
resumes/cv.pdf
BIN
resumes/cv.pdf
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -111,31 +111,12 @@
|
|||||||
|
|
||||||
{# ------------------- ADD EMAIL FORM ------------------- #}
|
{# ------------------- ADD EMAIL FORM ------------------- #}
|
||||||
<h5 class="fw-bold mb-3">{% translate "Add Email Address" %}</h5>
|
<h5 class="fw-bold mb-3">{% translate "Add Email Address" %}</h5>
|
||||||
<form method="post" action="{% url 'account_email' %}">
|
<form method="post" action="{% url 'account_email' %}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
{{ form|crispy }}
|
||||||
{# 1. Explicitly render non-field errors first #}
|
{# Teal/Dark Green button consistent with "Save Changes" #}
|
||||||
{% if form.non_field_errors %}
|
<button class="btn btn-success mt-3" type="submit" style="background-color: #008080; border-color: #008080;">{% translate "Add Email" %}</button>
|
||||||
<div class="alert alert-danger" role="alert">
|
</form>
|
||||||
{% for error in form.non_field_errors %}
|
|
||||||
{{ error }}
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{# 2. Render the fields using crispy #}
|
|
||||||
{{ form|crispy }}
|
|
||||||
|
|
||||||
{# 3. If there are any global errors (not common here, but safe to include) #}
|
|
||||||
{% if messages %}
|
|
||||||
{% for message in messages %}
|
|
||||||
<div class="alert alert-{{ message.tags }} mt-3">{{ message }}</div>
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{# Teal/Dark Green button consistent with "Save Changes" #}
|
|
||||||
<button class="btn btn-success mt-3" type="submit" style="background-color: #008080; border-color: #008080;">{% translate "Add Email" %}</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -2,8 +2,6 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
{% autoescape off %}
|
{% autoescape off %}
|
||||||
|
|
||||||
{# Use the built-in context variable 'password_reset_url' that allauth provides. #}
|
|
||||||
|
|
||||||
<div style="font-family: Arial, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: auto; border: 1px solid #ddd; border-radius: 8px; overflow: hidden;">
|
<div style="font-family: Arial, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: auto; border: 1px solid #ddd; border-radius: 8px; overflow: hidden;">
|
||||||
|
|
||||||
<div style="background-color: #00636e; padding: 20px; color: white; text-align: center;">
|
<div style="background-color: #00636e; padding: 20px; color: white; text-align: center;">
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{% load static i18n %}
|
{% load static i18n %}
|
||||||
{% load crispy_forms_tags %} <!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
@ -82,8 +82,52 @@
|
|||||||
<form method="post" action=".">
|
<form method="post" action=".">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
||||||
{# RENDER THE FORM USING CRISPY FORMS #}
|
{# Non-Field Errors (General errors like tokens or passwords not matching) #}
|
||||||
{{ form|crispy }}
|
{% if form.non_field_errors %}
|
||||||
|
<div class="alert alert-danger" role="alert">
|
||||||
|
{% for error in form.non_field_errors %}
|
||||||
|
{{ error }}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# Password 1 Field #}
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="{{ form.password.id_for_label }}" class="form-label fw-semibold">{% trans "New Password" %} *</label>
|
||||||
|
|
||||||
|
{# **CRITICAL FIX:** Iterate over the errors to display them correctly #}
|
||||||
|
{% if form.password.errors %}
|
||||||
|
<div class="alert alert-danger p-2 small">
|
||||||
|
{% for error in form.password.errors %}
|
||||||
|
{{ error }}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<input type="password" name="{{ form.password.name }}" id="{{ form.password.id_for_label }}"
|
||||||
|
class="form-control" placeholder="{% trans 'Enter new password' %}" required autofocus>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# Password 2 Field #}
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="{{ form.password2.id_for_label }}" class="form-label fw-semibold">{% trans "Confirm New Password" %} *</label>
|
||||||
|
|
||||||
|
{# **CRITICAL FIX:** Iterate over the errors to display them correctly #}
|
||||||
|
{% if form.password2.errors %}
|
||||||
|
<div class="alert alert-danger p-2 small">
|
||||||
|
{% for error in form.password2.errors %}
|
||||||
|
{{ error }}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<input type="password" name="{{ form.password2.name }}" id="{{ form.password2.id_for_label }}"
|
||||||
|
class="form-control" placeholder="{% trans 'Confirm new password' %}" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# Hidden fields MUST be present for the POST request to be valid #}
|
||||||
|
{{ form.uid }}
|
||||||
|
{{ form.token }}
|
||||||
|
|
||||||
{# Submit Button #}
|
{# Submit Button #}
|
||||||
<button type="submit" class="btn btn-primary w-100 mt-4">
|
<button type="submit" class="btn btn-primary w-100 mt-4">
|
||||||
|
|||||||
@ -146,12 +146,9 @@
|
|||||||
<div class="container-fluid py-4">
|
<div class="container-fluid py-4">
|
||||||
<nav aria-label="breadcrumb">
|
<nav aria-label="breadcrumb">
|
||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<li class="breadcrumb-item text-decorat"><a href="{% url 'job_detail' submission.template.job.slug %}" class="text-secondary">Job Detail</a></li>
|
<li class="breadcrumb-item"><a href="{% url 'job_detail' submission.template.job.slug %}">Job Detail</a></li>
|
||||||
<li class="breadcrumb-item"><a href="{% url 'form_builder' submission.template.pk%}" class="text-secondary">Form Template</a></li>
|
<li class="breadcrumb-item"><a href="{% url 'form_builder' submission.template.pk%}">Form Template</a></li>
|
||||||
<li class="breadcrumb-item active" aria-current="page" style="
|
<li class="breadcrumb-item active" aria-current="page">Submission Details</li>
|
||||||
color: #F43B5E; /* Rosy Accent Color */
|
|
||||||
font-weight: 600;
|
|
||||||
">Submission Details</li>
|
|
||||||
</ol>
|
</ol>
|
||||||
</nav>
|
</nav>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|||||||
@ -185,12 +185,9 @@
|
|||||||
<div class="container py-4">
|
<div class="container py-4">
|
||||||
<nav aria-label="breadcrumb">
|
<nav aria-label="breadcrumb">
|
||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<li class="breadcrumb-item"><a href="{% url 'dashboard' %}" class="text-secondary">{% trans "Dashboard" %}</a></li>
|
<li class="breadcrumb-item"><a href="{% url 'dashboard' %}">{% trans "Dashboard" %}</a></li>
|
||||||
<li class="breadcrumb-item"><a href="{% url 'form_templates_list' %}" class="text-secondary">{% trans "Form Templates" %}</a></li>
|
<li class="breadcrumb-item"><a href="{% url 'form_templates_list' %}">{% trans "Form Templates" %}</a></li>
|
||||||
<li class="breadcrumb-item active" style="
|
<li class="breadcrumb-item active">{% trans "Submissions" %}</li>
|
||||||
color: #F43B5E; /* Rosy Accent Color */
|
|
||||||
font-weight: 600;
|
|
||||||
">{% trans "Submissions" %}</li>
|
|
||||||
</ol>
|
</ol>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
|||||||
@ -364,7 +364,6 @@
|
|||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<div class="resume-container">
|
<div class="resume-container">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<div class="header-content">
|
<div class="header-content">
|
||||||
|
|||||||
@ -152,10 +152,7 @@
|
|||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<li class="breadcrumb-item"><a href="{% url 'dashboard' %}" class="text-secondary">Home</a></li>
|
<li class="breadcrumb-item"><a href="{% url 'dashboard' %}" class="text-secondary">Home</a></li>
|
||||||
<li class="breadcrumb-item"><a href="{% url 'job_list' %}" class="text-secondary">Jobs</a></li>
|
<li class="breadcrumb-item"><a href="{% url 'job_list' %}" class="text-secondary">Jobs</a></li>
|
||||||
<li class="breadcrumb-item active" aria-current="page" style="
|
<li class="breadcrumb-item active" aria-current="page">Job Detail</li>
|
||||||
color: #F43B5E; /* Rosy Accent Color */
|
|
||||||
font-weight: 600;
|
|
||||||
">Job Detail</li>
|
|
||||||
</ol>
|
</ol>
|
||||||
</nav>
|
</nav>
|
||||||
<div class="row g-4">
|
<div class="row g-4">
|
||||||
|
|||||||
@ -186,10 +186,7 @@
|
|||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<li class="breadcrumb-item"><a href="{% url 'dashboard' %}" class="text-secondary">Home</a></li>
|
<li class="breadcrumb-item"><a href="{% url 'dashboard' %}" class="text-secondary">Home</a></li>
|
||||||
<li class="breadcrumb-item"><a href="{% url 'job_detail' candidate.job.slug %}" class="text-secondary">Job:({{candidate.job.title}})</a></li>
|
<li class="breadcrumb-item"><a href="{% url 'job_detail' candidate.job.slug %}" class="text-secondary">Job:({{candidate.job.title}})</a></li>
|
||||||
<li class="breadcrumb-item active" aria-current="page" class="text-secondary" style="
|
<li class="breadcrumb-item active" aria-current="page" class="text-secondary">Applicant Detail</li>
|
||||||
color: #F43B5E; /* Rosy Accent Color */
|
|
||||||
font-weight: 600;
|
|
||||||
">Applicant Detail</li>
|
|
||||||
</ol>
|
</ol>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
{% load i18n %}
|
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
@ -518,10 +517,7 @@
|
|||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-kaauh-light-bg font-sans">
|
<body class="bg-kaauh-light-bg font-sans">
|
||||||
|
|
||||||
<div class="container container-fluid flex-grow-1" style="max-width: 1600px; margin: 0 auto;">
|
<div class="container container-fluid flex-grow-1" style="max-width: 1600px; margin: 0 auto;">
|
||||||
|
|
||||||
{% include 'recruitment/partials/ai_overview_breadcromb.html' %}
|
|
||||||
<!-- Header Section -->
|
<!-- Header Section -->
|
||||||
<header class="header-box">
|
<header class="header-box">
|
||||||
<div class="header-info">
|
<div class="header-info">
|
||||||
@ -720,9 +716,21 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
<div style="margin-bottom: 1rem;">
|
||||||
|
<div class="analysis-metric" style="margin-bottom: 0.5rem; border-bottom: none;">
|
||||||
|
<span class="metric-title">Match Score</span>
|
||||||
|
<span class="metric-value">{{ candidate.analysis_data.match_score|default:0 }}/100</span>
|
||||||
|
</div>
|
||||||
|
<div class="progress-container">
|
||||||
|
<div class="progress-bar progress-bar-animated"
|
||||||
|
style="width: {{ candidate.analysis_data.match_score|default:0 }}%; background-color:
|
||||||
|
{% if candidate.analysis_data.match_score|default:0 < 50 %}var(--score-red){% elif candidate.analysis_data.match_score|default:0 < 75 %}var(--score-yellow){% else %}var(--score-green){% endif %}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% if candidate.analysis_data.red_flags %}
|
{% if candidate.analysis_data.red_flags %}
|
||||||
<div class="narrative-box">
|
<div class="narrative-box red-flag-box">
|
||||||
<h3 class="flag-title red"><i class="fas fa-flag"></i>Red Flags</h3>
|
<h3 class="flag-title red"><i class="fas fa-flag"></i>Red Flags</h3>
|
||||||
<!-- scoring_data.red_flags -->
|
<!-- scoring_data.red_flags -->
|
||||||
<p class="narrative-text">{{ candidate.analysis_data.red_flags|join:". "|default:"None." }}</p>
|
<p class="narrative-text">{{ candidate.analysis_data.red_flags|join:". "|default:"None." }}</p>
|
||||||
|
|||||||
@ -1,118 +0,0 @@
|
|||||||
{% load i18n %}
|
|
||||||
{% with request.resolver_match as resolved %}
|
|
||||||
{% if 'resume-template' in resolved.route and resolved.kwargs.slug == candidate.slug %}
|
|
||||||
<nav aria-label="breadcrumb" style="font-family: 'Inter', sans-serif; margin-bottom:1.5rem;">
|
|
||||||
<ol style="
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
list-style: none;
|
|
||||||
font-size: 0.875rem; /* text-sm */
|
|
||||||
">
|
|
||||||
|
|
||||||
<li style="display: flex; align-items: center;">
|
|
||||||
<a
|
|
||||||
href="{% url 'dashboard' %}"
|
|
||||||
style="
|
|
||||||
color: #6c757d; /* text-secondary/gray */
|
|
||||||
text-decoration: none;
|
|
||||||
transition: color 0.15s ease-in-out;
|
|
||||||
"
|
|
||||||
onmouseover="this.style.color='#000000';"
|
|
||||||
onmouseout="this.style.color='#6c757d';"
|
|
||||||
>
|
|
||||||
{% translate 'Home' %}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li style="
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding-left: 0.5rem;
|
|
||||||
padding-right: 0.5rem;
|
|
||||||
">
|
|
||||||
<span style="color: #6c757d; opacity: 0.75;" aria-hidden="true">/</span>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li style="display: flex; align-items: center;">
|
|
||||||
<a
|
|
||||||
href="{% url 'job_list' %}"
|
|
||||||
style="
|
|
||||||
color: #6c757d; /* text-secondary/gray */
|
|
||||||
text-decoration: none;
|
|
||||||
transition: color 0.15s ease-in-out;
|
|
||||||
"
|
|
||||||
onmouseover="this.style.color='#000000';"
|
|
||||||
onmouseout="this.style.color='#6c757d';"
|
|
||||||
>
|
|
||||||
{% trans 'Jobs' %}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li style="
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding-left: 0.5rem;
|
|
||||||
padding-right: 0.5rem;
|
|
||||||
">
|
|
||||||
<span style="color: #6c757d; opacity: 0.75;" aria-hidden="true">/</span>
|
|
||||||
</li>
|
|
||||||
<li style="display: flex; align-items: center;">
|
|
||||||
<a
|
|
||||||
href="{% url 'candidate_list' %}"
|
|
||||||
style="
|
|
||||||
color: #6c757d; /* text-secondary/gray */
|
|
||||||
text-decoration: none;
|
|
||||||
transition: color 0.15s ease-in-out;
|
|
||||||
"
|
|
||||||
onmouseover="this.style.color='#000000';"
|
|
||||||
onmouseout="this.style.color='#6c757d';"
|
|
||||||
>
|
|
||||||
{% trans 'Applicants' %}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li style="
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding-left: 0.5rem;
|
|
||||||
padding-right: 0.5rem;
|
|
||||||
">
|
|
||||||
<span style="color: #6c757d; opacity: 0.75;" aria-hidden="true">/</span>
|
|
||||||
</li>
|
|
||||||
<li style="display: flex; align-items: center;">
|
|
||||||
<a
|
|
||||||
href="{% url 'candidate_detail' candidate.slug %}"
|
|
||||||
style="
|
|
||||||
color: #6c757d; /* text-secondary/gray */
|
|
||||||
text-decoration: none;
|
|
||||||
transition: color 0.15s ease-in-out;
|
|
||||||
"
|
|
||||||
onmouseover="this.style.color='#000000';"
|
|
||||||
onmouseout="this.style.color='#6c757d';"
|
|
||||||
>
|
|
||||||
{{candidate.resume_data.full_name}}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li style="
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding-left: 0.5rem;
|
|
||||||
padding-right: 0.5rem;
|
|
||||||
">
|
|
||||||
<span style="color: #6c757d; opacity: 0.75;" aria-hidden="true">/</span>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li
|
|
||||||
aria-current="page"
|
|
||||||
style="
|
|
||||||
color: #F43B5E; /* Rosy Accent Color */
|
|
||||||
font-weight: 600;
|
|
||||||
"
|
|
||||||
>
|
|
||||||
{% trans 'AI Overview' %}
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
</nav>
|
|
||||||
{% endif %}
|
|
||||||
{% endwith %}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user