more updates
This commit is contained in:
parent
eca1705ff8
commit
edd2d52015
@ -812,7 +812,7 @@ class NoteForm(forms.ModelForm):
|
|||||||
),
|
),
|
||||||
}
|
}
|
||||||
labels = {
|
labels = {
|
||||||
"content": _("Comment"),
|
"content": _("Note"),
|
||||||
}
|
}
|
||||||
|
|
||||||
# def __init__(self, *args, **kwargs):
|
# def __init__(self, *args, **kwargs):
|
||||||
@ -2186,7 +2186,7 @@ class MessageForm(forms.ModelForm):
|
|||||||
self.fields["job"].queryset = JobPosting.objects.filter(
|
self.fields["job"].queryset = JobPosting.objects.filter(
|
||||||
id__in=job_ids
|
id__in=job_ids
|
||||||
).order_by("-created_at")
|
).order_by("-created_at")
|
||||||
|
|
||||||
print("Agency user job queryset:", self.fields["job"].queryset)
|
print("Agency user job queryset:", self.fields["job"].queryset)
|
||||||
elif self.user.user_type == "candidate":
|
elif self.user.user_type == "candidate":
|
||||||
# Candidates can only see jobs they applied for
|
# Candidates can only see jobs they applied for
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-27 15:36
|
# Generated by Django 5.2.6 on 2025-12-02 10:27
|
||||||
|
|
||||||
import django.contrib.auth.models
|
import django.contrib.auth.models
|
||||||
import django.contrib.auth.validators
|
import django.contrib.auth.validators
|
||||||
@ -31,6 +31,18 @@ class Migration(migrations.Migration):
|
|||||||
('end_time', models.TimeField(verbose_name='End Time')),
|
('end_time', models.TimeField(verbose_name='End Time')),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='EmailContent',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('subject', models.CharField(max_length=255, verbose_name='Subject')),
|
||||||
|
('message', django_ckeditor_5.fields.CKEditor5Field(verbose_name='Message Body')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Email Content',
|
||||||
|
'verbose_name_plural': 'Email Contents',
|
||||||
|
},
|
||||||
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='FormStage',
|
name='FormStage',
|
||||||
fields=[
|
fields=[
|
||||||
@ -57,7 +69,6 @@ class Migration(migrations.Migration):
|
|||||||
('slug', django_extensions.db.fields.RandomCharField(blank=True, editable=False, length=8, unique=True, verbose_name='Slug')),
|
('slug', django_extensions.db.fields.RandomCharField(blank=True, editable=False, length=8, unique=True, verbose_name='Slug')),
|
||||||
('location_type', models.CharField(choices=[('Remote', 'Remote (e.g., Zoom, Google Meet)'), ('Onsite', 'In-Person (Physical Location)')], db_index=True, max_length=10, verbose_name='Location Type')),
|
('location_type', models.CharField(choices=[('Remote', 'Remote (e.g., Zoom, Google Meet)'), ('Onsite', 'In-Person (Physical Location)')], db_index=True, max_length=10, verbose_name='Location Type')),
|
||||||
('topic', models.CharField(blank=True, help_text="e.g., 'Zoom Topic: Software Interview' or 'Main Conference Room'", max_length=255, verbose_name='Meeting/Location Topic')),
|
('topic', models.CharField(blank=True, help_text="e.g., 'Zoom Topic: Software Interview' or 'Main Conference Room'", max_length=255, verbose_name='Meeting/Location Topic')),
|
||||||
('details_url', models.URLField(blank=True, max_length=2048, null=True, verbose_name='Meeting/Location URL')),
|
|
||||||
('timezone', models.CharField(default='UTC', max_length=50, verbose_name='Timezone')),
|
('timezone', models.CharField(default='UTC', max_length=50, verbose_name='Timezone')),
|
||||||
('start_time', models.DateTimeField(db_index=True, verbose_name='Start Time')),
|
('start_time', models.DateTimeField(db_index=True, verbose_name='Start Time')),
|
||||||
('duration', models.PositiveIntegerField(verbose_name='Duration (minutes)')),
|
('duration', models.PositiveIntegerField(verbose_name='Duration (minutes)')),
|
||||||
@ -65,6 +76,7 @@ class Migration(migrations.Migration):
|
|||||||
('meeting_id', models.CharField(blank=True, max_length=50, null=True, unique=True, verbose_name='External Meeting ID')),
|
('meeting_id', models.CharField(blank=True, max_length=50, null=True, unique=True, verbose_name='External Meeting ID')),
|
||||||
('password', models.CharField(blank=True, max_length=20, null=True)),
|
('password', models.CharField(blank=True, max_length=20, null=True)),
|
||||||
('zoom_gateway_response', models.JSONField(blank=True, null=True)),
|
('zoom_gateway_response', models.JSONField(blank=True, null=True)),
|
||||||
|
('details_url', models.JSONField(blank=True, null=True)),
|
||||||
('participant_video', models.BooleanField(default=True)),
|
('participant_video', models.BooleanField(default=True)),
|
||||||
('join_before_host', models.BooleanField(default=False)),
|
('join_before_host', models.BooleanField(default=False)),
|
||||||
('host_email', models.CharField(blank=True, max_length=255, null=True)),
|
('host_email', models.CharField(blank=True, max_length=255, null=True)),
|
||||||
@ -278,24 +290,6 @@ class Migration(migrations.Migration):
|
|||||||
'verbose_name_plural': 'Applications',
|
'verbose_name_plural': 'Applications',
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
|
||||||
name='InterviewNote',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')),
|
|
||||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')),
|
|
||||||
('slug', django_extensions.db.fields.RandomCharField(blank=True, editable=False, length=8, unique=True, verbose_name='Slug')),
|
|
||||||
('note_type', models.CharField(choices=[('Feedback', 'Candidate Feedback'), ('Logistics', 'Logistical Note'), ('General', 'General Comment')], default='Feedback', max_length=50, verbose_name='Note Type')),
|
|
||||||
('content', django_ckeditor_5.fields.CKEditor5Field(verbose_name='Content/Feedback')),
|
|
||||||
('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='interview_notes', to=settings.AUTH_USER_MODEL, verbose_name='Author')),
|
|
||||||
('interview', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notes', to='recruitment.interview', verbose_name='Scheduled Interview')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Interview Note',
|
|
||||||
'verbose_name_plural': 'Interview Notes',
|
|
||||||
'ordering': ['created_at'],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='JobPosting',
|
name='JobPosting',
|
||||||
fields=[
|
fields=[
|
||||||
@ -363,12 +357,15 @@ class Migration(migrations.Migration):
|
|||||||
('start_date', models.DateField(db_index=True, verbose_name='Start Date')),
|
('start_date', models.DateField(db_index=True, verbose_name='Start Date')),
|
||||||
('end_date', models.DateField(db_index=True, verbose_name='End Date')),
|
('end_date', models.DateField(db_index=True, verbose_name='End Date')),
|
||||||
('working_days', models.JSONField(verbose_name='Working Days')),
|
('working_days', models.JSONField(verbose_name='Working Days')),
|
||||||
|
('topic', models.CharField(max_length=255, verbose_name='Interview Topic')),
|
||||||
('start_time', models.TimeField(verbose_name='Start Time')),
|
('start_time', models.TimeField(verbose_name='Start Time')),
|
||||||
('end_time', models.TimeField(verbose_name='End Time')),
|
('end_time', models.TimeField(verbose_name='End Time')),
|
||||||
('break_start_time', models.TimeField(blank=True, null=True, verbose_name='Break Start Time')),
|
('break_start_time', models.TimeField(blank=True, null=True, verbose_name='Break Start Time')),
|
||||||
('break_end_time', models.TimeField(blank=True, null=True, verbose_name='Break End Time')),
|
('break_end_time', models.TimeField(blank=True, null=True, verbose_name='Break End Time')),
|
||||||
('interview_duration', models.PositiveIntegerField(verbose_name='Interview Duration (minutes)')),
|
('interview_duration', models.PositiveIntegerField(verbose_name='Interview Duration (minutes)')),
|
||||||
('buffer_time', models.PositiveIntegerField(default=0, verbose_name='Buffer Time (minutes)')),
|
('buffer_time', models.PositiveIntegerField(default=0, verbose_name='Buffer Time (minutes)')),
|
||||||
|
('schedule_interview_type', models.CharField(choices=[('Remote', 'Remote (e.g., Zoom)'), ('Onsite', 'In-Person (Physical Location)')], default='Onsite', max_length=10, verbose_name='Interview Type')),
|
||||||
|
('physical_address', models.CharField(blank=True, max_length=255, null=True)),
|
||||||
('applications', models.ManyToManyField(blank=True, related_name='interview_schedules', to='recruitment.application')),
|
('applications', models.ManyToManyField(blank=True, related_name='interview_schedules', to='recruitment.application')),
|
||||||
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
('interview', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='schedule_templates', to='recruitment.interview', verbose_name='Location Template (Zoom/Onsite)')),
|
('interview', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='schedule_templates', to='recruitment.interview', verbose_name='Location Template (Zoom/Onsite)')),
|
||||||
@ -438,6 +435,25 @@ class Migration(migrations.Migration):
|
|||||||
'ordering': ['-created_at'],
|
'ordering': ['-created_at'],
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Note',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')),
|
||||||
|
('slug', django_extensions.db.fields.RandomCharField(blank=True, editable=False, length=8, unique=True, verbose_name='Slug')),
|
||||||
|
('note_type', models.CharField(choices=[('Feedback', 'Candidate Feedback'), ('Logistics', 'Logistical Note'), ('General', 'General Comment')], default='Feedback', max_length=50, verbose_name='Note Type')),
|
||||||
|
('content', django_ckeditor_5.fields.CKEditor5Field(verbose_name='Content/Feedback')),
|
||||||
|
('application', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='notes', to='recruitment.application', verbose_name='Application')),
|
||||||
|
('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='interview_notes', to=settings.AUTH_USER_MODEL, verbose_name='Author')),
|
||||||
|
('interview', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='notes', to='recruitment.interview', verbose_name='Scheduled Interview')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Interview Note',
|
||||||
|
'verbose_name_plural': 'Interview Notes',
|
||||||
|
'ordering': ['created_at'],
|
||||||
|
},
|
||||||
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Notification',
|
name='Notification',
|
||||||
fields=[
|
fields=[
|
||||||
@ -478,7 +494,7 @@ class Migration(migrations.Migration):
|
|||||||
('profile_image', models.ImageField(blank=True, null=True, upload_to='profile_pic/', validators=[recruitment.validators.validate_image_size], verbose_name='Profile Image')),
|
('profile_image', models.ImageField(blank=True, null=True, upload_to='profile_pic/', validators=[recruitment.validators.validate_image_size], verbose_name='Profile Image')),
|
||||||
('linkedin_profile', models.URLField(blank=True, null=True, verbose_name='LinkedIn Profile URL')),
|
('linkedin_profile', models.URLField(blank=True, null=True, verbose_name='LinkedIn Profile URL')),
|
||||||
('agency', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='recruitment.hiringagency', verbose_name='Hiring Agency')),
|
('agency', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='recruitment.hiringagency', verbose_name='Hiring Agency')),
|
||||||
('user', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='person_profile', to=settings.AUTH_USER_MODEL, verbose_name='User Account')),
|
('user', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='person_profile', to=settings.AUTH_USER_MODEL, verbose_name='User Account')),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'Person',
|
'verbose_name': 'Person',
|
||||||
|
|||||||
@ -1,20 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-28 10:24
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('recruitment', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='person',
|
|
||||||
name='user',
|
|
||||||
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='person_profile', to=settings.AUTH_USER_MODEL, verbose_name='User Account'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 5.2.6 on 2025-12-01 12:30
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('recruitment', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='bulkinterviewtemplate',
|
|
||||||
name='schedule_interview_type',
|
|
||||||
field=models.CharField(choices=[('Remote', 'Remote (e.g., Zoom)'), ('Onsite', 'In-Person (Physical Location)')], default='Onsite', max_length=10, verbose_name='Interview Type'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 5.2.6 on 2025-12-01 13:02
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('recruitment', '0002_bulkinterviewtemplate_schedule_interview_type'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='bulkinterviewtemplate',
|
|
||||||
name='physical_address',
|
|
||||||
field=models.CharField(blank=True, max_length=255, null=True),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
# Generated by Django 5.2.6 on 2025-12-01 14:43
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('recruitment', '0003_bulkinterviewtemplate_physical_address'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='bulkinterviewtemplate',
|
|
||||||
name='topic',
|
|
||||||
field=models.CharField(default='', max_length=255, verbose_name='Interview Topic'),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
# Generated by Django 5.2.6 on 2025-12-02 10:08
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('recruitment', '0002_alter_person_user'),
|
|
||||||
('recruitment', '0004_bulkinterviewtemplate_topic'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
]
|
|
||||||
@ -27,9 +27,9 @@ except ImportError:
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
OPENROUTER_API_KEY ='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'
|
OPENROUTER_API_KEY ='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'
|
||||||
# OPENROUTER_MODEL = 'qwen/qwen-2.5-72b-instruct:free'
|
OPENROUTER_MODEL = 'qwen/qwen-2.5-72b-instruct'
|
||||||
|
|
||||||
OPENROUTER_MODEL = 'openai/gpt-oss-20b'
|
# OPENROUTER_MODEL = 'qwen/qwen-2.5-7b-instruct'
|
||||||
# OPENROUTER_MODEL = 'openai/gpt-oss-20b'
|
# OPENROUTER_MODEL = 'openai/gpt-oss-20b'
|
||||||
# OPENROUTER_MODEL = 'mistralai/mistral-small-3.2-24b-instruct:free'
|
# OPENROUTER_MODEL = 'mistralai/mistral-small-3.2-24b-instruct:free'
|
||||||
|
|
||||||
@ -623,7 +623,8 @@ def handle_resume_parsing_and_scoring(pk: int):
|
|||||||
}}
|
}}
|
||||||
|
|
||||||
If a top-level key or its required fields are missing, set the field to null, an empty list, or an empty object as appropriate.
|
If a top-level key or its required fields are missing, set the field to null, an empty list, or an empty object as appropriate.
|
||||||
|
Be Clear and Direct Avoid overly indirect politeness which can add confusion.
|
||||||
|
Be strict,objective and concise and critical in your responses, and don't give inflated scores to weak candidates.
|
||||||
Output only valid JSON—no markdown, no extra text.
|
Output only valid JSON—no markdown, no extra text.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|||||||
@ -713,7 +713,7 @@ def request_cvs_download(request, slug):
|
|||||||
if not job.applications.exists():
|
if not job.applications.exists():
|
||||||
messages.warning(request, _("No applications found for this job. ZIP file generation skipped."))
|
messages.warning(request, _("No applications found for this job. ZIP file generation skipped."))
|
||||||
return redirect('job_detail', slug=slug)
|
return redirect('job_detail', slug=slug)
|
||||||
|
|
||||||
async_task('recruitment.tasks.generate_and_save_cv_zip', job.id)
|
async_task('recruitment.tasks.generate_and_save_cv_zip', job.id)
|
||||||
|
|
||||||
# Provide user feedback and redirect
|
# Provide user feedback and redirect
|
||||||
@ -4868,7 +4868,7 @@ def message_delete(request, message_id):
|
|||||||
Redirects to the message list on success (either via standard redirect
|
Redirects to the message list on success (either via standard redirect
|
||||||
or HTMX's hx-redirect header).
|
or HTMX's hx-redirect header).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# 1. Retrieve the message
|
# 1. Retrieve the message
|
||||||
# Use select_related to fetch linked objects efficiently for checks/logging
|
# Use select_related to fetch linked objects efficiently for checks/logging
|
||||||
message = get_object_or_404(
|
message = get_object_or_404(
|
||||||
@ -4879,13 +4879,13 @@ def message_delete(request, message_id):
|
|||||||
# Only the sender or recipient can delete the message
|
# Only the sender or recipient can delete the message
|
||||||
if message.sender != request.user and message.recipient != request.user:
|
if message.sender != request.user and message.recipient != request.user:
|
||||||
messages.error(request, "You don't have permission to delete this message.")
|
messages.error(request, "You don't have permission to delete this message.")
|
||||||
|
|
||||||
# HTMX requests should handle redirection via client-side logic (hx-redirect)
|
# HTMX requests should handle redirection via client-side logic (hx-redirect)
|
||||||
if "HX-Request" in request.headers:
|
if "HX-Request" in request.headers:
|
||||||
# Returning 403 or 400 is ideal, but 200 with an empty body is often accepted
|
# Returning 403 or 400 is ideal, but 200 with an empty body is often accepted
|
||||||
# by HTMX and the message is shown on the next page/refresh.
|
# by HTMX and the message is shown on the next page/refresh.
|
||||||
return HttpResponse(status=403)
|
return HttpResponse(status=403)
|
||||||
|
|
||||||
# Standard navigation redirect
|
# Standard navigation redirect
|
||||||
return redirect("message_list")
|
return redirect("message_list")
|
||||||
|
|
||||||
@ -4899,7 +4899,7 @@ def message_delete(request, message_id):
|
|||||||
# 1. Set the HTMX response header for redirection
|
# 1. Set the HTMX response header for redirection
|
||||||
response = HttpResponse(status=200)
|
response = HttpResponse(status=200)
|
||||||
response["HX-Redirect"] = reverse("message_list") # <--- EXPLICIT HEADER
|
response["HX-Redirect"] = reverse("message_list") # <--- EXPLICIT HEADER
|
||||||
return response
|
return response
|
||||||
|
|
||||||
# Standard navigation fallback
|
# Standard navigation fallback
|
||||||
return redirect("message_list")
|
return redirect("message_list")
|
||||||
@ -5069,7 +5069,8 @@ def document_upload(request, slug):
|
|||||||
if upload_target == 'person':
|
if upload_target == 'person':
|
||||||
return redirect("applicant_portal_dashboard")
|
return redirect("applicant_portal_dashboard")
|
||||||
else:
|
else:
|
||||||
return redirect("applicant_application_detail", slug=application.slug)
|
return render(request, 'recruitment/application_detail.html', {'application': application})
|
||||||
|
# return redirect("application_detail", slug=application.slug)
|
||||||
|
|
||||||
# Handle GET request for AJAX
|
# Handle GET request for AJAX
|
||||||
if request.headers.get("X-Requested-With") == "XMLHttpRequest":
|
if request.headers.get("X-Requested-With") == "XMLHttpRequest":
|
||||||
@ -5081,7 +5082,6 @@ def document_upload(request, slug):
|
|||||||
def document_delete(request, document_id):
|
def document_delete(request, document_id):
|
||||||
"""Delete a document"""
|
"""Delete a document"""
|
||||||
document = get_object_or_404(Document, id=document_id)
|
document = get_object_or_404(Document, id=document_id)
|
||||||
print(document)
|
|
||||||
|
|
||||||
# Initialize variables for redirection outside of the complex logic
|
# Initialize variables for redirection outside of the complex logic
|
||||||
is_htmx = "HX-Request" in request.headers
|
is_htmx = "HX-Request" in request.headers
|
||||||
@ -5144,7 +5144,9 @@ def document_delete(request, document_id):
|
|||||||
if is_htmx or request.headers.get("X-Requested-With") == "XMLHttpRequest":
|
if is_htmx or request.headers.get("X-Requested-With") == "XMLHttpRequest":
|
||||||
# For HTMX, return a 200 OK. The front-end is expected to use hx-swap='outerHTML'
|
# For HTMX, return a 200 OK. The front-end is expected to use hx-swap='outerHTML'
|
||||||
# to remove the element, or hx-redirect to navigate.
|
# to remove the element, or hx-redirect to navigate.
|
||||||
return HttpResponse(status=200)
|
response = HttpResponse(status=200)
|
||||||
|
response["HX-Refresh"] = "true" # Instruct HTMX to refresh the current view
|
||||||
|
return response
|
||||||
|
|
||||||
# --- Standard Navigation Fallback ---
|
# --- Standard Navigation Fallback ---
|
||||||
else:
|
else:
|
||||||
|
|||||||
@ -50,7 +50,7 @@
|
|||||||
<nav class="navbar navbar-expand-lg navbar-dark sticky-top">
|
<nav class="navbar navbar-expand-lg navbar-dark sticky-top">
|
||||||
<div class="container-fluid max-width-1600">
|
<div class="container-fluid max-width-1600">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a class="navbar-brand text-white d-none d-lg-block me-4 pe-4" href="{% url 'dashboard' %}" aria-label="Home">
|
<a class="navbar-brand text-white d-none d-lg-block me-4 pe-4" href="{% url 'dashboard' %}" aria-label="Home">
|
||||||
<img src="{% static 'image/kaauh_green1.png' %}" alt="{% trans 'kaauh logo green bg' %}" style="width: 60px; height: 60px;">
|
<img src="{% static 'image/kaauh_green1.png' %}" alt="{% trans 'kaauh logo green bg' %}" style="width: 60px; height: 60px;">
|
||||||
@ -206,7 +206,7 @@
|
|||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="d-lg-none"><a class="dropdown-item py-2 px-4 d-flex align-items-center text-decoration-none text-teal" href="{% url 'message_list' %}"> <i class="fas fa-envelope fs-5 me-3"></i> <span>{% trans "Messages" %}</span></a></li>
|
<li class="d-lg-none"><a class="dropdown-item py-2 px-4 d-flex align-items-center text-decoration-none text-teal" href="{% url 'message_list' %}"> <i class="fas fa-envelope fs-5 me-3"></i> <span>{% trans "Messages" %}</span></a></li>
|
||||||
{% if request.user.is_authenticated %}
|
{% if request.user.is_authenticated %}
|
||||||
<li><a class="dropdown-item py-2 px-4 d-flex align-items-center text-decoration-none text-teal" href="{% url 'user_detail' request.user.pk %}"><i class="fas fa-user-circle me-3 fs-5"></i> <span>{% trans "My Profile" %}</span></a></li>
|
<li><a class="dropdown-item py-2 px-4 d-flex align-items-center text-decoration-none text-teal" href="{% url 'user_detail' request.user.pk %}"><i class="fas fa-user-circle me-3 fs-5"></i> <span>{% trans "My Profile" %}</span></a></li>
|
||||||
@ -293,10 +293,10 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item me-lg-4">
|
<li class="nav-item me-lg-4">
|
||||||
<a class="nav-link {% if request.resolver_match.url_name == 'list_meetings' %}active{% endif %}" href="{% url 'interview_list' %}">
|
<a class="nav-link {% if request.resolver_match.url_name == 'interview_list' %}active{% endif %}" href="{% url 'interview_list' %}">
|
||||||
<span class="d-flex align-items-center gap-2">
|
<span class="d-flex align-items-center gap-2">
|
||||||
<i class="fas fa-calendar-check me-2"></i>
|
<i class="fas fa-calendar-check me-2"></i>
|
||||||
{% trans "Meetings" %}
|
{% trans "Meetings & interviews" %}
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
{% load file_filters %}
|
{% load file_filters %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
<div class="card shadow-sm">
|
<div class="card shadow-sm">
|
||||||
<div class="card-header bg-white border-bottom d-flex justify-content-between align-items-center">
|
<div class="card-header bg-white border-bottom d-flex justify-content-between align-items-center">
|
||||||
<h5 class="card-title mb-0 text-primary">{% trans "Documents" %}</h5>
|
<h5 class="card-title mb-0 text-primary">{% trans "Documents" %}</h5>
|
||||||
@ -25,12 +26,8 @@
|
|||||||
|
|
||||||
<form
|
<form
|
||||||
method="post"
|
method="post"
|
||||||
|
action="{% url 'application_document_upload' application.slug %}"
|
||||||
enctype="multipart/form-data"
|
enctype="multipart/form-data"
|
||||||
hx-post="{% url 'application_document_upload' application.slug %}"
|
|
||||||
hx-target="#documents-pane"
|
|
||||||
hx-select="#documents-pane"
|
|
||||||
hx-swap="outerHTML"
|
|
||||||
hx-on::after-request="bootstrap.Modal.getInstance(document.getElementById('documentUploadModal')).hide()"
|
|
||||||
>
|
>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
@ -64,7 +61,7 @@
|
|||||||
id="documentDescription"
|
id="documentDescription"
|
||||||
rows="3"
|
rows="3"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
placeholder="{% trans "Optional description..." %}"
|
placeholder='{% trans "Optional description..." %}'
|
||||||
></textarea>
|
></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -101,22 +98,23 @@
|
|||||||
|
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<a
|
<a
|
||||||
href="{% url 'document_download' document.id %}"
|
href="{% url 'document_download' document.id %}"
|
||||||
class="btn btn-sm btn-outline-primary me-2"
|
class="btn btn-sm btn-outline-primary me-2"
|
||||||
title="{% trans "Download" %}"
|
title='{% trans "Download" %}'
|
||||||
>
|
>
|
||||||
<i class="fas fa-download"></i>
|
<i class="fas fa-download"></i>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
{% if user.is_superuser or application.job.assigned_to == user %}
|
{% if user.is_superuser or application.job.assigned_to == user %}
|
||||||
<button
|
<a
|
||||||
|
hx-post="{% url 'document_delete' document.id %}"
|
||||||
|
hx-confirm='{% trans "Are you sure you want to delete" %}'
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-sm btn-outline-danger"
|
class="btn btn-sm btn-outline-danger"
|
||||||
onclick="confirmDelete({{ document.id }}, '{{ document.file.name|filename|default:"Document" }}')"
|
title='{% trans "Delete" %}'
|
||||||
title="{% trans "Delete" %}"
|
|
||||||
>
|
>
|
||||||
<i class="fas fa-trash"></i>
|
<i class="fas fa-trash"></i>
|
||||||
</button>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -131,6 +129,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.hover-bg-light:hover {
|
.hover-bg-light:hover {
|
||||||
background-color: #f8f9fa;
|
background-color: #f8f9fa;
|
||||||
@ -139,7 +138,7 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
function confirmDelete(documentId, fileName) {
|
/*function confirmDelete(documentId, fileName) {
|
||||||
var deletePrefix = "{% trans "Are you sure you want to delete" %}";
|
var deletePrefix = "{% trans "Are you sure you want to delete" %}";
|
||||||
if (confirm(deletePrefix + ' "' + fileName + '"?')) {
|
if (confirm(deletePrefix + ' "' + fileName + '"?')) {
|
||||||
htmx.ajax('POST', `{% url 'document_delete' 0 %}`.replace('0', documentId), {
|
htmx.ajax('POST', `{% url 'document_delete' 0 %}`.replace('0', documentId), {
|
||||||
@ -147,5 +146,16 @@ function confirmDelete(documentId, fileName) {
|
|||||||
swap: 'innerHTML'
|
swap: 'innerHTML'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
function closeUploadModal() {
|
||||||
|
var modalElement = document.getElementById('documentUploadModal');
|
||||||
|
if (modalElement) {
|
||||||
|
var modal = bootstrap.Modal.getInstance(modalElement);
|
||||||
|
if (modal) {
|
||||||
|
modal.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -480,7 +480,7 @@
|
|||||||
|
|
||||||
{# TAB 5 CONTENT: PARSED SUMMARY #}
|
{# TAB 5 CONTENT: PARSED SUMMARY #}
|
||||||
{% if application.parsed_summary %}
|
{% if application.parsed_summary %}
|
||||||
|
|
||||||
<div class="tab-pane fade" id="summary-pane" role="tabpanel" aria-labelledby="summary-tab">
|
<div class="tab-pane fade" id="summary-pane" role="tabpanel" aria-labelledby="summary-tab">
|
||||||
<h5 class="text-primary mb-4">{% trans "AI Generated Summary" %}</h5>
|
<h5 class="text-primary mb-4">{% trans "AI Generated Summary" %}</h5>
|
||||||
<div class="border-start border-primary ps-3 pt-1 pb-1">
|
<div class="border-start border-primary ps-3 pt-1 pb-1">
|
||||||
@ -663,7 +663,7 @@
|
|||||||
<i class="fas fa-eye me-1"></i>
|
<i class="fas fa-eye me-1"></i>
|
||||||
{% trans "View Actual Resume" %}
|
{% trans "View Actual Resume" %}
|
||||||
</a> {% endcomment %}
|
</a> {% endcomment %}
|
||||||
|
|
||||||
<a href="{{ application.resume.url }}" download class="btn btn-outline-primary">
|
<a href="{{ application.resume.url }}" download class="btn btn-outline-primary">
|
||||||
<i class="fas fa-download me-1"></i>
|
<i class="fas fa-download me-1"></i>
|
||||||
{% trans "Download Resume" %}
|
{% trans "Download Resume" %}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user