safe filter

This commit is contained in:
Faheed 2025-10-08 19:28:06 +03:00
parent 3b8ed4c93b
commit 53318a998c
21 changed files with 127 additions and 65 deletions

View File

@ -120,6 +120,8 @@ DATABASES = {
# Password validation # Password validation
# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators # https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [ AUTH_PASSWORD_VALIDATORS = [
{ {
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',

Binary file not shown.

View File

@ -5,7 +5,7 @@ from django.utils import timezone
from .models import ( from .models import (
JobPosting, Candidate, TrainingMaterial, ZoomMeeting, JobPosting, Candidate, TrainingMaterial, ZoomMeeting,
FormTemplate, FormStage, FormField, FormSubmission, FieldResponse, FormTemplate, FormStage, FormField, FormSubmission, FieldResponse,
SharedFormTemplate, Source, HiringAgency, IntegrationLog,InterviewSchedule SharedFormTemplate, Source, HiringAgency, IntegrationLog,InterviewSchedule,Profile
) )
class FormFieldInline(admin.TabularInline): class FormFieldInline(admin.TabularInline):
@ -261,4 +261,5 @@ admin.site.register(FormStage)
admin.site.register(FormField) admin.site.register(FormField)
admin.site.register(FieldResponse) admin.site.register(FieldResponse)
admin.site.register(InterviewSchedule) admin.site.register(InterviewSchedule)
admin.site.register(Profile)
# admin.site.register(HiringAgency) # admin.site.register(HiringAgency)

View File

@ -259,8 +259,9 @@ class JobPostingForm(forms.ModelForm):
'type': 'date' 'type': 'date'
}), }),
'application_instructions': SummernoteWidget(attrs={ 'application_instructions': SummernoteWidget(attrs={
# Removed 'class' and 'rows'
'placeholder': 'Special instructions for applicants (e.g., required documents, reference requirements, etc.)' 'placeholder': 'Special instructions for applicants (e.g., required documents, reference requirements, etc.)',
}), }),
'open_positions': forms.NumberInput(attrs={ 'open_positions': forms.NumberInput(attrs={
'class': 'form-control', 'class': 'form-control',

View File

@ -0,0 +1,24 @@
# Generated by Django 5.2.7 on 2025-10-08 13:01
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('recruitment', '0026_interviewschedule_scheduledinterview'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Profile',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('profile_image', models.ImageField(blank=True, null=True, upload_to='profile_pic/')),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='profile', to=settings.AUTH_USER_MODEL)),
],
),
]

View File

@ -9,6 +9,9 @@ from django.core.exceptions import ValidationError
from django_countries.fields import CountryField from django_countries.fields import CountryField
from django.urls import reverse from django.urls import reverse
class Profile(models.Model):
profile_image=models.ImageField(null=True,blank=True,upload_to='profile_pic/')
user=models.OneToOneField(User,on_delete=models.CASCADE,related_name='profile')
class Base(models.Model): class Base(models.Model):
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('Created at')) created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('Created at'))
updated_at = models.DateTimeField(auto_now=True, verbose_name=_('Updated at')) updated_at = models.DateTimeField(auto_now=True, verbose_name=_('Updated at'))

View File

@ -83,8 +83,7 @@ class ZoomMeetingListView(ListView):
if search_query: if search_query:
queryset = queryset.filter( queryset = queryset.filter(
Q(topic__icontains=search_query) | Q(topic__icontains=search_query) |
Q(meeting_id__icontains=search_query) | Q(meeting_id__icontains=search_query)
Q(host_email__icontains=search_query)
) )
return queryset return queryset
@ -143,27 +142,30 @@ def ZoomMeetingDeleteView(request, pk):
#Job Posting #Job Posting
def job_list(request): # def job_list(request):
"""Display the list of job postings order by creation date descending""" # """Display the list of job postings order by creation date descending"""
jobs=JobPosting.objects.all().order_by('-created_at') # jobs=JobPosting.objects.all().order_by('-created_at')
# Filter by status if provided # # Filter by status if provided
status=request.GET.get('status') # print(f"the request is: {request} ")
if status: # status=request.GET.get('status')
jobs=jobs.filter(status=status) # print(f"DEBUG: Status filter received: {status}")
# if status:
# jobs=jobs.filter(status=status)
#pagination # #pagination
paginator=Paginator(jobs,10) # Show 10 jobs per page # paginator=Paginator(jobs,10) # Show 10 jobs per page
page_number=request.GET.get('page') # page_number=request.GET.get('page')
page_obj=paginator.get_page(page_number) # page_obj=paginator.get_page(page_number)
return render(request, 'jobs/job_list.html', { # return render(request, 'jobs/job_list.html', {
'page_obj': page_obj, # 'page_obj': page_obj,
'status_filter': status # 'status_filter': status
}) # })
def create_job(request): def create_job(request):
"""Create a new job posting""" """Create a new job posting"""
if request.method=='POST': if request.method=='POST':
form=JobPostingForm(request.POST,is_anonymous_user=not request.user.is_authenticated) form=JobPostingForm(request.POST,is_anonymous_user=not request.user.is_authenticated)
@ -761,7 +763,8 @@ def delete_form_template(request, template_id):
def form_wizard_view(request, template_id): def form_wizard_view(request, template_id):
"""Display the form as a step-by-step wizard""" """Display the form as a step-by-step wizard"""
template = get_object_or_404(FormTemplate, id=template_id, is_active=True) template = get_object_or_404(FormTemplate, id=template_id, is_active=True)
return render(request, 'forms/form_wizard.html', {'template_id': template_id}) job_id=template.job.internal_job_id
return render(request, 'forms/form_wizard.html', {'template_id': template_id,'job_id':job_id})
@require_http_methods(["POST"]) @require_http_methods(["POST"])
def submit_form(request, template_id): def submit_form(request, template_id):
"""Handle form submission""" """Handle form submission"""

View File

@ -41,10 +41,15 @@ class JobListView(LoginRequiredMixin, ListView):
# Filter for non-staff users # Filter for non-staff users
if not self.request.user.is_staff: if not self.request.user.is_staff:
queryset = queryset.filter(status='Published') queryset = queryset.filter(status='Published')
status=self.request.GET.get('status')
if status:
queryset=queryset.filter(status=status)
return queryset return queryset
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['search_query'] = self.request.GET.get('search', '') context['search_query'] = self.request.GET.get('search', '')
context['lang'] = get_language() context['lang'] = get_language()
@ -288,7 +293,7 @@ class TrainingListView(LoginRequiredMixin, ListView):
template_name = 'recruitment/training_list.html' template_name = 'recruitment/training_list.html'
context_object_name = 'materials' context_object_name = 'materials'
paginate_by = 10 paginate_by = 10
def get_queryset(self): def get_queryset(self):
queryset = super().get_queryset() queryset = super().get_queryset()
@ -296,8 +301,7 @@ class TrainingListView(LoginRequiredMixin, ListView):
search_query = self.request.GET.get('search', '') search_query = self.request.GET.get('search', '')
if search_query: if search_query:
queryset = queryset.filter( queryset = queryset.filter(
Q(title__icontains=search_query) | Q(title__icontains=search_query)
Q(description__icontains=search_query)
) )
# Filter for non-staff users # Filter for non-staff users

View File

@ -320,11 +320,13 @@
<li class="nav-item me-2"> <li class="nav-item me-2">
<a class="nav-link {% if request.resolver_match.url_name == 'form_templates_list' %}active{% endif %}" href="{% url 'form_templates_list' %}"> <a class="nav-link {% if request.resolver_match.url_name == 'form_templates_list' %}active{% endif %}" href="{% url 'form_templates_list' %}">
<span class="d-flex align-items-center gap-2"> <span class="d-flex align-items-center gap-2">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" stroke="currentColor" stroke-width="2"></path> <path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z" />
</svg> </svg>
{% trans "Form Templates" %} {% trans "Form Templates" %}
</span> </span>
</a> </a>
</li> </li>
<li class="nav-item dropdown ms-2"> <li class="nav-item dropdown ms-2">
@ -389,21 +391,35 @@
data-bs-auto-close="outside" data-bs-auto-close="outside"
data-bs-offset="0, 8" data-bs-offset="0, 8"
> >
<div class="profile-avatar" title="{% trans 'Your account' %}"> {% if user.profile.profile_image %}
{{ user.username|first|upper }} <img src="{{ user.profile.profile_image.url }}" alt="{{ user.username }}" class="profile-avatar"
</div> style="width: 36px; height: 36px; object-fit: cover; background-color: var(--kaauh-teal); display: inline-block; vertical-align: middle;"
title="{% trans 'Your account' %}">
{% else %}
<div class="profile-avatar" title="{% trans 'Your account' %}">
{{ user.username|first|upper }}
</div>
{% endif %}
{% comment %} <span class="ms-2 d-none d-lg-inline fw-semibold">{{ user.username }}</span> {% endcomment %}
</button> </button>
<ul <ul
class="dropdown-menu dropdown-menu-end py-0 shadow border-0 rounded-3" class="dropdown-menu dropdown-menu-end py-0 shadow border-0 rounded-3"
data-bs-popper="static" data-bs-popper="static"
style="min-width: 240px;" style="min-width: 240px;"
> >
<li class="px-4 py-3 bg-dark-subtle"> <li class="px-4 py-3 ">
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<div class="me-3"> <div class="me-3 d-flex align-items-center justify-content-center" style="min-width: 48px;">
<div class="profile-avatar" style="width: 44px; height: 44px; background-color: var(--kaauh-teal);"> {% if user.profile.profile_image %}
{{ user.username|first|upper }} <img src="{{ user.profile.profile_image.url }}" alt="{{ user.username }}" class="profile-avatar shadow-sm border"
</div> style="width: 44px; height: 44px; object-fit: cover; background-color: var(--kaauh-teal); display: block;"
title="{% trans 'Your account' %}">
{% else %}
<div class="profile-avatar shadow-sm border d-flex align-items-center justify-content-center"
style="width: 44px; height: 44px; background-color: var(--kaauh-teal); font-size: 1.2rem;">
{{ user.username|first|upper }}
</div>
{% endif %}
</div> </div>
<div> <div>
<div class="fw-semibold text-dark">{{ user.get_full_name|default:user.username }}</div> <div class="fw-semibold text-dark">{{ user.get_full_name|default:user.username }}</div>

View File

@ -1,3 +1,4 @@
{% load static i18n %}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
@ -685,8 +686,11 @@
<!-- Sidebar with form elements --> <!-- Sidebar with form elements -->
<div class="sidebar"> <div class="sidebar">
<div class="sidebar-header"> <div class="sidebar-header">
<a class="" href="{% url 'form_templates_list' %}"></a> <a class="" href="{% url 'form_templates_list' %}">
<h2><i class="fas fa-cube"></i> Form Elements</h2> <img src="{% static 'image/kaauh.jpeg' %}" style="height:100px; width:100px;">
</a>
</div> </div>
<div class="field-categories"> <div class="field-categories">
<div class="field-category"> <div class="field-category">

View File

@ -476,11 +476,15 @@
<li class="nav-item"> <li class="nav-item">
<a class="nav-link text-secondary" href="/profile/">{% translate "Profile" %}</a> <a class="nav-link text-secondary" href="/profile/">{% translate "Profile" %}</a>
</li> </li>
<li class="nav-item">
<a class="nav-link text-secondary" href="https://kaauh.edu.sa/career">{% translate "Careers" %}</a>
</li>
</ul> </ul>
</div> </div>
</div> </div>
</nav> </nav>
<nav id="bottomNavbar" class="navbar navbar-expand-lg sticky-top" style="background-color: var(--kaauh-teal); z-index: 1030;"> <nav id="bottomNavbar" class="navbar navbar-expand-lg sticky-top" style="background-color: var(--kaauh-teal); z-index: 1030;">
<span class="ms-2 text-white">JOB ID:&nbsp;&nbsp;{{job_id}}</span>
</nav> </nav>
<div class="page-content-wrapper"> <div class="page-content-wrapper">

View File

@ -161,7 +161,7 @@
<div class="col-12"> <div class="col-12">
<div> <div>
<label for="{{ form.description.id_for_label }}" class="form-label">{% trans "Job Description" %} <span class="text-danger">*</span></label> <label for="{{ form.description.id_for_label }}" class="form-label">{% trans "Job Description" %} <span class="text-danger">*</span></label>
{{ form.description }} {{ form.description}}
{% if form.description.errors %}<div class="text-danger small mt-1">{{ form.description.errors }}</div>{% endif %} {% if form.description.errors %}<div class="text-danger small mt-1">{{ form.description.errors }}</div>{% endif %}
</div> </div>
</div> </div>
@ -169,7 +169,7 @@
<div class="col-12"> <div class="col-12">
<div> <div>
<label for="{{ form.qualifications.id_for_label }}" class="form-label">{% trans "Qualifications and Requirements" %}</label> <label for="{{ form.qualifications.id_for_label }}" class="form-label">{% trans "Qualifications and Requirements" %}</label>
{{ form.qualifications }} {{ form.qualifications}}
{% if form.qualifications.errors %}<div class="text-danger small mt-1">{{ form.qualifications.errors }}</div>{% endif %} {% if form.qualifications.errors %}<div class="text-danger small mt-1">{{ form.qualifications.errors }}</div>{% endif %}
</div> </div>
</div> </div>

View File

@ -339,19 +339,19 @@
{% if job.description %} {% if job.description %}
<div class="mb-4"> <div class="mb-4">
<h5>{% trans "Job Description" %}</h5> <h5>{% trans "Job Description" %}</h5>
<div class="text-secondary">{{ job.description|linebreaks }}</div> <div class="text-secondary">{{ job.description|safe }}</div>
</div> </div>
{% endif %} {% endif %}
{% if job.qualifications %} {% if job.qualifications %}
<div class="mb-4"> <div class="mb-4">
<h5>{% trans "Required Qualifications" %}</h5> <h5>{% trans "Required Qualifications" %}</h5>
<div class="text-secondary">{{ job.qualifications|linebreaks }}</div> <div class="text-secondary">{{ job.qualifications|safe }}</div>
</div> </div>
{% endif %} {% endif %}
{% if job.benefits %} {% if job.benefits %}
<div class="mb-4"> <div class="mb-4">
<h5>{% trans "Benefits" %}</h5> <h5>{% trans "Benefits" %}</h5>
<div class="text-secondary">{{ job.benefits|linebreaks }}</div> <div class="text-secondary">{{ job.benefits|safe}}</div>
</div> </div>
{% endif %} {% endif %}
</div> </div>
@ -361,7 +361,7 @@
<div class="tab-pane fade" id="instructions" role="tabpanel" aria-labelledby="instructions-tab"> <div class="tab-pane fade" id="instructions" role="tabpanel" aria-labelledby="instructions-tab">
<div class="mb-4"> <div class="mb-4">
<h5>{% trans "Application Instructions" %}</h5> <h5>{% trans "Application Instructions" %}</h5>
<div class="text-secondary">{{ job.application_instructions|linebreaks }}</div> <div class="text-secondary">{{ job.application_instructions|safe }}</div>
</div> </div>
</div> </div>
{% endif %} {% endif %}
@ -526,7 +526,7 @@
{% trans "Manage the custom application forms associated with this job posting." %} {% trans "Manage the custom application forms associated with this job posting." %}
</p> </p>
<a href="" class="btn btn-main-action"> <a href="{% url 'create_form_template' %}" class="btn btn-main-action">
<i class="fas fa-plus-circle me-2"></i> {% trans "Create New Form" %} <i class="fas fa-plus-circle me-2"></i> {% trans "Create New Form" %}
</a> </a>
@ -534,9 +534,9 @@
<i class="fas fa-list-alt me-1"></i> {% trans "View All Existing Forms" %} <i class="fas fa-list-alt me-1"></i> {% trans "View All Existing Forms" %}
</a> </a>
<a href="{% url 'candidate_create_for_job' job.slug %}" class="btn btn-success me-2"> <a href="{% url 'candidate_create_for_job' job.slug %}" class="btn btn-main-action">
<i class="fas fa-user-plus"></i> {% trans "Create Candidate" %} <i class="fas fa-user-plus"></i> {% trans "Create Candidate" %}
</a> </a>
</div> </div>
</div> </div>

View File

@ -151,6 +151,9 @@
<li class="nav-item"> <li class="nav-item">
<a class="nav-link text-secondary" href="/profile/">{% translate "Profile" %}</a> <a class="nav-link text-secondary" href="/profile/">{% translate "Profile" %}</a>
</li> </li>
<li class="nav-item">
<a class="nav-link text-secondary" href="https://kaauh.edu.sa/career">{% translate "Careers" %}</a>
</li>
</ul> </ul>
</div> </div>
</div> </div>
@ -233,10 +236,10 @@
{% comment %} <div class="col"> <i class="fas fa-user-tie text-muted me-2"></i> <strong>Created By:</strong> {{ job.created_by|default:"N/A" }} </div> {% endcomment %} {% comment %} <div class="col"> <i class="fas fa-user-tie text-muted me-2"></i> <strong>Created By:</strong> {{ job.created_by|default:"N/A" }} </div> {% endcomment %}
</div> </div>
{% if job.description %}<hr class="my-4"><div class="mb-4"><h5 class="fw-bold" style="color: var(--kaauh-teal-dark);"><i class="fas fa-info-circle me-2"></i>Job Description</h5><div class="text-secondary">{{ job.description|linebreaks }}</div></div>{% endif %} {% if job.description %}<hr class="my-4"><div class="mb-4"><h5 class="fw-bold" style="color: var(--kaauh-teal-dark);"><i class="fas fa-info-circle me-2"></i>Job Description</h5><div class="text-secondary">{{ job.description|safe }}</div></div>{% endif %}
{% if job.qualifications %}<hr class="my-4"><div class="mb-4"><h5 class="fw-bold" style="color: var(--kaauh-teal-dark);"><i class="fas fa-graduation-cap me-2"></i>Qualifications</h5><div class="text-secondary">{{ job.qualifications|linebreaks }}</div></div>{% endif %} {% if job.qualifications %}<hr class="my-4"><div class="mb-4"><h5 class="fw-bold" style="color: var(--kaauh-teal-dark);"><i class="fas fa-graduation-cap me-2"></i>Qualifications</h5><div class="text-secondary">{{ job.qualifications|safe }}</div></div>{% endif %}
{% if job.benefits %}<hr class="my-4"><div class="mb-4"><h5 class="fw-bold" style="color: var(--kaauh-teal-dark);"><i class="fas fa-hand-holding-usd me-2"></i>Benefits</h5><div class="text-secondary">{{ job.benefits|linebreaks }}</div></div>{% endif %} {% if job.benefits %}<hr class="my-4"><div class="mb-4"><h5 class="fw-bold" style="color: var(--kaauh-teal-dark);"><i class="fas fa-hand-holding-usd me-2"></i>Benefits</h5><div class="text-secondary">{{ job.benefits|safe }}</div></div>{% endif %}
{% if job.application_instructions %}<hr class="my-4"><div class="mb-4"><h5 class="fw-bold" style="color: var(--kaauh-teal-dark);"><i class="fas fa-file-alt me-2"></i>Application Instructions</h5><div class="text-secondary">{{ job.application_instructions|linebreaks }}</div></div>{% endif %} {% if job.application_instructions %}<hr class="my-4"><div class="mb-4"><h5 class="fw-bold" style="color: var(--kaauh-teal-dark);"><i class="fas fa-file-alt me-2"></i>Application Instructions</h5><div class="text-secondary">{{ job.application_instructions|safe }}</div></div>{% endif %}
</div> </div>
</div> </div>

View File

@ -123,25 +123,26 @@
<div class="card mb-4 shadow-sm no-hover"> <div class="card mb-4 shadow-sm no-hover">
<div class="card-body"> <div class="card-body">
<h5 class="card-title text-muted mb-3" style="font-weight: 500;">Filter & Search</h5> <div class="row">
<form method="get" class="row g-3 align-items-end">
<div class="col-md-4"> <div class="col-md-4">
<label for="search" class="form-label small text-muted">Search by Title or Department</label> <label for="search" class="form-label small text-muted">Search by Title or Department</label>
<div class="input-group input-group-lg"> <div class="input-group input-group-lg mb-3">
<span class="input-group-text bg-white border-end-0"><i class="fas fa-search text-muted"></i></span> <form method="get" action="" class="w-100">
<input type="text" name="q" id="search" class="form-control form-control-search border-start-0" {% include 'includes/search_form.html' %}
placeholder="e.g., 'Professor' or 'Marketing'" </form>
value="{{ search_query|default_if_none:'' }}">
</div> </div>
</div> </div>
<div class="col-md-8">
{% url 'job_list' as job_list_url %}
<form method="GET" class="row g-3 align-items-end" >
<div class="col-md-3"> <div class="col-md-3">
<label for="status" class="form-label small text-muted">Filter by Status</label> <label for="status" class="form-label small text-muted">Filter by Status</label>
<select name="status" id="status" class="form-select form-select-sm"> <select name="status" id="status" class="form-select form-select-sm">
<option value="">All Statuses</option> <option value="">All Statuses</option>
<option value="DRAFT" {% if status_filter == 'DRAFT' %}selected{% endif %}>Draft</option> <option value="DRAFT" {% if status_filter == 'DRAFT' %}selected{% endif %}>Draft</option>
<option value="ACTIVE" {% if status_filter == 'ACTIVE' %}selected{% endif %}>Active</option> <option value="PUBLISHED" {% if status_filter == 'PUBLISHED' %}selected{% endif %}>Published</option>
<option value="CLOSED" {% if status_filter == 'CLOSED' %}selected{% endif %}>Closed</option> <option value="CLOSED" {% if status_filter == 'CLOSED' %}selected{% endif %}>Closed</option>
<option value="ARCHIVED" {% if status_filter == 'ARCHIVED' %}selected{% endif %}>Archived</option> <option value="ARCHIVED" {% if status_filter == 'ARCHIVED' %}selected{% endif %}>Archived</option>
</select> </select>
@ -152,17 +153,13 @@
<button type="submit" class="btn btn-main-action btn-lg"> <button type="submit" class="btn btn-main-action btn-lg">
<i class="fas fa-filter me-1"></i> Apply Filters <i class="fas fa-filter me-1"></i> Apply Filters
</button> </button>
{# Show Clear button if any filter/search is active #}
{% if status_filter or search_query %}
<a href="{% url 'job_list' %}" class="btn btn-outline-danger btn-sm">
<i class="fas fa-times me-1"></i> Clear Filters
</a>
{% endif %}
</div> </div>
</div> </div>
</form> </form>
</div>
</div>
</div> </div>
</div> </div>