diff --git a/.env b/.env index 8d7fbd5..b9e2bf0 100644 --- a/.env +++ b/.env @@ -1,3 +1,3 @@ -DB_NAME=haikal_db -DB_USER=faheed -DB_PASSWORD=Faheed@215 \ No newline at end of file +DB_NAME=norahuniversity +DB_USER=norahuniversity +DB_PASSWORD=norahuniversity \ No newline at end of file diff --git a/static/image/applicant/__init__.py b/static/image/applicant/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/static/image/applicant/admin.py b/static/image/applicant/admin.py deleted file mode 100644 index 8c38f3f..0000000 --- a/static/image/applicant/admin.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.contrib import admin - -# Register your models here. diff --git a/static/image/applicant/apps.py b/static/image/applicant/apps.py deleted file mode 100644 index 27badf7..0000000 --- a/static/image/applicant/apps.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.apps import AppConfig - - -class ApplicantConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'applicant' diff --git a/static/image/applicant/forms.py b/static/image/applicant/forms.py deleted file mode 100644 index 5c5b0b5..0000000 --- a/static/image/applicant/forms.py +++ /dev/null @@ -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'}), - } \ No newline at end of file diff --git a/static/image/applicant/forms_builder.py b/static/image/applicant/forms_builder.py deleted file mode 100644 index c3a43e7..0000000 --- a/static/image/applicant/forms_builder.py +++ /dev/null @@ -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) \ No newline at end of file diff --git a/static/image/applicant/migrations/0001_initial.py b/static/image/applicant/migrations/0001_initial.py deleted file mode 100644 index d7437c3..0000000 --- a/static/image/applicant/migrations/0001_initial.py +++ /dev/null @@ -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'], - }, - ), - ] diff --git a/static/image/applicant/migrations/__init__.py b/static/image/applicant/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/static/image/applicant/models.py b/static/image/applicant/models.py deleted file mode 100644 index 6b35d2f..0000000 --- a/static/image/applicant/models.py +++ /dev/null @@ -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}" \ No newline at end of file diff --git a/static/image/applicant/templates/applicant/apply_form.html b/static/image/applicant/templates/applicant/apply_form.html deleted file mode 100644 index eae2993..0000000 --- a/static/image/applicant/templates/applicant/apply_form.html +++ /dev/null @@ -1,94 +0,0 @@ -{% extends 'base.html' %} - -{% block title %} - Apply: {{ job.title }} -{% endblock %} - -{% block content %} -
-
-
- - {# --- 1. Job Header and Overview (Fixed/Static Info) --- #} -
-

{{ job.title }}

- -

- Your final step to apply for this position. -

- -
-
- - Department: {{ job.department|default:"Not specified" }} -
-
- - Location: {{ job.get_location_display }} -
-
- - Type: {{ job.get_job_type_display }} • {{ job.get_workplace_type_display }} -
-
-
- - {# --- 2. Application Form Section --- #} -
-

Application Details

- - {% if applicant_form.description %} -

{{ applicant_form.description }}

- {% endif %} - -
- {% csrf_token %} - - {% for field in form %} -
- {# Label Tag #} - - - {# The Field Widget (Assumes form-control is applied in backend) #} - {{ field }} - - {# Field Errors #} - {% if field.errors %} -
{{ field.errors }}
- {% endif %} - - {# Help Text #} - {% if field.help_text %} -
{{ field.help_text }}
- {% endif %} -
- {% endfor %} - - {# General Form Errors (Non-field errors) #} - {% if form.non_field_errors %} -
- {{ form.non_field_errors }} -
- {% endif %} - - -
-
- - - -
-
-
- -{% endblock %} \ No newline at end of file diff --git a/static/image/applicant/templates/applicant/create_form.html b/static/image/applicant/templates/applicant/create_form.html deleted file mode 100644 index e1c616a..0000000 --- a/static/image/applicant/templates/applicant/create_form.html +++ /dev/null @@ -1,68 +0,0 @@ -{% extends 'base.html' %} - -{% block title %} - Define Form for {{ job.title }} -{% endblock %} - -{% block content %} -
-
-
- -
- -

- 🛠️ New Application Form Configuration -

- -

- You are creating a new form structure for job: {{ job.title }} -

- -
- {% csrf_token %} - -
- Form Metadata - -
- - {# The field should already have form-control applied from the backend #} - {{ form.name }} - - {% if form.name.errors %} -
{{ form.name.errors }}
- {% endif %} -
- -
- - {# The field should already have form-control applied from the backend #} - {{ form.description}} - - {% if form.description.errors %} -
{{ form.description.errors }}
- {% endif %} -
-
- -
- - Cancel - - -
- -
-
-
-
-
-{% endblock %} \ No newline at end of file diff --git a/static/image/applicant/templates/applicant/edit_form.html b/static/image/applicant/templates/applicant/edit_form.html deleted file mode 100644 index e9ad842..0000000 --- a/static/image/applicant/templates/applicant/edit_form.html +++ /dev/null @@ -1,1020 +0,0 @@ -{% extends 'base.html' %} -{% load static i18n %} - -{% block title %} -Edit Application Form - {{ applicant_form.name }} -{% endblock %} - -{% block customCSS %} - -{% endblock %} - -{% block content %} -
- {% if messages %} - - {% endif %} - -
-
-

Edit Application Form

- -
- -
- -
-
-

Form Details

-
-
- {% csrf_token %} -
- - {{ form_details.name }} -
-
- - {{ form_details.description }} -
-
- -
-
-
- -
-
-
-

+ Add Field

-
- T Text Input -
-
- @ Email -
-
- Dropdown -
-
- Checkbox -
-
- Paragraph -
-
- 📅 Date -
-
- Radio Buttons -
-
- -
-
-

Form Structure

- -
-
-
- + -

Drag fields from the left panel to build your form

-
-
- -
-
-
-
-{% endblock %} - -{% block customJS %} - -{% endblock %} \ No newline at end of file diff --git a/static/image/applicant/templates/applicant/job_forms_list.html b/static/image/applicant/templates/applicant/job_forms_list.html deleted file mode 100644 index 7c7253f..0000000 --- a/static/image/applicant/templates/applicant/job_forms_list.html +++ /dev/null @@ -1,103 +0,0 @@ -{% extends 'base.html' %} - -{% block title %} - Manage Forms | {{ job.title }} -{% endblock %} - -{% block content %} -
-
- -
-
-

- - Application Forms for "{{ job.title }}" -

-

- Internal Job ID: **{{ job.internal_job_id }}** -

-
- - {# Primary Action Button using the theme color #} - - Create New Form - -
- - {% if forms %} - -
- {% for form in forms %} - - {# Custom styling based on active state #} -
- - {# Left Section: Form Details #} -
-

- {{ form.name }} -

- - {# Status Badge #} - {% if form.is_active %} - - Active Form - - {% else %} - - Inactive - - {% endif %} - -

- {{ form.description|default:"— No description provided. —" }} -

-
- - {# Right Section: Actions #} -
- - {# Edit Structure Button #} - - Edit Structure - - - {# Conditional Activation Button #} - {% if not form.is_active %} - - Activate Form - - {% else %} - {# Active indicator/Deactivate button placeholder #} - - Current Form - - {% endif %} -
-
- {% endfor %} -
- - {% else %} -
- -

No application forms have been created yet for this job.

-

Click the button above to define a new form structure.

-
- {% endif %} - - - -
-
-{% endblock %} \ No newline at end of file diff --git a/static/image/applicant/templates/applicant/review_job_detail.html b/static/image/applicant/templates/applicant/review_job_detail.html deleted file mode 100644 index 44414b3..0000000 --- a/static/image/applicant/templates/applicant/review_job_detail.html +++ /dev/null @@ -1,129 +0,0 @@ -{% extends "base.html" %} - -{% block title %}{{ job.title }} - University ATS{% endblock %} - -{% block content %} -
-
-
-
-

{{ job.title }}

- - {{ job.get_status_display }} - -
-
- -
-
- Department: {{ job.department|default:"Not specified" }} -
-
- Position Number: {{ job.position_number|default:"Not specified" }} -
-
- -
-
- Job Type: {{ job.get_job_type_display }} -
-
- Workplace: {{ job.get_workplace_type_display }} -
-
- -
-
- Location: {{ job.get_location_display }} -
-
- Created By: {{ job.created_by|default:"Not specified" }} -
-
- - {% if job.salary_range %} -
-
- Salary Range: {{ job.salary_range }} -
-
- {% endif %} - - {% if job.start_date %} -
-
- Start Date: {{ job.start_date }} -
-
- {% endif %} - - {% if job.application_deadline %} -
-
- Application Deadline: {{ job.application_deadline }} - {% if job.is_expired %} - EXPIRED - {% endif %} -
-
- {% endif %} - - - {% if job.description %} -
-
Description
-
{{ job.description|linebreaks }}
-
- {% endif %} - - {% if job.qualifications %} -
-
Qualifications
-
{{ job.qualifications|linebreaks }}
-
- {% endif %} - - {% if job.benefits %} -
-
Benefits
-
{{ job.benefits|linebreaks }}
-
- {% endif %} - - {% if job.application_instructions %} -
-
Application Instructions
-
{{ job.application_instructions|linebreaks }}
-
- {% endif %} - - -
-
-
- -
- - -
-
-
Ready to Apply?
-
-
-

Review the job details on the left, then click the button below to submit your application.

- - Apply for this Position - -

- You'll be redirected to our secure application form where you can upload your resume and provide additional details. -

-
-
- - - -
-
- - -{% endblock %} \ No newline at end of file diff --git a/static/image/applicant/templates/applicant/thank_you.html b/static/image/applicant/templates/applicant/thank_you.html deleted file mode 100644 index b93c945..0000000 --- a/static/image/applicant/templates/applicant/thank_you.html +++ /dev/null @@ -1,35 +0,0 @@ -{% extends 'base.html' %} -{% block title %}Application Submitted - {{ job.title }}{% endblock %} -{% block content %} -
-
-
- - - -
- -

Thank You!

-

Your application has been submitted successfully

- -
-

Position: {{ job.title }}

-

Job ID: {{ job.internal_job_id }}

-

Department: {{ job.department|default:"Not specified" }}

- {% if job.application_deadline %} -

Application Deadline: {{ job.application_deadline|date:"F j, Y" }}

- {% endif %} -
- -

- 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. -

- - {% comment %}
- Apply to Another Position - View Job Details -
{% endcomment %} -
-
-{% endblock %} \ No newline at end of file diff --git a/static/image/applicant/templatetags/__init__.py b/static/image/applicant/templatetags/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/static/image/applicant/templatetags/mytags.py b/static/image/applicant/templatetags/mytags.py deleted file mode 100644 index b60911d..0000000 --- a/static/image/applicant/templatetags/mytags.py +++ /dev/null @@ -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) \ No newline at end of file diff --git a/static/image/applicant/templatetags/signals.py b/static/image/applicant/templatetags/signals.py deleted file mode 100644 index 8d5f22f..0000000 --- a/static/image/applicant/templatetags/signals.py +++ /dev/null @@ -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 \ No newline at end of file diff --git a/static/image/applicant/tests.py b/static/image/applicant/tests.py deleted file mode 100644 index 7ce503c..0000000 --- a/static/image/applicant/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/static/image/applicant/urls.py b/static/image/applicant/urls.py deleted file mode 100644 index fa4fe8a..0000000 --- a/static/image/applicant/urls.py +++ /dev/null @@ -1,18 +0,0 @@ -from django.urls import path -from . import views - -app_name = 'applicant' - -urlpatterns = [ - # Form Management - path('job//forms/', views.job_forms_list, name='job_forms_list'), - path('job//forms/create/', views.create_form_for_job, name='create_form'), - path('form//edit/', views.edit_form, name='edit_form'), - path('field//delete/', views.delete_field, name='delete_field'), - path('form//activate/', views.activate_form, name='activate_form'), - - # Public Application - path('apply//', views.apply_form_view, name='apply_form'), - path('review/job/detail//',views.review_job_detail, name="review_job_detail"), - path('apply//thank-you/', views.thank_you_view, name='thank_you'), -] \ No newline at end of file diff --git a/static/image/applicant/utils.py b/static/image/applicant/utils.py deleted file mode 100644 index 4901d72..0000000 --- a/static/image/applicant/utils.py +++ /dev/null @@ -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 \ No newline at end of file diff --git a/static/image/applicant/views.py b/static/image/applicant/views.py deleted file mode 100644 index 2cb4dc3..0000000 --- a/static/image/applicant/views.py +++ /dev/null @@ -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}) \ No newline at end of file