small update

This commit is contained in:
ismail 2025-11-24 14:04:27 +03:00
parent 5983dc75ff
commit 9bf0125121
22 changed files with 3 additions and 2014 deletions

6
.env
View File

@ -1,3 +1,3 @@
DB_NAME=haikal_db
DB_USER=faheed
DB_PASSWORD=Faheed@215
DB_NAME=norahuniversity
DB_USER=norahuniversity
DB_PASSWORD=norahuniversity

View File

@ -1,3 +0,0 @@
from django.contrib import admin
# Register your models here.

View File

@ -1,6 +0,0 @@
from django.apps import AppConfig
class ApplicantConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'applicant'

View File

@ -1,22 +0,0 @@
from django import forms
from .models import ApplicantForm, FormField
class ApplicantFormCreateForm(forms.ModelForm):
class Meta:
model = ApplicantForm
fields = ['name', 'description']
widgets = {
'name': forms.TextInput(attrs={'class': 'form-control'}),
'description': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
}
class FormFieldForm(forms.ModelForm):
class Meta:
model = FormField
fields = ['label', 'field_type', 'required', 'help_text', 'choices']
widgets = {
'label': forms.TextInput(attrs={'class': 'form-control'}),
'field_type': forms.Select(attrs={'class': 'form-control'}),
'help_text': forms.Textarea(attrs={'class': 'form-control', 'rows': 2}),
'choices': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Option1, Option2, Option3'}),
}

View File

@ -1,49 +0,0 @@
from django import forms
from .models import FormField
# applicant/forms_builder.py
def create_dynamic_form(form_instance):
fields = {}
for field in form_instance.fields.all():
field_kwargs = {
'label': field.label,
'required': field.required,
'help_text': field.help_text
}
# Use stable field_name instead of database ID
field_key = field.field_name
if field.field_type == 'text':
fields[field_key] = forms.CharField(**field_kwargs)
elif field.field_type == 'email':
fields[field_key] = forms.EmailField(**field_kwargs)
elif field.field_type == 'phone':
fields[field_key] = forms.CharField(**field_kwargs)
elif field.field_type == 'number':
fields[field_key] = forms.IntegerField(**field_kwargs)
elif field.field_type == 'date':
fields[field_key] = forms.DateField(**field_kwargs)
elif field.field_type == 'textarea':
fields[field_key] = forms.CharField(
widget=forms.Textarea,
**field_kwargs
)
elif field.field_type in ['select', 'radio']:
choices = [(c.strip(), c.strip()) for c in field.choices.split(',') if c.strip()]
if not choices:
choices = [('', '---')]
if field.field_type == 'select':
fields[field_key] = forms.ChoiceField(choices=choices, **field_kwargs)
else:
fields[field_key] = forms.ChoiceField(
choices=choices,
widget=forms.RadioSelect,
**field_kwargs
)
elif field.field_type == 'checkbox':
field_kwargs['required'] = False
fields[field_key] = forms.BooleanField(**field_kwargs)
return type('DynamicApplicantForm', (forms.Form,), fields)

View File

@ -1,70 +0,0 @@
# Generated by Django 5.2.6 on 2025-10-01 21:41
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('jobs', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='ApplicantForm',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(help_text="Form version name (e.g., 'Version A', 'Version B' etc)", max_length=200)),
('description', models.TextField(blank=True, help_text='Optional description of this form version')),
('is_active', models.BooleanField(default=False, help_text='Only one form can be active per job')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('job_posting', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='applicant_forms', to='jobs.jobposting')),
],
options={
'verbose_name': 'Application Form',
'verbose_name_plural': 'Application Forms',
'ordering': ['-created_at'],
'unique_together': {('job_posting', 'name')},
},
),
migrations.CreateModel(
name='ApplicantSubmission',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('submitted_at', models.DateTimeField(auto_now_add=True)),
('data', models.JSONField()),
('ip_address', models.GenericIPAddressField(blank=True, null=True)),
('score', models.FloatField(default=0, help_text='Ranking score for the applicant submission')),
('form', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='applicant.applicantform')),
('job_posting', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='jobs.jobposting')),
],
options={
'verbose_name': 'Applicant Submission',
'verbose_name_plural': 'Applicant Submissions',
'ordering': ['-submitted_at'],
},
),
migrations.CreateModel(
name='FormField',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('label', models.CharField(max_length=255)),
('field_type', models.CharField(choices=[('text', 'Text'), ('email', 'Email'), ('phone', 'Phone'), ('number', 'Number'), ('date', 'Date'), ('select', 'Dropdown'), ('radio', 'Radio Buttons'), ('checkbox', 'Checkbox'), ('textarea', 'Paragraph Text'), ('file', 'File Upload'), ('image', 'Image Upload')], max_length=20)),
('required', models.BooleanField(default=True)),
('help_text', models.TextField(blank=True)),
('choices', models.TextField(blank=True, help_text='Comma-separated options for select/radio fields')),
('order', models.IntegerField(default=0)),
('field_name', models.CharField(blank=True, max_length=100)),
('form', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='fields', to='applicant.applicantform')),
],
options={
'verbose_name': 'Form Field',
'verbose_name_plural': 'Form Fields',
'ordering': ['order'],
},
),
]

View File

@ -1,144 +0,0 @@
# models.py
from django.db import models
from django.core.exceptions import ValidationError
from jobs.models import JobPosting
from django.urls import reverse
class ApplicantForm(models.Model):
"""Multiple dynamic forms per job posting, only one active at a time"""
job_posting = models.ForeignKey(
JobPosting,
on_delete=models.CASCADE,
related_name='applicant_forms'
)
name = models.CharField(
max_length=200,
help_text="Form version name (e.g., 'Version A', 'Version B' etc)"
)
description = models.TextField(
blank=True,
help_text="Optional description of this form version"
)
is_active = models.BooleanField(
default=False,
help_text="Only one form can be active per job"
)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
unique_together = ('job_posting', 'name')
ordering = ['-created_at']
verbose_name = "Application Form"
verbose_name_plural = "Application Forms"
def __str__(self):
status = "(Active)" if self.is_active else "(Inactive)"
return f"{self.name} for {self.job_posting.title} {status}"
def clean(self):
"""Ensure only one active form per job"""
if self.is_active:
existing_active = self.job_posting.applicant_forms.filter(
is_active=True
).exclude(pk=self.pk)
if existing_active.exists():
raise ValidationError(
"Only one active application form is allowed per job posting."
)
super().clean()
def activate(self):
"""Set this form as active and deactivate others"""
self.is_active = True
self.save()
# Deactivate other forms
self.job_posting.applicant_forms.exclude(pk=self.pk).update(
is_active=False
)
def get_public_url(self):
"""Returns the public application URL for this job's active form"""
return reverse('applicant:apply_form', args=[self.job_posting.internal_job_id])
class FormField(models.Model):
FIELD_TYPES = [
('text', 'Text'),
('email', 'Email'),
('phone', 'Phone'),
('number', 'Number'),
('date', 'Date'),
('select', 'Dropdown'),
('radio', 'Radio Buttons'),
('checkbox', 'Checkbox'),
('textarea', 'Paragraph Text'),
('file', 'File Upload'),
('image', 'Image Upload'),
]
form = models.ForeignKey(
ApplicantForm,
related_name='fields',
on_delete=models.CASCADE
)
label = models.CharField(max_length=255)
field_type = models.CharField(max_length=20, choices=FIELD_TYPES)
required = models.BooleanField(default=True)
help_text = models.TextField(blank=True)
choices = models.TextField(
blank=True,
help_text="Comma-separated options for select/radio fields"
)
order = models.IntegerField(default=0)
field_name = models.CharField(max_length=100, blank=True)
class Meta:
ordering = ['order']
verbose_name = "Form Field"
verbose_name_plural = "Form Fields"
def __str__(self):
return f"{self.label} ({self.field_type}) in {self.form.name}"
def save(self, *args, **kwargs):
if not self.field_name:
# Create a stable field name from label (e.g., "Full Name" → "full_name")
import re
# Use Unicode word characters, including Arabic, for field_name
self.field_name = re.sub(
r'[^\w]+',
'_',
self.label.lower(),
flags=re.UNICODE
).strip('_')
# Ensure uniqueness within the form
base_name = self.field_name
counter = 1
while FormField.objects.filter(
form=self.form,
field_name=self.field_name
).exists():
self.field_name = f"{base_name}_{counter}"
counter += 1
super().save(*args, **kwargs)
class ApplicantSubmission(models.Model):
job_posting = models.ForeignKey(JobPosting, on_delete=models.CASCADE)
form = models.ForeignKey(ApplicantForm, on_delete=models.CASCADE)
submitted_at = models.DateTimeField(auto_now_add=True)
data = models.JSONField()
ip_address = models.GenericIPAddressField(null=True, blank=True)
score = models.FloatField(
default=0,
help_text="Ranking score for the applicant submission"
)
class Meta:
ordering = ['-submitted_at']
verbose_name = "Applicant Submission"
verbose_name_plural = "Applicant Submissions"
def __str__(self):
return f"Submission for {self.job_posting.title} at {self.submitted_at}"

View File

@ -1,94 +0,0 @@
{% extends 'base.html' %}
{% block title %}
Apply: {{ job.title }}
{% endblock %}
{% block content %}
<div class="container my-5">
<div class="row justify-content-center">
<div class="col-lg-8">
{# --- 1. Job Header and Overview (Fixed/Static Info) --- #}
<div class="card bg-light-subtle mb-4 p-4 border-0 rounded-3 shadow-sm">
<h1 class="h2 fw-bold text-primary mb-1">{{ job.title }}</h1>
<p class="mb-3 text-muted">
Your final step to apply for this position.
</p>
<div class="d-flex gap-4 small text-secondary">
<div>
<i class="fas fa-building me-1"></i>
<strong>Department:</strong> {{ job.department|default:"Not specified" }}
</div>
<div>
<i class="fas fa-map-marker-alt me-1"></i>
<strong>Location:</strong> {{ job.get_location_display }}
</div>
<div>
<i class="fas fa-briefcase me-1"></i>
<strong>Type:</strong> {{ job.get_job_type_display }} • {{ job.get_workplace_type_display }}
</div>
</div>
</div>
{# --- 2. Application Form Section --- #}
<div class="card p-5 border-0 rounded-3 shadow">
<h2 class="h3 fw-semibold mb-3">Application Details</h2>
{% if applicant_form.description %}
<p class="text-muted mb-4">{{ applicant_form.description }}</p>
{% endif %}
<form method="post" novalidate>
{% csrf_token %}
{% for field in form %}
<div class="form-group mb-4">
{# Label Tag #}
<label for="{{ field.id_for_label }}" class="form-label">
{{ field.label }}
{% if field.field.required %}<span class="text-danger">*</span>{% endif %}
</label>
{# The Field Widget (Assumes form-control is applied in backend) #}
{{ field }}
{# Field Errors #}
{% if field.errors %}
<div class="invalid-feedback d-block">{{ field.errors }}</div>
{% endif %}
{# Help Text #}
{% if field.help_text %}
<div class="form-text">{{ field.help_text }}</div>
{% endif %}
</div>
{% endfor %}
{# General Form Errors (Non-field errors) #}
{% if form.non_field_errors %}
<div class="alert alert-danger mb-4">
{{ form.non_field_errors }}
</div>
{% endif %}
<button type="submit" class="btn btn-primary btn-lg mt-3 w-100">
<i class="fas fa-paper-plane me-2"></i> Submit Application
</button>
</form>
</div>
<footer class="mt-4 text-center">
<a href="{% url 'applicant:review_job_detail' job.internal_job_id %}"
class="btn btn-link text-secondary">
&larr; Review Job Details
</a>
</footer>
</div>
</div>
</div>
{% endblock %}

View File

@ -1,68 +0,0 @@
{% extends 'base.html' %}
{% block title %}
Define Form for {{ job.title }}
{% endblock %}
{% block content %}
<div class="container my-5">
<div class="row justify-content-center">
<div class="col-lg-8 col-md-10">
<div class="card shadow-lg border-0 p-4 p-md-5">
<h2 class="card-title text-center mb-4 text-dark">
🛠️ New Application Form Configuration
</h2>
<p class="text-center text-muted mb-4 border-bottom pb-3">
You are creating a new form structure for job: <strong>{{ job.title }}</strong>
</p>
<form method="post" novalidate>
{% csrf_token %}
<fieldset class="mb-5">
<legend class="h5 mb-3 text-secondary">Form Metadata</legend>
<div class="form-group mb-4">
<label for="{{ form.name.id_for_label }}" class="form-label required">
Form Name
</label>
{# The field should already have form-control applied from the backend #}
{{ form.name }}
{% if form.name.errors %}
<div class="text-danger small mt-1">{{ form.name.errors }}</div>
{% endif %}
</div>
<div class="form-group mb-4">
<label for="{{ form.description.id_for_label }}" class="form-label">
Description
</label>
{# The field should already have form-control applied from the backend #}
{{ form.description}}
{% if form.description.errors %}
<div class="text-danger small mt-1">{{ form.description.errors }}</div>
{% endif %}
</div>
</fieldset>
<div class="d-flex justify-content-end gap-3 pt-3">
<a href="{% url 'applicant:job_forms_list' job.internal_job_id %}"
class="btn btn-outline-secondary">
Cancel
</a>
<button type="submit" class="btn univ-color btn-lg">
Create Form & Continue &rarr;
</button>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock %}

File diff suppressed because it is too large Load Diff

View File

@ -1,103 +0,0 @@
{% extends 'base.html' %}
{% block title %}
Manage Forms | {{ job.title }}
{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-lg-10">
<header class="mb-5 pb-3 border-bottom d-flex flex-column flex-md-row justify-content-between align-items-md-center">
<div>
<h2 class="h3 mb-1 ">
<i class="fas fa-clipboard-list me-2 text-secondary"></i>
Application Forms for <span class="text-success fw-bold">"{{ job.title }}"</span>
</h2>
<p class="text-muted small">
Internal Job ID: **{{ job.internal_job_id }}**
</p>
</div>
{# Primary Action Button using the theme color #}
<a href="{% url 'applicant:create_form' job_id=job.internal_job_id %}"
class="btn univ-color btn-lg shadow-sm mt-3 mt-md-0">
<i class="fas fa-plus me-1"></i> Create New Form
</a>
</header>
{% if forms %}
<div class="list-group">
{% for form in forms %}
{# Custom styling based on active state #}
<div class="list-group-item d-flex flex-column flex-sm-row justify-content-between align-items-start align-items-sm-center p-3 mb-3 rounded shadow-sm
{% if form.is_active %}border-success border-3 bg-light{% else %}border-secondary border-1{% endif %}">
{# Left Section: Form Details #}
<div class="flex-grow-1 me-4 mb-2 mb-sm-0">
<h4 class="h5 mb-1 d-inline-block">
{{ form.name }}
</h4>
{# Status Badge #}
{% if form.is_active %}
<span class="badge bg-success ms-2">
<i class="fas fa-check-circle me-1"></i> Active Form
</span>
{% else %}
<span class="badge bg-secondary ms-2">
<i class="fas fa-times-circle me-1"></i> Inactive
</span>
{% endif %}
<p class="text-muted mt-1 mb-1 small">
{{ form.description|default:"— No description provided. —" }}
</p>
</div>
{# Right Section: Actions #}
<div class="d-flex gap-2 align-items-center flex-wrap">
{# Edit Structure Button #}
<a href="{% url 'applicant:edit_form' form.id %}"
class="btn btn-sm btn-outline-secondary">
<i class="fas fa-pen me-1"></i> Edit Structure
</a>
{# Conditional Activation Button #}
{% if not form.is_active %}
<a href="{% url 'applicant:activate_form' form.id %}"
class="btn btn-sm univ-color">
<i class="fas fa-bolt me-1"></i> Activate Form
</a>
{% else %}
{# Active indicator/Deactivate button placeholder #}
<a href="#" class="btn btn-sm btn-outline-success" disabled>
<i class="fas fa-star me-1"></i> Current Form
</a>
{% endif %}
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="text-center py-5 bg-light rounded shadow-sm">
<i class="fas fa-file-alt fa-4x text-muted mb-3"></i>
<p class="lead mb-0">No application forms have been created yet for this job.</p>
<p class="mt-2 mb-0 text-secondary">Click the button above to define a new form structure.</p>
</div>
{% endif %}
<footer class="text-end mt-5 pt-3 border-top">
<a href="{% url 'jobs:job_detail' job.internal_job_id %}"
class="btn btn-outline-secondary">
<i class="fas fa-arrow-left me-1"></i> Back to Job Details
</a>
</footer>
</div>
</div>
{% endblock %}

View File

@ -1,129 +0,0 @@
{% extends "base.html" %}
{% block title %}{{ job.title }} - University ATS{% endblock %}
{% block content %}
<div class="row mb-5">
<div class="col-lg-8">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h2>{{ job.title }}</h2>
<span class="badge bg-{{ job.status|lower }} status-badge">
{{ job.get_status_display }}
</span>
</div>
<div class="card-body">
<!-- Job Details -->
<div class="row mb-3">
<div class="col-md-6">
<strong>Department:</strong> {{ job.department|default:"Not specified" }}
</div>
<div class="col-md-6">
<strong>Position Number:</strong> {{ job.position_number|default:"Not specified" }}
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<strong>Job Type:</strong> {{ job.get_job_type_display }}
</div>
<div class="col-md-6">
<strong>Workplace:</strong> {{ job.get_workplace_type_display }}
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<strong>Location:</strong> {{ job.get_location_display }}
</div>
<div class="col-md-6">
<strong>Created By:</strong> {{ job.created_by|default:"Not specified" }}
</div>
</div>
{% if job.salary_range %}
<div class="row mb-3">
<div class="col-12">
<strong>Salary Range:</strong> {{ job.salary_range }}
</div>
</div>
{% endif %}
{% if job.start_date %}
<div class="row mb-3">
<div class="col-12">
<strong>Start Date:</strong> {{ job.start_date }}
</div>
</div>
{% endif %}
{% if job.application_deadline %}
<div class="row mb-3">
<div class="col-12">
<strong>Application Deadline:</strong> {{ job.application_deadline }}
{% if job.is_expired %}
<span class="badge bg-danger">EXPIRED</span>
{% endif %}
</div>
</div>
{% endif %}
<!-- Description -->
{% if job.description %}
<div class="mb-3">
<h5>Description</h5>
<div>{{ job.description|linebreaks }}</div>
</div>
{% endif %}
{% if job.qualifications %}
<div class="mb-3">
<h5>Qualifications</h5>
<div>{{ job.qualifications|linebreaks }}</div>
</div>
{% endif %}
{% if job.benefits %}
<div class="mb-3">
<h5>Benefits</h5>
<div>{{ job.benefits|linebreaks }}</div>
</div>
{% endif %}
{% if job.application_instructions %}
<div class="mb-3">
<h5>Application Instructions</h5>
<div>{{ job.application_instructions|linebreaks }}</div>
</div>
{% endif %}
</div>
</div>
</div>
<div class="col-lg-4">
<!-- Add this section below your existing job details -->
<div class="card mt-4">
<div class="card-header bg-success text-white">
<h5><i class="fas fa-file-signature"></i> Ready to Apply?</h5>
</div>
<div class="card-body">
<p>Review the job details on the left, then click the button below to submit your application.</p>
<a href="{% url 'applicant:apply_form' job.internal_job_id %}" class="btn btn-success btn-lg w-100">
<i class="fas fa-paper-plane"></i> Apply for this Position
</a>
<p class="text-muted mt-2">
<small>You'll be redirected to our secure application form where you can upload your resume and provide additional details.</small>
</p>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -1,35 +0,0 @@
{% extends 'base.html' %}
{% block title %}Application Submitted - {{ job.title }}{% endblock %}
{% block content %}
<div class="card">
<div style="text-align: center; padding: 30px 0;">
<div style="width: 80px; height: 80px; background: #d4edda; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; margin-bottom: 20px;">
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" fill="#28a745" viewBox="0 0 16 16">
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-3.97-3.03a.75.75 0 0 0-1.08.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-.01-1.05z"/>
</svg>
</div>
<h1 style="color: #28a745; margin-bottom: 15px;">Thank You!</h1>
<h2>Your application has been submitted successfully</h2>
<div style="background: #f8f9fa; padding: 20px; border-radius: 8px; margin: 25px 0; text-align: left;">
<p><strong>Position:</strong> {{ job.title }}</p>
<p><strong>Job ID:</strong> {{ job.internal_job_id }}</p>
<p><strong>Department:</strong> {{ job.department|default:"Not specified" }}</p>
{% if job.application_deadline %}
<p><strong>Application Deadline:</strong> {{ job.application_deadline|date:"F j, Y" }}</p>
{% endif %}
</div>
<p style="font-size: 18px; line-height: 1.6;">
We appreciate your interest in joining our team. Our hiring team will review your application
and contact you if there's a potential match for this position.
</p>
{% comment %} <div style="margin-top: 30px;">
<a href="/" class="btn btn-primary" style="margin-right: 10px;">Apply to Another Position</a>
<a href="{% url 'jobs:job_detail' job.internal_job_id %}" class="btn btn-outline">View Job Details</a>
</div> {% endcomment %}
</div>
</div>
{% endblock %}

View File

@ -1,24 +0,0 @@
import json
from django import template
register = template.Library()
@register.filter(name='from_json')
def from_json(json_string):
"""
Safely loads a JSON string into a Python object (list or dict).
"""
try:
# The JSON string comes from the context and needs to be parsed
return json.loads(json_string)
except (TypeError, json.JSONDecodeError):
# Handle cases where the string is invalid or None/empty
return []
@register.filter(name='split')
def split_string(value, key=None):
"""Splits a string by the given key (default is space)."""
if key is None:
return value.split()
return value.split(key)

View File

@ -1,14 +0,0 @@
# from django.db.models.signals import post_save
# from django.dispatch import receiver
# from . import models
#
# @receiver(post_save, sender=models.Candidate)
# def parse_resume(sender, instance, created, **kwargs):
# if instance.resume and not instance.summary:
# from .utils import extract_summary_from_pdf,match_resume_with_job_description
# summary = extract_summary_from_pdf(instance.resume.path)
# if 'error' not in summary:
# instance.summary = summary
# instance.save()
#
# # match_resume_with_job_description

View File

@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

View File

@ -1,18 +0,0 @@
from django.urls import path
from . import views
app_name = 'applicant'
urlpatterns = [
# Form Management
path('job/<str:job_id>/forms/', views.job_forms_list, name='job_forms_list'),
path('job/<str:job_id>/forms/create/', views.create_form_for_job, name='create_form'),
path('form/<int:form_id>/edit/', views.edit_form, name='edit_form'),
path('field/<int:field_id>/delete/', views.delete_field, name='delete_field'),
path('form/<int:form_id>/activate/', views.activate_form, name='activate_form'),
# Public Application
path('apply/<str:job_id>/', views.apply_form_view, name='apply_form'),
path('review/job/detail/<str:job_id>/',views.review_job_detail, name="review_job_detail"),
path('apply/<str:job_id>/thank-you/', views.thank_you_view, name='thank_you'),
]

View File

@ -1,34 +0,0 @@
import os
import fitz # PyMuPDF
import spacy
import requests
from recruitment import models
from django.conf import settings
nlp = spacy.load("en_core_web_sm")
def extract_text_from_pdf(pdf_path):
text = ""
with fitz.open(pdf_path) as doc:
for page in doc:
text += page.get_text()
return text
def extract_summary_from_pdf(pdf_path):
if not os.path.exists(pdf_path):
return {'error': 'File not found'}
text = extract_text_from_pdf(pdf_path)
doc = nlp(text)
summary = {
'name': doc.ents[0].text if doc.ents else '',
'skills': [chunk.text for chunk in doc.noun_chunks if len(chunk.text.split()) > 1],
'summary': text[:500]
}
return summary
def match_resume_with_job_description(resume, job_description,prompt=""):
resume_doc = nlp(resume)
job_doc = nlp(job_description)
similarity = resume_doc.similarity(job_doc)
return similarity

View File

@ -1,175 +0,0 @@
# applicant/views.py (Updated edit_form function)
from django.shortcuts import render, get_object_or_404, redirect
from django.contrib import messages
from django.http import Http404, JsonResponse # <-- Import JsonResponse
from django.views.decorators.csrf import csrf_exempt # <-- Needed for JSON POST if not using FormData
import json # <-- Import json
from django.db import transaction # <-- Import transaction
# (Keep all your existing imports)
from .models import ApplicantForm, FormField, ApplicantSubmission
from .forms import ApplicantFormCreateForm, FormFieldForm
from jobs.models import JobPosting
from .forms_builder import create_dynamic_form
# ... (Keep all other functions like job_forms_list, create_form_for_job, etc.)
# ...
# === FORM MANAGEMENT VIEWS ===
def job_forms_list(request, job_id):
"""List all forms for a specific job"""
job = get_object_or_404(JobPosting, internal_job_id=job_id)
forms = job.applicant_forms.all()
return render(request, 'applicant/job_forms_list.html', {
'job': job,
'forms': forms
})
def create_form_for_job(request, job_id):
"""Create a new form for a job"""
job = get_object_or_404(JobPosting, internal_job_id=job_id)
if request.method == 'POST':
form = ApplicantFormCreateForm(request.POST)
if form.is_valid():
applicant_form = form.save(commit=False)
applicant_form.job_posting = job
applicant_form.save()
messages.success(request, 'Form created successfully!')
return redirect('applicant:job_forms_list', job_id=job_id)
else:
form = ApplicantFormCreateForm()
return render(request, 'applicant/create_form.html', {
'job': job,
'form': form
})
@transaction.atomic # Ensures all fields are saved or none are
def edit_form(request, form_id):
"""Edit form details and manage fields, including dynamic builder save."""
applicant_form = get_object_or_404(ApplicantForm, id=form_id)
job = applicant_form.job_posting
if request.method == 'POST':
# --- 1. Handle JSON data from the Form Builder (JavaScript) ---
if request.content_type == 'application/json':
try:
field_data = json.loads(request.body)
# Clear existing fields for this form
applicant_form.fields.all().delete()
# Create new fields from the JSON data
for field_config in field_data:
# Sanitize/ensure required fields are present
FormField.objects.create(
form=applicant_form,
label=field_config.get('label', 'New Field'),
field_type=field_config.get('field_type', 'text'),
required=field_config.get('required', True),
help_text=field_config.get('help_text', ''),
choices=field_config.get('choices', ''),
order=field_config.get('order', 0),
# field_name will be auto-generated/re-generated on save() if needed
)
return JsonResponse({'status': 'success', 'message': 'Form structure saved successfully!'})
except json.JSONDecodeError:
return JsonResponse({'status': 'error', 'message': 'Invalid JSON data.'}, status=400)
except Exception as e:
return JsonResponse({'status': 'error', 'message': f'Server error: {str(e)}'}, status=500)
# --- 2. Handle standard POST requests (e.g., saving form details) ---
elif 'save_form_details' in request.POST: # Changed the button name for clarity
form_details = ApplicantFormCreateForm(request.POST, instance=applicant_form)
if form_details.is_valid():
form_details.save()
messages.success(request, 'Form details updated successfully!')
return redirect('applicant:edit_form', form_id=form_id)
# Note: The 'add_field' branch is now redundant since we use the builder,
# but you can keep it if you want the old manual way too.
# --- GET Request (or unsuccessful POST) ---
form_details = ApplicantFormCreateForm(instance=applicant_form)
# Get initial fields to load into the JS builder
initial_fields_json = list(applicant_form.fields.values(
'label', 'field_type', 'required', 'help_text', 'choices', 'order', 'field_name'
))
return render(request, 'applicant/edit_form.html', {
'applicant_form': applicant_form,
'job': job,
'form_details': form_details,
'initial_fields_json': json.dumps(initial_fields_json)
})
def delete_field(request, field_id):
"""Delete a form field"""
field = get_object_or_404(FormField, id=field_id)
form_id = field.form.id
field.delete()
messages.success(request, 'Field deleted successfully!')
return redirect('applicant:edit_form', form_id=form_id)
def activate_form(request, form_id):
"""Activate a form (deactivates others automatically)"""
applicant_form = get_object_or_404(ApplicantForm, id=form_id)
applicant_form.activate()
messages.success(request, f'Form "{applicant_form.name}" is now active!')
return redirect('applicant:job_forms_list', job_id=applicant_form.job_posting.internal_job_id)
# === PUBLIC VIEWS (for applicants) ===
def apply_form_view(request, job_id):
"""Public application form - serves active form"""
job = get_object_or_404(JobPosting, internal_job_id=job_id, status='ACTIVE')
if job.is_expired():
raise Http404("Application deadline has passed")
try:
applicant_form = job.applicant_forms.get(is_active=True)
except ApplicantForm.DoesNotExist:
raise Http404("No active application form configured for this job")
DynamicForm = create_dynamic_form(applicant_form)
if request.method == 'POST':
form = DynamicForm(request.POST)
if form.is_valid():
ApplicantSubmission.objects.create(
job_posting=job,
form=applicant_form,
data=form.cleaned_data,
ip_address=request.META.get('REMOTE_ADDR')
)
return redirect('applicant:thank_you', job_id=job_id)
else:
form = DynamicForm()
return render(request, 'applicant/apply_form.html', {
'form': form,
'job': job,
'applicant_form': applicant_form
})
def review_job_detail(request,job_id):
"""Public job detail view for applicants"""
job = get_object_or_404(JobPosting, internal_job_id=job_id, status='ACTIVE')
if job.is_expired():
raise Http404("This job posting has expired.")
return render(request,'applicant/review_job_detail.html',{'job':job})
def thank_you_view(request, job_id):
job = get_object_or_404(JobPosting, internal_job_id=job_id)
return render(request, 'applicant/thank_you.html', {'job': job})