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 @@
{% 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 %}{{ emailaddress.email }} - {# Status Badges: Using rounded-pill and appropriate colors #} + {# Status Badges #} {% if emailaddress.primary %} {% translate "Primary" %} {% endif %} @@ -71,33 +81,30 @@
+ {% 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 %} +
+ + + ++ {% 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 }}
+
{% translate "Verify your email to secure your account and unlock full features." %}
+ {# Centering the main header content #} +{% trans "Verify your email to secure your account and unlock full features." %}
- {% 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 #} {% else %} - {# ------------------- CONFIRMATION FAILED (Error) ------------------- #} - -- {% translate "The email confirmation link has expired or is invalid." %} +
+ {% 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 %} + + + + + ++ {% 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." %} +
+ +