978 lines
35 KiB
Python
978 lines
35 KiB
Python
import json
|
||
import requests
|
||
from django.views.decorators.csrf import csrf_exempt
|
||
from django.views.decorators.http import require_http_methods
|
||
from django.http import JsonResponse
|
||
from datetime import datetime
|
||
from django.views import View
|
||
from django.db.models import Q
|
||
from django.urls import reverse
|
||
from django.conf import settings
|
||
from django.utils import timezone
|
||
from .forms import ZoomMeetingForm,JobPostingForm,FormTemplateForm,InterviewScheduleForm
|
||
from rest_framework import viewsets
|
||
from django.contrib import messages
|
||
from django.core.paginator import Paginator
|
||
from .linkedin_service import LinkedInService
|
||
from .models import FormTemplate, FormStage, FormField,FieldResponse,FormSubmission,InterviewSchedule
|
||
from .models import ZoomMeeting, Candidate, JobPosting
|
||
from .serializers import JobPostingSerializer, CandidateSerializer
|
||
from django.shortcuts import get_object_or_404, render, redirect
|
||
from django.views.generic import CreateView,UpdateView,DetailView,ListView
|
||
from .utils import create_zoom_meeting, delete_zoom_meeting, update_zoom_meeting,schedule_interviews,get_available_time_slots
|
||
from django.views.decorators.csrf import ensure_csrf_cookie
|
||
import logging
|
||
|
||
logger=logging.getLogger(__name__)
|
||
|
||
|
||
class JobPostingViewSet(viewsets.ModelViewSet):
|
||
queryset = JobPosting.objects.all()
|
||
serializer_class = JobPostingSerializer
|
||
|
||
class CandidateViewSet(viewsets.ModelViewSet):
|
||
queryset = Candidate.objects.all()
|
||
serializer_class = CandidateSerializer
|
||
|
||
|
||
class ZoomMeetingCreateView(CreateView):
|
||
model = ZoomMeeting
|
||
template_name = 'meetings/create_meeting.html'
|
||
form_class = ZoomMeetingForm
|
||
success_url = '/'
|
||
|
||
def form_valid(self, form):
|
||
instance = form.save(commit=False)
|
||
try:
|
||
topic = instance.topic
|
||
if instance.start_time < timezone.now():
|
||
messages.error(self.request, "Start time must be in the future.")
|
||
return redirect('/create-meeting/', status=400)
|
||
start_time = instance.start_time.isoformat() + "Z"
|
||
duration = instance.duration
|
||
|
||
result = create_zoom_meeting(topic, start_time, duration)
|
||
|
||
if result["status"] == "success":
|
||
instance.meeting_id = result['meeting_details']['meeting_id']
|
||
instance.join_url = result['meeting_details']['join_url']
|
||
instance.host_email = result['meeting_details']['host_email']
|
||
instance.zoom_gateway_response = result['zoom_gateway_response']
|
||
instance.save()
|
||
messages.success(self.request, result["message"])
|
||
|
||
return redirect('/', status=201)
|
||
else:
|
||
messages.error(self.request, result["message"])
|
||
return redirect('/', status=400)
|
||
except Exception as e:
|
||
return redirect('/', status=500)
|
||
|
||
class ZoomMeetingListView(ListView):
|
||
model = ZoomMeeting
|
||
template_name = 'meetings/list_meetings.html'
|
||
context_object_name = 'meetings'
|
||
paginate_by = 10
|
||
|
||
def get_queryset(self):
|
||
queryset = super().get_queryset().order_by('-start_time')
|
||
|
||
# Handle search
|
||
search_query = self.request.GET.get('search', '')
|
||
if search_query:
|
||
queryset = queryset.filter(
|
||
Q(topic__icontains=search_query) |
|
||
Q(meeting_id__icontains=search_query) |
|
||
Q(host_email__icontains=search_query)
|
||
)
|
||
|
||
return queryset
|
||
|
||
def get_context_data(self, **kwargs):
|
||
context = super().get_context_data(**kwargs)
|
||
context['search_query'] = self.request.GET.get('search', '')
|
||
return context
|
||
|
||
class ZoomMeetingDetailsView(DetailView):
|
||
model = ZoomMeeting
|
||
template_name = 'meetings/meeting_details.html'
|
||
context_object_name = 'meeting'
|
||
|
||
class ZoomMeetingUpdateView(UpdateView):
|
||
model = ZoomMeeting
|
||
form_class = ZoomMeetingForm
|
||
context_object_name = 'meeting'
|
||
template_name = 'meetings/update_meeting.html'
|
||
success_url = '/'
|
||
|
||
def form_valid(self, form):
|
||
instance = form.save(commit=False)
|
||
updated_data = {
|
||
'topic': instance.topic,
|
||
'start_time': instance.start_time.isoformat() + "Z",
|
||
'duration': instance.duration
|
||
}
|
||
if instance.start_time < timezone.now():
|
||
messages.error(self.request, "Start time must be in the future.")
|
||
return redirect(f'/update-meeting/{instance.pk}/', status=400)
|
||
|
||
result = update_zoom_meeting(instance.meeting_id, updated_data)
|
||
if result["status"] == "success":
|
||
instance.save()
|
||
messages.success(self.request, result["message"])
|
||
return redirect(reverse('meeting_details', kwargs={'pk': instance.pk}))
|
||
else:
|
||
messages.error(self.request, result["message"])
|
||
return redirect(reverse('meeting_details', kwargs={'pk': instance.pk}))
|
||
|
||
def ZoomMeetingDeleteView(request, pk):
|
||
meeting = get_object_or_404(ZoomMeeting, pk=pk)
|
||
meeting_id = meeting.meeting_id
|
||
try:
|
||
result = delete_zoom_meeting(meeting_id)
|
||
if result["status"] == "success":
|
||
meeting.delete()
|
||
messages.success(request, result["message"])
|
||
else:
|
||
messages.error(request, result["message"])
|
||
return redirect('/')
|
||
except Exception as e:
|
||
messages.error(request, str(e))
|
||
return redirect('/')
|
||
|
||
|
||
#Job Posting
|
||
def job_list(request):
|
||
"""Display the list of job postings order by creation date descending"""
|
||
jobs=JobPosting.objects.all().order_by('-created_at')
|
||
|
||
# Filter by status if provided
|
||
status=request.GET.get('status')
|
||
if status:
|
||
jobs=jobs.filter(status=status)
|
||
|
||
#pagination
|
||
paginator=Paginator(jobs,10) # Show 10 jobs per page
|
||
page_number=request.GET.get('page')
|
||
page_obj=paginator.get_page(page_number)
|
||
return render(request, 'jobs/job_list.html', {
|
||
'page_obj': page_obj,
|
||
'status_filter': status
|
||
})
|
||
|
||
|
||
def create_job(request):
|
||
"""Create a new job posting"""
|
||
if request.method=='POST':
|
||
|
||
form=JobPostingForm(request.POST,is_anonymous_user=not request.user.is_authenticated)
|
||
#to check user is authenticated or not
|
||
if form.is_valid():
|
||
try:
|
||
job=form.save(commit=False)
|
||
if request.user.is_authenticated:
|
||
job.created_by=request.user.get_full_name() or request.user.username
|
||
else:
|
||
job.created_by=request.POST.get('created_by','').strip()
|
||
if not job.created_by:
|
||
job.created_by="University Administrator"
|
||
job.save()
|
||
messages.success(request,f'Job "{job.title}" created successfully!')
|
||
return redirect('job_list')
|
||
except Exception as e:
|
||
logger.error(f"Error creating job: {e}")
|
||
messages.error(request,f"Error creating job: {e}")
|
||
else:
|
||
messages.error(request, f'Please correct the errors below.{form.errors}')
|
||
else:
|
||
form=JobPostingForm(is_anonymous_user=not request.user.is_authenticated)
|
||
return render(request,'jobs/create_job.html',{'form':form})
|
||
|
||
|
||
|
||
|
||
|
||
def edit_job(request,slug):
|
||
"""Edit an existing job posting"""
|
||
if request.method=='POST':
|
||
job=get_object_or_404(JobPosting,slug=slug)
|
||
form=JobPostingForm(request.POST,instance=job,is_anonymous_user=not request.user.is_authenticated)
|
||
if form.is_valid():
|
||
try:
|
||
job=form.save(commit=False)
|
||
if request.user.is_authenticated:
|
||
job.created_by=request.user.get_full_name() or request.user.username
|
||
else:
|
||
job.created_by=request.POST.get('created_by','').strip()
|
||
if not job.created_by:
|
||
job.created_by="University Administrator"
|
||
job.save()
|
||
messages.success(request,f'Job "{job.title}" updated successfully!')
|
||
return redirect('job_list')
|
||
except Exception as e:
|
||
logger.error(f"Error updating job: {e}")
|
||
messages.error(request,f"Error updating job: {e}")
|
||
else:
|
||
messages.error(request, 'Please correct the errors below.')
|
||
else:
|
||
job=get_object_or_404(JobPosting,slug=slug)
|
||
form=JobPostingForm(instance=job,is_anonymous_user=not request.user.is_authenticated)
|
||
return render(request,'jobs/edit_job.html',{'form':form,'job':job})
|
||
|
||
def job_detail(request, slug):
|
||
"""View details of a specific job"""
|
||
job = get_object_or_404(JobPosting, slug=slug)
|
||
|
||
# Get all candidates for this job, ordered by most recent
|
||
candidates = job.candidates.all().order_by('-created_at')
|
||
|
||
# Count candidates by stage for summary statistics
|
||
total_candidates = candidates.count()
|
||
applied_count = candidates.filter(stage='Applied').count()
|
||
interview_count = candidates.filter(stage='Interview').count()
|
||
offer_count = candidates.filter(stage='Offer').count()
|
||
|
||
context = {
|
||
'job': job,
|
||
'candidates': candidates,
|
||
'total_candidates': total_candidates,
|
||
'applied_count': applied_count,
|
||
'interview_count': interview_count,
|
||
'offer_count': offer_count,
|
||
}
|
||
return render(request, 'jobs/job_detail.html', context)
|
||
|
||
|
||
# job detail facing the candidate:
|
||
def job_detail_candidate(request,slug):
|
||
job=get_object_or_404(JobPosting,slug=slug)
|
||
return render(request,'jobs/job_detail_candidate.html',{'job':job})
|
||
|
||
def post_to_linkedin(request,slug):
|
||
"""Post a job to LinkedIn"""
|
||
job=get_object_or_404(JobPosting,slug=slug)
|
||
if job.status!='ACTIVE':
|
||
messages.info(request,'Only active jobs can be posted to LinkedIn.')
|
||
return redirect('job_list')
|
||
|
||
if request.method=='POST':
|
||
try:
|
||
# Check if user is authenticated with LinkedIn
|
||
if 'linkedin_access_token' not in request.session:
|
||
messages.error(request,'Please authenticate with LinkedIn first.')
|
||
return redirect('linkedin_login')
|
||
|
||
# Clear previous LinkedIn data for re-posting
|
||
job.posted_to_linkedin=False
|
||
job.linkedin_post_id=''
|
||
job.linkedin_post_url=''
|
||
job.linkedin_post_status=''
|
||
job.linkedin_posted_at=None
|
||
job.save()
|
||
|
||
# Initialize LinkedIn service
|
||
service=LinkedInService()
|
||
service.access_token=request.session['linkedin_access_token']
|
||
|
||
# Post to LinkedIn
|
||
result=service.create_job_post(job)
|
||
if result['success']:
|
||
# Update job with LinkedIn info
|
||
job.posted_to_linkedin=True
|
||
job.linkedin_post_id=result['post_id']
|
||
job.linkedin_post_url=result['post_url']
|
||
job.linkedin_post_status='SUCCESS'
|
||
job.linkedin_posted_at=timezone.now()
|
||
job.save()
|
||
|
||
messages.success(request,'Job posted to LinkedIn successfully!')
|
||
else:
|
||
error_msg=result.get('error','Unknown error')
|
||
job.linkedin_post_status=f'ERROR: {error_msg}'
|
||
job.save()
|
||
messages.error(request,f'Error posting to LinkedIn: {error_msg}')
|
||
|
||
except Exception as e:
|
||
logger.error(f"Error in post_to_linkedin: {e}")
|
||
job.linkedin_post_status = f'ERROR: {str(e)}'
|
||
job.save()
|
||
messages.error(request, f'Error posting to LinkedIn: {e}')
|
||
|
||
return redirect('job_detail', slug=job.slug)
|
||
|
||
def linkedin_login(request):
|
||
"""Redirect to LinkedIn OAuth"""
|
||
service=LinkedInService()
|
||
auth_url=service.get_auth_url()
|
||
"""
|
||
It creates a special URL that:
|
||
Sends the user to LinkedIn to log in
|
||
Asks the user to grant your app permission to post on their behalf
|
||
Tells LinkedIn where to send the user back after they approve (your redirect_uri)
|
||
http://yoursite.com/linkedin/callback/?code=TEMPORARY_CODE_HERE
|
||
"""
|
||
return redirect(auth_url)
|
||
|
||
|
||
def linkedin_callback(request):
|
||
"""Handle LinkedIn OAuth callback"""
|
||
code=request.GET.get('code')
|
||
if not code:
|
||
messages.error(request,'No authorization code received from LinkedIn.')
|
||
return redirect('job_list')
|
||
|
||
try:
|
||
service=LinkedInService()
|
||
#get_access_token(code)->It makes a POST request to LinkedIn’s token endpoint with parameters
|
||
access_token=service.get_access_token(code)
|
||
request.session['linkedin_access_token']=access_token
|
||
request.session['linkedin_authenticated']=True
|
||
settings.LINKEDIN_IS_CONNECTED = True
|
||
messages.success(request,'Successfully authenticated with LinkedIn!')
|
||
except Exception as e:
|
||
logger.error(f"LinkedIn authentication error: {e}")
|
||
messages.error(request,f'LinkedIn authentication failed: {e}')
|
||
|
||
return redirect('job_list')
|
||
|
||
|
||
#applicant views
|
||
def applicant_job_detail(request,slug):
|
||
"""View job details for applicants"""
|
||
job=get_object_or_404(JobPosting,slug=slug,status='ACTIVE')
|
||
return render(request,'jobs/applicant_job_detail.html',{'job':job})
|
||
|
||
|
||
# Form Preview Views
|
||
# from django.http import JsonResponse
|
||
# from django.views.decorators.csrf import csrf_exempt
|
||
# from django.core.paginator import Paginator
|
||
# from django.contrib.auth.decorators import login_required
|
||
# import json
|
||
|
||
# def form_list(request):
|
||
# """Display list of all available forms"""
|
||
# forms = Form.objects.filter(is_active=True).order_by('-created_at')
|
||
|
||
# # Pagination
|
||
# paginator = Paginator(forms, 12)
|
||
# page_number = request.GET.get('page')
|
||
# page_obj = paginator.get_page(page_number)
|
||
|
||
# return render(request, 'forms/form_list.html', {
|
||
# 'page_obj': page_obj
|
||
# })
|
||
|
||
# def form_preview(request, form_id):
|
||
# """Display form preview for end users"""
|
||
# form = get_object_or_404(Form, id=form_id, is_active=True)
|
||
|
||
# # Get submission count for analytics
|
||
# submission_count = form.submissions.count()
|
||
|
||
# return render(request, 'forms/form_preview.html', {
|
||
# 'form': form,
|
||
# 'submission_count': submission_count,
|
||
# 'is_embed': request.GET.get('embed', 'false') == 'true'
|
||
# })
|
||
|
||
# @csrf_exempt
|
||
# def form_submit(request, form_id):
|
||
# """Handle form submission via AJAX"""
|
||
# if request.method != 'POST':
|
||
# return JsonResponse({'success': False, 'error': 'Only POST method allowed'}, status=405)
|
||
|
||
# form = get_object_or_404(Form, id=form_id, is_active=True)
|
||
|
||
# try:
|
||
# # Parse form data
|
||
# submission_data = {}
|
||
# files = {}
|
||
|
||
# # Process regular form fields
|
||
# for key, value in request.POST.items():
|
||
# if key != 'csrfmiddlewaretoken':
|
||
# submission_data[key] = value
|
||
|
||
# # Process file uploads
|
||
# for key, file in request.FILES.items():
|
||
# if file:
|
||
# files[key] = file
|
||
|
||
# # Create form submission
|
||
# submission = FormSubmission.objects.create(
|
||
# form=form,
|
||
# submission_data=submission_data,
|
||
# ip_address=request.META.get('REMOTE_ADDR'),
|
||
# user_agent=request.META.get('HTTP_USER_AGENT', '')
|
||
# )
|
||
|
||
# # Handle file uploads
|
||
# for field_id, file in files.items():
|
||
# UploadedFile.objects.create(
|
||
# submission=submission,
|
||
# field_id=field_id,
|
||
# file=file,
|
||
# original_filename=file.name
|
||
# )
|
||
|
||
# # TODO: Send email notification if configured
|
||
|
||
# return JsonResponse({
|
||
# 'success': True,
|
||
# 'message': 'Form submitted successfully!',
|
||
# 'submission_id': submission.id
|
||
# })
|
||
|
||
# except Exception as e:
|
||
# logger.error(f"Error submitting form {form_id}: {e}")
|
||
# return JsonResponse({
|
||
# 'success': False,
|
||
# 'error': 'An error occurred while submitting the form. Please try again.'
|
||
# }, status=500)
|
||
|
||
# def form_embed(request, form_id):
|
||
# """Display embeddable version of form"""
|
||
# form = get_object_or_404(Form, id=form_id, is_active=True)
|
||
|
||
# return render(request, 'forms/form_embed.html', {
|
||
# 'form': form,
|
||
# 'is_embed': True
|
||
# })
|
||
|
||
# @login_required
|
||
# def save_form_builder(request):
|
||
# """Save form from builder to database"""
|
||
# if request.method != 'POST':
|
||
# return JsonResponse({'success': False, 'error': 'Only POST method allowed'}, status=405)
|
||
|
||
# try:
|
||
# data = json.loads(request.body)
|
||
# form_data = data.get('form', {})
|
||
|
||
# # Check if this is an update or create
|
||
# form_id = data.get('form_id')
|
||
|
||
# if form_id:
|
||
# # Update existing form
|
||
# form = Form.objects.get(id=form_id, created_by=request.user)
|
||
# form.title = form_data.get('title', 'Untitled Form')
|
||
# form.description = form_data.get('description', '')
|
||
# form.structure = form_data
|
||
# form.save()
|
||
# else:
|
||
# # Create new form
|
||
# form = Form.objects.create(
|
||
# title=form_data.get('title', 'Untitled Form'),
|
||
# description=form_data.get('description', ''),
|
||
# structure=form_data,
|
||
# created_by=request.user
|
||
# )
|
||
|
||
# return JsonResponse({
|
||
# 'success': True,
|
||
# 'form_id': form.id,
|
||
# 'message': 'Form saved successfully!'
|
||
# })
|
||
|
||
# except json.JSONDecodeError:
|
||
# return JsonResponse({
|
||
# 'success': False,
|
||
# 'error': 'Invalid JSON data'
|
||
# }, status=400)
|
||
# except Exception as e:
|
||
# logger.error(f"Error saving form: {e}")
|
||
# return JsonResponse({
|
||
# 'success': False,
|
||
# 'error': 'An error occurred while saving the form'
|
||
# }, status=500)
|
||
|
||
# @login_required
|
||
# def load_form(request, form_id):
|
||
# """Load form data for editing in builder"""
|
||
# try:
|
||
# form = get_object_or_404(Form, id=form_id, created_by=request.user)
|
||
|
||
# return JsonResponse({
|
||
# 'success': True,
|
||
# 'form': {
|
||
# 'id': form.id,
|
||
# 'title': form.title,
|
||
# 'description': form.description,
|
||
# 'structure': form.structure
|
||
# }
|
||
# })
|
||
|
||
# except Exception as e:
|
||
# logger.error(f"Error loading form {form_id}: {e}")
|
||
# return JsonResponse({
|
||
# 'success': False,
|
||
# 'error': 'An error occurred while loading the form'
|
||
# }, status=500)
|
||
|
||
# @csrf_exempt
|
||
# def update_form_builder(request, form_id):
|
||
# """Update existing form from builder"""
|
||
# if request.method != 'POST':
|
||
# return JsonResponse({'success': False, 'error': 'Only POST method allowed'}, status=405)
|
||
|
||
# try:
|
||
# form = get_object_or_404(Form, id=form_id)
|
||
|
||
# # Check if user has permission to edit this form
|
||
# if form.created_by != request.user:
|
||
# return JsonResponse({
|
||
# 'success': False,
|
||
# 'error': 'You do not have permission to edit this form'
|
||
# }, status=403)
|
||
|
||
# data = json.loads(request.body)
|
||
# form_data = data.get('form', {})
|
||
|
||
# # Update form
|
||
# form.title = form_data.get('title', 'Untitled Form')
|
||
# form.description = form_data.get('description', '')
|
||
# form.structure = form_data
|
||
# form.save()
|
||
|
||
# return JsonResponse({
|
||
# 'success': True,
|
||
# 'form_id': form.id,
|
||
# 'message': 'Form updated successfully!'
|
||
# })
|
||
|
||
# except json.JSONDecodeError:
|
||
# return JsonResponse({
|
||
# 'success': False,
|
||
# 'error': 'Invalid JSON data'
|
||
# }, status=400)
|
||
# except Exception as e:
|
||
# logger.error(f"Error updating form {form_id}: {e}")
|
||
# return JsonResponse({
|
||
# 'success': False,
|
||
# 'error': 'An error occurred while updating the form'
|
||
# }, status=500)
|
||
|
||
# def edit_form(request, form_id):
|
||
# """Display form edit page"""
|
||
# form = get_object_or_404(Form, id=form_id)
|
||
|
||
# # Check if user has permission to edit this form
|
||
# if form.created_by != request.user:
|
||
# messages.error(request, 'You do not have permission to edit this form.')
|
||
# return redirect('form_list')
|
||
|
||
# return render(request, 'forms/edit_form.html', {
|
||
# 'form': form
|
||
# })
|
||
|
||
# def form_submissions(request, form_id):
|
||
# """View submissions for a specific form"""
|
||
# form = get_object_or_404(Form, id=form_id, created_by=request.user)
|
||
# submissions = form.submissions.all().order_by('-submitted_at')
|
||
|
||
# # Pagination
|
||
# paginator = Paginator(submissions, 20)
|
||
# page_number = request.GET.get('page')
|
||
# page_obj = paginator.get_page(page_number)
|
||
|
||
# return render(request, 'forms/form_submissions.html', {
|
||
# 'form': form,
|
||
# 'page_obj': page_obj
|
||
# })
|
||
|
||
|
||
@ensure_csrf_cookie
|
||
def form_builder(request, template_id=None):
|
||
"""Render the form builder interface"""
|
||
context = {}
|
||
if template_id:
|
||
template = get_object_or_404(FormTemplate, id=template_id, created_by=request.user)
|
||
context['template_id'] = template.id
|
||
context['template_name'] = template.name
|
||
return render(request,'forms/form_builder.html',context)
|
||
|
||
|
||
@csrf_exempt
|
||
@require_http_methods(["POST"])
|
||
def save_form_template(request):
|
||
"""Save a new or existing form template"""
|
||
try:
|
||
data = json.loads(request.body)
|
||
template_name = data.get('name', 'Untitled Form')
|
||
stages_data = data.get('stages', [])
|
||
template_id = data.get('template_id')
|
||
|
||
if template_id:
|
||
# Update existing template
|
||
template = get_object_or_404(FormTemplate, id=template_id, created_by=request.user)
|
||
template.name = template_name
|
||
template.save()
|
||
# Clear existing stages and fields
|
||
template.stages.all().delete()
|
||
else:
|
||
# Create new template
|
||
template = FormTemplate.objects.create(
|
||
name=template_name,
|
||
created_by=request.user
|
||
)
|
||
|
||
# Create stages and fields
|
||
for stage_order, stage_data in enumerate(stages_data):
|
||
stage = FormStage.objects.create(
|
||
template=template,
|
||
name=stage_data['name'],
|
||
order=stage_order,
|
||
is_predefined=stage_data.get('predefined', False)
|
||
)
|
||
|
||
for field_order, field_data in enumerate(stage_data['fields']):
|
||
options = field_data.get('options', [])
|
||
if not isinstance(options, list):
|
||
options = []
|
||
|
||
file_types = field_data.get('fileTypes', '')
|
||
max_file_size = field_data.get('maxFileSize', 5)
|
||
|
||
FormField.objects.create(
|
||
stage=stage,
|
||
label=field_data.get('label', ''),
|
||
field_type=field_data.get('type', 'text'),
|
||
placeholder=field_data.get('placeholder', ''),
|
||
required=field_data.get('required', False),
|
||
order=field_order,
|
||
is_predefined=field_data.get('predefined', False),
|
||
options=options,
|
||
file_types=file_types,
|
||
max_file_size=max_file_size
|
||
)
|
||
|
||
return JsonResponse({
|
||
'success': True,
|
||
'template_id': template.id,
|
||
'message': 'Form template saved successfully!'
|
||
})
|
||
except Exception as e:
|
||
return JsonResponse({
|
||
'success': False,
|
||
'error': str(e)
|
||
}, status=400)
|
||
|
||
|
||
@require_http_methods(["GET"])
|
||
def load_form_template(request, template_id):
|
||
"""Load an existing form template"""
|
||
template = get_object_or_404(FormTemplate, id=template_id, created_by=request.user)
|
||
|
||
stages = []
|
||
for stage in template.stages.all():
|
||
fields = []
|
||
for field in stage.fields.all():
|
||
fields.append({
|
||
'id': field.id,
|
||
'type': field.field_type,
|
||
'label': field.label,
|
||
'placeholder': field.placeholder,
|
||
'required': field.required,
|
||
'options': field.options,
|
||
'fileTypes': field.file_types,
|
||
'maxFileSize': field.max_file_size,
|
||
'predefined': field.is_predefined
|
||
})
|
||
stages.append({
|
||
'id': stage.id,
|
||
'name': stage.name,
|
||
'predefined': stage.is_predefined,
|
||
'fields': fields
|
||
})
|
||
|
||
return JsonResponse({
|
||
'success': True,
|
||
'template': {
|
||
'id': template.id,
|
||
'name': template.name,
|
||
'description': template.description,
|
||
'is_active': template.is_active,
|
||
'job': template.job_id if template.job else None,
|
||
'stages': stages
|
||
}
|
||
})
|
||
def form_templates_list(request):
|
||
"""List all form templates for the current user"""
|
||
query = request.GET.get('q', '')
|
||
templates = FormTemplate.objects.filter(created_by=request.user)
|
||
|
||
if query:
|
||
templates = templates.filter(
|
||
Q(name__icontains=query) | Q(description__icontains=query)
|
||
)
|
||
|
||
templates = templates.order_by('-created_at')
|
||
paginator = Paginator(templates, 10) # Show 10 templates per page
|
||
page_number = request.GET.get('page')
|
||
page_obj = paginator.get_page(page_number)
|
||
form = FormTemplateForm()
|
||
form.fields['job'].queryset = JobPosting.objects.filter(form_template__isnull=True)
|
||
context = {
|
||
'templates': page_obj,
|
||
'query': query,
|
||
'form': form
|
||
}
|
||
return render(request, 'forms/form_templates_list.html', context)
|
||
|
||
|
||
def create_form_template(request):
|
||
"""Create a new form template"""
|
||
if request.method == 'POST':
|
||
form = FormTemplateForm(request.POST)
|
||
if form.is_valid():
|
||
template = form.save(commit=False)
|
||
template.created_by = request.user
|
||
template.save()
|
||
|
||
messages.success(request, f'Form template "{template.name}" created successfully!')
|
||
return redirect('form_builder', template_id=template.id)
|
||
else:
|
||
form = FormTemplateForm()
|
||
|
||
return render(request, 'forms/create_form_template.html', {'form': form})
|
||
|
||
@require_http_methods(["GET"])
|
||
def list_form_templates(request):
|
||
"""List all form templates for the current user"""
|
||
templates = FormTemplate.objects.filter(created_by=request.user).values(
|
||
'id', 'name', 'description', 'created_at', 'updated_at'
|
||
)
|
||
return JsonResponse({
|
||
'success': True,
|
||
'templates': list(templates)
|
||
})
|
||
|
||
@require_http_methods(["DELETE"])
|
||
def delete_form_template(request, template_id):
|
||
"""Delete a form template"""
|
||
template = get_object_or_404(FormTemplate, id=template_id, created_by=request.user)
|
||
template.delete()
|
||
return JsonResponse({'success': True, 'message': 'Form template deleted successfully!'})
|
||
|
||
|
||
|
||
def form_wizard_view(request, template_id):
|
||
"""Display the form as a step-by-step wizard"""
|
||
template = get_object_or_404(FormTemplate, id=template_id, is_active=True)
|
||
return render(request, 'forms/form_wizard.html', {'template_id': template_id})
|
||
@require_http_methods(["POST"])
|
||
def submit_form(request, template_id):
|
||
"""Handle form submission"""
|
||
try:
|
||
template = get_object_or_404(FormTemplate, id=template_id)
|
||
print(template)
|
||
|
||
# Create form submission
|
||
submission = FormSubmission.objects.create(
|
||
template=template,
|
||
applicant_name=request.POST.get('applicant_name', ''),
|
||
applicant_email=request.POST.get('applicant_email', '')
|
||
)
|
||
|
||
# Process field responses
|
||
for field_id, value in request.POST.items():
|
||
if field_id.startswith('field_'):
|
||
actual_field_id = field_id.replace('field_', '')
|
||
try:
|
||
field = FormField.objects.get(id=actual_field_id, stage__template=template)
|
||
FieldResponse.objects.create(
|
||
submission=submission,
|
||
field=field,
|
||
value=value if value else None
|
||
)
|
||
except FormField.DoesNotExist:
|
||
continue
|
||
|
||
# Handle file uploads
|
||
for field_id, uploaded_file in request.FILES.items():
|
||
if field_id.startswith('field_'):
|
||
actual_field_id = field_id.replace('field_', '')
|
||
try:
|
||
field = FormField.objects.get(id=actual_field_id, stage__template=template)
|
||
FieldResponse.objects.create(
|
||
submission=submission,
|
||
field=field,
|
||
uploaded_file=uploaded_file
|
||
)
|
||
except FormField.DoesNotExist:
|
||
continue
|
||
|
||
return JsonResponse({
|
||
'success': True,
|
||
'message': 'Form submitted successfully!',
|
||
'submission_id': submission.id
|
||
})
|
||
except Exception as e:
|
||
return JsonResponse({
|
||
'success': False,
|
||
'error': str(e)
|
||
}, status=400)
|
||
|
||
def form_submission_details(request, form_id, submission_id):
|
||
"""Display detailed view of a specific form submission"""
|
||
# Get the form template and verify ownership
|
||
form = get_object_or_404(FormTemplate, id=form_id, created_by=request.user)
|
||
|
||
# Get the specific submission
|
||
submission = get_object_or_404(FormSubmission, id=submission_id, template=form)
|
||
|
||
# Get all stages with their fields
|
||
stages = form.stages.prefetch_related('fields').order_by('order')
|
||
|
||
# Get all responses for this submission, ordered by field order
|
||
responses = submission.responses.select_related('field').order_by('field__order')
|
||
|
||
# Group responses by stage
|
||
stage_responses = {}
|
||
for stage in stages:
|
||
stage_responses[stage.id] = {
|
||
'stage': stage,
|
||
'responses': responses.filter(field__stage=stage)
|
||
}
|
||
# print(stages)
|
||
return render(request, 'forms/form_submission_details.html', {
|
||
'form': form,
|
||
'submission': submission,
|
||
'stages': stages,
|
||
'responses': responses,
|
||
'stage_responses': stage_responses
|
||
})
|
||
|
||
|
||
|
||
def schedule_interviews_view(request, slug):
|
||
job = get_object_or_404(JobPosting, slug=slug)
|
||
|
||
if request.method == 'POST':
|
||
form = InterviewScheduleForm(slug, request.POST)
|
||
|
||
# Check if this is a confirmation request
|
||
if 'confirm_schedule' in request.POST:
|
||
# Get the schedule data from session
|
||
schedule_data = request.session.get('interview_schedule_data')
|
||
if not schedule_data:
|
||
messages.error(request, "Session expired. Please try again.")
|
||
return redirect('schedule_interviews', slug=slug)
|
||
|
||
# Create the interview schedule
|
||
schedule = InterviewSchedule.objects.create(
|
||
job=job,
|
||
created_by=request.user,
|
||
**schedule_data
|
||
)
|
||
|
||
# Add candidates to the schedule
|
||
candidates = Candidate.objects.filter(id__in=schedule_data['candidate_ids'])
|
||
schedule.candidates.set(candidates)
|
||
|
||
# Schedule the interviews
|
||
try:
|
||
scheduled_count = schedule_interviews(schedule)
|
||
messages.success(
|
||
request,
|
||
f"Successfully scheduled {scheduled_count} interviews."
|
||
)
|
||
# Clear the session data
|
||
if 'interview_schedule_data' in request.session:
|
||
del request.session['interview_schedule_data']
|
||
return redirect('job_detail', slug=slug)
|
||
except Exception as e:
|
||
messages.error(
|
||
request,
|
||
f"Error scheduling interviews: {str(e)}"
|
||
)
|
||
return redirect('schedule_interviews', slug=slug)
|
||
|
||
# This is the initial form submission
|
||
if form.is_valid():
|
||
# Get the form data
|
||
candidates = form.cleaned_data['candidates']
|
||
start_date = form.cleaned_data['start_date']
|
||
end_date = form.cleaned_data['end_date']
|
||
working_days = form.cleaned_data['working_days']
|
||
start_time = form.cleaned_data['start_time']
|
||
end_time = form.cleaned_data['end_time']
|
||
break_start_time = form.cleaned_data['break_start_time']
|
||
break_end_time = form.cleaned_data['break_end_time']
|
||
interview_duration = form.cleaned_data['interview_duration']
|
||
buffer_time = form.cleaned_data['buffer_time']
|
||
|
||
# Create a temporary schedule object (not saved to DB)
|
||
temp_schedule = InterviewSchedule(
|
||
job=job,
|
||
start_date=start_date,
|
||
end_date=end_date,
|
||
working_days=working_days,
|
||
start_time=start_time,
|
||
end_time=end_time,
|
||
break_start_time=break_start_time,
|
||
break_end_time=break_end_time,
|
||
interview_duration=interview_duration,
|
||
buffer_time=buffer_time
|
||
)
|
||
|
||
# Get available slots
|
||
available_slots = get_available_time_slots(temp_schedule)
|
||
|
||
if len(available_slots) < len(candidates):
|
||
messages.error(
|
||
request,
|
||
f"Not enough available slots. Required: {len(candidates)}, Available: {len(available_slots)}"
|
||
)
|
||
return render(request, 'interviews/schedule_interviews.html', {
|
||
'form': form,
|
||
'job': job
|
||
})
|
||
|
||
# Create a preview schedule
|
||
preview_schedule = []
|
||
for i, candidate in enumerate(candidates):
|
||
slot = available_slots[i]
|
||
preview_schedule.append({
|
||
'candidate': candidate,
|
||
'date': slot['date'],
|
||
'time': slot['time']
|
||
})
|
||
|
||
# Save the form data to session for later use
|
||
schedule_data = {
|
||
'start_date': start_date.isoformat(),
|
||
'end_date': end_date.isoformat(),
|
||
'working_days': working_days,
|
||
'start_time': start_time.isoformat(),
|
||
'end_time': end_time.isoformat(),
|
||
'break_start_time': break_start_time.isoformat() if break_start_time else None,
|
||
'break_end_time': break_end_time.isoformat() if break_end_time else None,
|
||
'interview_duration': interview_duration,
|
||
'buffer_time': buffer_time,
|
||
'candidate_ids': [c.id for c in candidates]
|
||
}
|
||
request.session['interview_schedule_data'] = schedule_data
|
||
|
||
# Render the preview page
|
||
return render(request, 'interviews/preview_schedule.html', {
|
||
'job': job,
|
||
'schedule': preview_schedule,
|
||
'start_date': start_date,
|
||
'end_date': end_date,
|
||
'working_days': working_days,
|
||
'start_time': start_time,
|
||
'end_time': end_time,
|
||
'break_start_time': break_start_time,
|
||
'break_end_time': break_end_time,
|
||
'interview_duration': interview_duration,
|
||
'buffer_time': buffer_time
|
||
})
|
||
else:
|
||
form = InterviewScheduleForm(slug=slug)
|
||
|
||
return render(request, 'interviews/schedule_interviews.html', {
|
||
'form': form,
|
||
'job': job
|
||
}) |