diff --git a/NorahUniversity/__pycache__/settings.cpython-312.pyc b/NorahUniversity/__pycache__/settings.cpython-312.pyc index b40e327..a882953 100644 Binary files a/NorahUniversity/__pycache__/settings.cpython-312.pyc and b/NorahUniversity/__pycache__/settings.cpython-312.pyc differ diff --git a/NorahUniversity/settings.py b/NorahUniversity/settings.py index e6c1980..3eb57de 100644 --- a/NorahUniversity/settings.py +++ b/NorahUniversity/settings.py @@ -185,8 +185,7 @@ ACCOUNT_SIGNUP_FIELDS = ['email*', 'password1*', 'password2*'] ACCOUNT_UNIQUE_EMAIL = True ACCOUNT_EMAIL_VERIFICATION = 'none' ACCOUNT_USER_MODEL_USERNAME_FIELD = None - - +ACCOUNT_EMAIL_VERIFICATION = "mandatory" ACCOUNT_LOGIN_ON_EMAIL_CONFIRMATION = True @@ -238,7 +237,7 @@ STATICFILES_DIRS = [ BASE_DIR / 'static' ] STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') -MEDIA_ROOT = os.path.join(BASE_DIR, 'static/media') +MEDIA_ROOT = os.path.join(BASE_DIR, 'media') # Default primary key field type # https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field diff --git a/media/resumes/Abdullah_Bakhsh_-_2025.pdf b/media/resumes/Abdullah_Bakhsh_-_2025.pdf deleted file mode 100644 index 852169f..0000000 Binary files a/media/resumes/Abdullah_Bakhsh_-_2025.pdf and /dev/null differ diff --git a/recruitment/__pycache__/forms.cpython-312.pyc b/recruitment/__pycache__/forms.cpython-312.pyc index 8def54e..cfa60ce 100644 Binary files a/recruitment/__pycache__/forms.cpython-312.pyc and b/recruitment/__pycache__/forms.cpython-312.pyc differ diff --git a/recruitment/__pycache__/models.cpython-312.pyc b/recruitment/__pycache__/models.cpython-312.pyc index ae64c81..1dc5ea3 100644 Binary files a/recruitment/__pycache__/models.cpython-312.pyc and b/recruitment/__pycache__/models.cpython-312.pyc differ diff --git a/recruitment/__pycache__/signals.cpython-312.pyc b/recruitment/__pycache__/signals.cpython-312.pyc index 6a63901..56eb7e6 100644 Binary files a/recruitment/__pycache__/signals.cpython-312.pyc and b/recruitment/__pycache__/signals.cpython-312.pyc differ diff --git a/recruitment/forms.py b/recruitment/forms.py index faaeefb..6f4e28a 100644 --- a/recruitment/forms.py +++ b/recruitment/forms.py @@ -642,3 +642,6 @@ class CandidateExamDateForm(forms.ModelForm): widgets = { 'exam_date': forms.DateTimeInput(attrs={'type': 'datetime-local', 'class': 'form-control'}), } + + + diff --git a/recruitment/hooks.py b/recruitment/hooks.py index 8b13789..ca9c361 100644 --- a/recruitment/hooks.py +++ b/recruitment/hooks.py @@ -1 +1,14 @@ +from .models import Candidate +from time import sleep +def callback_ai_parsing(task): + if task.success: + try: + pk = task.args[0] + c = Candidate.objects.get(pk=pk) + if c.retry and not c.is_resume_parsed: + sleep(30) + c.retry -= 1 + c.save() + except Exception as e: + print(e) \ No newline at end of file diff --git a/recruitment/management/commands/seed.py b/recruitment/management/commands/seed.py index da15698..370eb7f 100644 --- a/recruitment/management/commands/seed.py +++ b/recruitment/management/commands/seed.py @@ -7,7 +7,7 @@ import random from datetime import date, timedelta from django.core.management.base import BaseCommand from django.utils import timezone - +from time import sleep from faker import Faker from recruitment.models import JobPosting, Candidate, Source, FormTemplate @@ -67,6 +67,7 @@ class Command(BaseCommand): created_jobs = [] for i in range(jobs_count): # Dynamic job details + sleep(random.randint(4,10)) title = fake.job() department = random.choice(DEPARTMENTS) is_faculty = random.random() < 0.1 # 10% chance of being a faculty job @@ -101,7 +102,7 @@ class Command(BaseCommand): job = JobPosting.objects.create( **job_data ) - FormTemplate.objects.create(job=job, name=f"{job.title} Form", description=f"Form for {job.title}",is_active=True) + # FormTemplate.objects.create(job=job, name=f"{job.title} Form", description=f"Form for {job.title}",is_active=True) created_jobs.append(job) self.stdout.write(self.style.SUCCESS(f'Created JobPosting {i+1}/{jobs_count}: {job.title}')) @@ -109,6 +110,7 @@ class Command(BaseCommand): # 4. Generate Candidates if created_jobs: for i in range(candidates_count): + sleep(random.randint(4,10)) # Link candidate to a random job target_job = random.choice(created_jobs) print(target_job) diff --git a/recruitment/migrations/0002_candidate_retry.py b/recruitment/migrations/0002_candidate_retry.py new file mode 100644 index 0000000..0f405fc --- /dev/null +++ b/recruitment/migrations/0002_candidate_retry.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.7 on 2025-10-26 11:53 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('recruitment', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='candidate', + name='retry', + field=models.SmallIntegerField(default=3, verbose_name='Resume Parsing Retry'), + ), + ] diff --git a/recruitment/models.py b/recruitment/models.py index 720cb71..a0776f0 100644 --- a/recruitment/models.py +++ b/recruitment/models.py @@ -453,6 +453,7 @@ class Candidate(Base): related_name="submitted_candidates", verbose_name=_("Submitted by Agency"), ) + retry = models.SmallIntegerField(verbose_name="Resume Parsing Retry",default=3) class Meta: verbose_name = _("Candidate") diff --git a/recruitment/signals.py b/recruitment/signals.py index 501a944..5f6b623 100644 --- a/recruitment/signals.py +++ b/recruitment/signals.py @@ -13,7 +13,7 @@ logger = logging.getLogger(__name__) @receiver(post_save, sender=JobPosting) def format_job(sender, instance, created, **kwargs): if created: - # FormTemplate.objects.create(job=instance, is_active=False, name=instance.title) + FormTemplate.objects.create(job=instance, is_active=False, name=instance.title) async_task( 'recruitment.tasks.format_job_description', instance.pk, @@ -49,10 +49,10 @@ def format_job(sender, instance, created, **kwargs): def score_candidate_resume(sender, instance, created, **kwargs): if not instance.is_resume_parsed: logger.info(f"Scoring resume for candidate {instance.pk}") - async_task( + async_task( 'recruitment.tasks.handle_reume_parsing_and_scoring', instance.pk, - # hook='myapp.tasks.email_sent_callback' # Optional callback + hook='recruitment.hooks.callback_ai_parsing' ) @receiver(post_save, sender=FormTemplate) diff --git a/recruitment/tasks.py b/recruitment/tasks.py index 31ebc8d..d975b10 100644 --- a/recruitment/tasks.py +++ b/recruitment/tasks.py @@ -167,6 +167,7 @@ def ai_handler(prompt): print(response.status_code) if response.status_code == 200: res = response.json() + print(res) content = res["choices"][0]['message']['content'] try: # print(content) @@ -231,7 +232,7 @@ def handle_reume_parsing_and_scoring(pk): logger.error(f"Error during initial data retrieval/parsing for candidate {instance.pk}: {e}") print(f"Error during initial data retrieval/parsing for candidate {instance.pk}: {e}") return - + print(resume_text) # --- 3. Single, Combined LLM Prompt (Major Cost & Latency Optimization) --- prompt = f""" You are an expert AI system functioning as both a Resume Parser and a Technical Recruiter. diff --git a/templates/account/email.html b/templates/account/email.html index 048d994..5979a19 100644 --- a/templates/account/email.html +++ b/templates/account/email.html @@ -17,12 +17,13 @@
- {# ------------------- LEFT COLUMN: ACCOUNT MENU (New Card Style) ------------------- #} + {# ------------------- LEFT COLUMN: ACCOUNT MENU ------------------- #}
{# Assuming a main 'Profile' or 'Personal Information' page exists #} + {# NOTE: You will need to replace 'user_detail' with your actual profile URL name #} {% translate "Personal Information" %} @@ -51,6 +52,15 @@
{% translate "Email Addresses" %}

{% translate "These email addresses are linked to your account. You can set the primary address, resend verification, or remove an address." %}

+ {# Django Messages for Success/Error feedback #} + {% if messages %} + {% for message in messages %} + + {% endfor %} + {% endif %} + {% if emailaddresses %} {% for emailaddress in emailaddresses %}
@@ -58,7 +68,7 @@

{{ emailaddress.email }} - {# Status Badges: Using rounded-pill and appropriate colors #} + {# Status Badges #} {% if emailaddress.primary %} {% translate "Primary" %} {% endif %} @@ -71,33 +81,30 @@

- {# 1. MAKE PRIMARY ACTION #} + {# 1. MAKE PRIMARY ACTION (Uses 'action_primary') #} {% if not emailaddress.primary %}
{% csrf_token %} - - +
{% endif %} - {# 2. RESEND VERIFICATION ACTION #} + {# 2. RESEND VERIFICATION ACTION (Uses 'action_send') #} {% if not emailaddress.verified %}
{% csrf_token %} - - +
{% endif %} - {# 3. REMOVE ACTION #} + {# 3. REMOVE ACTION (Uses 'action_remove') #} {% if not emailaddress.primary %}
{% csrf_token %} - - +
{% endif %}
@@ -109,33 +116,26 @@
- {# ------------------- ADD EMAIL FORM ------------------- #} -
{% translate "Add Email Address" %}
-
- {% csrf_token %} - - {# 1. Explicitly render non-field errors first #} - {% if form.non_field_errors %} - - {% 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 %} -
{{ message }}
- {% endfor %} - {% endif %} - - {# Teal/Dark Green button consistent with "Save Changes" #} - -
+ {# ------------------- ADD EMAIL FORM (The form that failed previously) ------------------- #} + {% if can_add_email %} +
{% translate "Add Email Address" %}
+
+ {% csrf_token %} + + {# Ensure non-field errors are displayed (e.g., "Email already registered") #} + {% if form.non_field_errors %} + + {% endif %} + + {# Crispy renders the form fields (including new_email) #} + {{ form|crispy }} + + {# The button name is crucial for the allauth view to recognize the "Add" action #} + +
+ {% endif %}
diff --git a/templates/account/email/email_confirmation_message.html b/templates/account/email/email_confirmation_message.html new file mode 100644 index 0000000..fe01de7 --- /dev/null +++ b/templates/account/email/email_confirmation_message.html @@ -0,0 +1,28 @@ +{% load account i18n %} +{% autoescape off %} + +

+ {% blocktrans %}Hello,{% endblocktrans %} +

+ +

+ {% blocktrans %}Thank you for choosing **KAAUH ATS**. To verify the ownership of your email address, please click the confirmation link below:{% endblocktrans %} +

+ +
+ + {% trans "Confirm My KAAUH ATS Email" %} + +
+ +

+ {% blocktrans %}If you did not request this verification, you can safely ignore this email.{% endblocktrans %} +

+ +

+ {% blocktrans %}Alternatively, copy and paste this link into your browser:{% endblocktrans %}
+ {{ activate_url }} +

+ +{% endautoescape %} \ No newline at end of file diff --git a/templates/account/email_confirm.html b/templates/account/email_confirm.html index 3b802d6..7489e61 100644 --- a/templates/account/email_confirm.html +++ b/templates/account/email_confirm.html @@ -2,62 +2,65 @@ {% load i18n %} {% load account %} -{% block title %}{% translate "Confirm Email Address" %}{% endblock %} +{% block title %}{% trans "Confirm Email Address" %}{% endblock %} {% block content %}
-
-
-

{% translate "Account Verification" %}

-

{% translate "Verify your email to secure your account and unlock full features." %}

+ {# Centering the main header content #} +
+
+

{% trans "Account Verification" %}

+

{% trans "Verify your email to secure your account and unlock full features." %}

-
+
- {% with emailaddress.email as email %} + {% with email as email %} {% if confirmation %} - {# ------------------- CONFIRMATION REQUEST (GET) ------------------- #} + {# ------------------- CONFIRMATION REQUEST (GET) - Success Theme ------------------- #} {% user_display confirmation.email_address.user as user_display %} - + {# Changed icon to a checkmark for clarity #} +

{% translate "Confirm Your Email Address" %}

-

- {% blocktrans with email as email %}Please confirm that **{{ email }}** is an email address for user **{{ user_display }}**.{% endblocktrans %} +

+ {% blocktrans with email as email %}Please confirm that **{{ email }}** is the correct email address for your account.{% endblocktrans %}

{# Confirmation Form #}
{% csrf_token %} - {# Teal/Dark Green button consistent with the UI theme #} -
{% else %} - {# ------------------- CONFIRMATION FAILED (Error) ------------------- #} - -

{% translate "Invalid Link" %}

+ {# ------------------- CONFIRMATION FAILED (Error) - Danger Theme ------------------- #} + {# Changed icon to be more specific #} -

- {% translate "The email confirmation link has expired or is invalid." %} +

{% translate "Verification Failed" %}

+ +

+ {% translate "The email confirmation link is expired or invalid." %}

-

- {% translate "Please request a new verification email from your account settings page." %} +

+ {% translate "If you recently requested a link, please ensure you use the newest one. You can request a new verification email from your account settings." %}

- - {% translate "Go to Settings" %} + + {% translate "Go to Settings" %} {% endif %} diff --git a/templates/account/verification_sent.html b/templates/account/verification_sent.html new file mode 100644 index 0000000..0718199 --- /dev/null +++ b/templates/account/verification_sent.html @@ -0,0 +1,193 @@ +{% load static %} +{% load i18n %} +{% load allauth %} +{% load account %} + + + + + + KAAUH ATS - Verify Email + + + + + + + + +
+ +
+
+

+ +
+
جامعة الأميرة نورة بنت عبدالرحمن الأكاديمية
+
ومستشفى الملك عبدالله بن عبدالعزيز التخصصي
+
Princess Nourah bint Abdulrahman University
+
King Abdullah bin Abdulaziz University Hospital
+
+
+

+ Powered By TENHAL | تنحل +
+
+ +
+ +
+ +

+ {% trans "Verify Your Email Address" %} +

+ +
+ +

+ {% blocktrans %} + We have sent an email to your email id for verification. Follow the link provided to finalize the signup process. + {% endblocktrans %} +

+ +

+ {% trans "If you do not see the verification email in your main inbox, please check your spam folder." %} +

+ +

+ {% trans "Please contact us if you do not receive the verification email within a few minutes." %} +

+ +
+ + + + +
+
+ +
+
+ + + + \ No newline at end of file