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
# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'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 (
JobPosting, Candidate, TrainingMaterial, ZoomMeeting,
FormTemplate, FormStage, FormField, FormSubmission, FieldResponse,
SharedFormTemplate, Source, HiringAgency, IntegrationLog,InterviewSchedule
SharedFormTemplate, Source, HiringAgency, IntegrationLog,InterviewSchedule,Profile
)
class FormFieldInline(admin.TabularInline):
@ -261,4 +261,5 @@ admin.site.register(FormStage)
admin.site.register(FormField)
admin.site.register(FieldResponse)
admin.site.register(InterviewSchedule)
admin.site.register(Profile)
# admin.site.register(HiringAgency)

View File

@ -259,8 +259,9 @@ class JobPostingForm(forms.ModelForm):
'type': 'date'
}),
'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={
'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.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):
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('Created at'))
updated_at = models.DateTimeField(auto_now=True, verbose_name=_('Updated at'))

View File

@ -83,8 +83,7 @@ class ZoomMeetingListView(ListView):
if search_query:
queryset = queryset.filter(
Q(topic__icontains=search_query) |
Q(meeting_id__icontains=search_query) |
Q(host_email__icontains=search_query)
Q(meeting_id__icontains=search_query)
)
return queryset
@ -143,27 +142,30 @@ def ZoomMeetingDeleteView(request, pk):
#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')
# 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)
# # Filter by status if provided
# print(f"the request is: {request} ")
# status=request.GET.get('status')
# print(f"DEBUG: Status filter received: {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
})
# #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)
@ -761,7 +763,8 @@ def delete_form_template(request, template_id):
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})
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"])
def submit_form(request, template_id):
"""Handle form submission"""

View File

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

View File

@ -320,11 +320,13 @@
<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' %}">
<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">
<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>
<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 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>
{% trans "Form Templates" %}
</span>
</a>
</li>
<li class="nav-item dropdown ms-2">
@ -389,21 +391,35 @@
data-bs-auto-close="outside"
data-bs-offset="0, 8"
>
<div class="profile-avatar" title="{% trans 'Your account' %}">
{{ user.username|first|upper }}
</div>
{% if user.profile.profile_image %}
<img src="{{ user.profile.profile_image.url }}" alt="{{ user.username }}" class="profile-avatar"
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>
<ul
class="dropdown-menu dropdown-menu-end py-0 shadow border-0 rounded-3"
data-bs-popper="static"
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="me-3">
<div class="profile-avatar" style="width: 44px; height: 44px; background-color: var(--kaauh-teal);">
{{ user.username|first|upper }}
</div>
<div class="me-3 d-flex align-items-center justify-content-center" style="min-width: 48px;">
{% if user.profile.profile_image %}
<img src="{{ user.profile.profile_image.url }}" alt="{{ user.username }}" class="profile-avatar shadow-sm border"
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 class="fw-semibold text-dark">{{ user.get_full_name|default:user.username }}</div>

View File

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

View File

@ -476,11 +476,15 @@
<li class="nav-item">
<a class="nav-link text-secondary" href="/profile/">{% translate "Profile" %}</a>
</li>
<li class="nav-item">
<a class="nav-link text-secondary" href="https://kaauh.edu.sa/career">{% translate "Careers" %}</a>
</li>
</ul>
</div>
</div>
</nav>
<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>
<div class="page-content-wrapper">

View File

@ -161,7 +161,7 @@
<div class="col-12">
<div>
<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 %}
</div>
</div>
@ -169,7 +169,7 @@
<div class="col-12">
<div>
<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 %}
</div>
</div>

View File

@ -339,19 +339,19 @@
{% if job.description %}
<div class="mb-4">
<h5>{% trans "Job Description" %}</h5>
<div class="text-secondary">{{ job.description|linebreaks }}</div>
<div class="text-secondary">{{ job.description|safe }}</div>
</div>
{% endif %}
{% if job.qualifications %}
<div class="mb-4">
<h5>{% trans "Required Qualifications" %}</h5>
<div class="text-secondary">{{ job.qualifications|linebreaks }}</div>
<div class="text-secondary">{{ job.qualifications|safe }}</div>
</div>
{% endif %}
{% if job.benefits %}
<div class="mb-4">
<h5>{% trans "Benefits" %}</h5>
<div class="text-secondary">{{ job.benefits|linebreaks }}</div>
<div class="text-secondary">{{ job.benefits|safe}}</div>
</div>
{% endif %}
</div>
@ -361,7 +361,7 @@
<div class="tab-pane fade" id="instructions" role="tabpanel" aria-labelledby="instructions-tab">
<div class="mb-4">
<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>
{% endif %}
@ -526,7 +526,7 @@
{% trans "Manage the custom application forms associated with this job posting." %}
</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" %}
</a>
@ -534,9 +534,9 @@
<i class="fas fa-list-alt me-1"></i> {% trans "View All Existing Forms" %}
</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" %}
</a>
</a>
</div>
</div>

View File

@ -151,6 +151,9 @@
<li class="nav-item">
<a class="nav-link text-secondary" href="/profile/">{% translate "Profile" %}</a>
</li>
<li class="nav-item">
<a class="nav-link text-secondary" href="https://kaauh.edu.sa/career">{% translate "Careers" %}</a>
</li>
</ul>
</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 %}
</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.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.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.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.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|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|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|safe }}</div></div>{% endif %}
</div>
</div>

View File

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